Move all example pallets under examples folder. (#10215)

* Put all examples under one folder

Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com>

* Updated Cargo.toml

Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com>

* updated for ci script

Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com>

* update

Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com>

* Added notes that example pallets are not meant to be used in production.

Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com>

* updated

Signed-off-by: Jimmy Chu <jimmychu0807@gmail.com>
This commit is contained in:
Jimmy Chu
2021-11-10 16:11:28 +08:00
committed by GitHub
parent 617e2cc75e
commit db59cfcf14
18 changed files with 81 additions and 60 deletions
+46
View File
@@ -0,0 +1,46 @@
[package]
name = "pallet-example-basic"
version = "4.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Unlicense"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME example pallet"
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
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-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" }
sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" }
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" }
[dev-dependencies]
sp-core = { version = "4.0.0-dev", path = "../../../primitives/core", default-features = false }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-balances/std",
"scale-info/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std"
]
runtime-benchmarks = ["frame-benchmarking"]
try-runtime = ["frame-support/try-runtime"]
+240
View File
@@ -0,0 +1,240 @@
<!-- markdown-link-check-disable -->
# Basic Example Pallet
<!-- Original author of paragraph: @gavofyork -->
The Example: A simple example of a FRAME pallet demonstrating
concepts, APIs and structures common to most FRAME runtimes.
Run `cargo doc --package pallet-example-basic --open` to view this pallet's documentation.
**This pallet serves as an example and is not meant to be used in production.**
### Documentation Guidelines:
<!-- Original author of paragraph: Various. Based on collation of review comments to PRs addressing issues with -->
<!-- label 'S3-FRAME' in https://github.com/paritytech/substrate-developer-hub/issues -->
<ul>
<li>Documentation comments (i.e. <code>/// comment</code>) - should
accompany pallet functions and be restricted to the pallet interface,
not the internals of the pallet implementation. Only state inputs,
outputs, and a brief description that mentions whether calling it
requires root, but without repeating the source code details.
Capitalize the first word of each documentation comment and end it with
a full stop. See
<a href="https://github.com/paritytech/substrate#72-contributing-to-documentation-for-substrate-packages"
target="_blank"> Generic example of annotating source code with documentation comments</a></li>
<li>Self-documenting code - Try to refactor code to be self-documenting.</li>
<li>Code comments - Supplement complex code with a brief explanation, not every line of code.</li>
<li>Identifiers - surround by backticks (i.e. <code>INHERENT_IDENTIFIER</code>, <code>InherentType</code>,
<code>u64</code>)</li>
<li>Usage scenarios - should be simple doctests. The compiler should ensure they stay valid.</li>
<li>Extended tutorials - should be moved to external files and refer to.</li>
<!-- Original author of paragraph: @AmarRSingh -->
<li>Mandatory - include all of the sections/subsections where <b>MUST</b> is specified.</li>
<li>Optional - optionally include sections/subsections where <b>CAN</b> is specified.</li>
</ul>
### Documentation Template:<br>
Copy and paste this template from frame/examples/basic/src/lib.rs into file
`frame/<INSERT_CUSTOM_PALLET_NAME>/src/lib.rs` of your own custom pallet and complete it.
<details><p><pre>
// Add heading with custom pallet name
\# <INSERT_CUSTOM_PALLET_NAME> Pallet
// Add simple description
// Include the following links that shows what trait needs to be implemented to use the pallet
// and the supported dispatchables that are documented in the Call enum.
- \[`<INSERT_CUSTOM_PALLET_NAME>::Config`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/trait.Config.html)
- \[`Call`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/enum.Call.html)
- \[`Module`](https://docs.rs/pallet-example-basic/latest/pallet_example_basic/struct.Module.html)
\## Overview
<!-- Original author of paragraph: Various. See https://github.com/paritytech/substrate-developer-hub/issues/44 -->
// Short description of pallet's purpose.
// Links to Traits that should be implemented.
// What this pallet is for.
// What functionality the pallet provides.
// When to use the pallet (use case examples).
// How it is used.
// Inputs it uses and the source of each input.
// Outputs it produces.
<!-- Original author of paragraph: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
<!-- and comment https://github.com/paritytech/substrate-developer-hub/issues/44#issuecomment-471982710 -->
\## Terminology
// Add terminology used in the custom pallet. Include concepts, storage items, or actions that you think
// deserve to be noted to give context to the rest of the documentation or pallet usage. The author needs to
// use some judgment about what is included. We don't want a list of every storage item nor types - the user
// can go to the code for that. For example, "transfer fee" is obvious and should not be included, but
// "free balance" and "reserved balance" should be noted to give context to the pallet.
// Please do not link to outside resources. The reference docs should be the ultimate source of truth.
<!-- Original author of heading: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
\## Goals
// Add goals that the custom pallet is designed to achieve.
<!-- Original author of heading: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
\### Scenarios
<!-- Original author of paragraph: @Kianenigma. Based on PR https://github.com/paritytech/substrate/pull/1951 -->
\#### <INSERT_SCENARIO_NAME>
// Describe requirements prior to interacting with the custom pallet.
// Describe the process of interacting with the custom pallet for this scenario and public API functions used.
\## Interface
\### Supported Origins
// What origins are used and supported in this pallet (root, signed, none)
// i.e. root when <code>\`ensure_root\`</code> used
// i.e. none when <code>\`ensure_none\`</code> used
// i.e. signed when <code>\`ensure_signed\`</code> used
<code>\`inherent\`</code> <INSERT_DESCRIPTION>
<!-- Original author of paragraph: @Kianenigma in comment -->
<!-- https://github.com/paritytech/substrate-developer-hub/issues/44#issuecomment-471982710 -->
\### Types
// Type aliases. Include any associated types and where the user would typically define them.
<code>\`ExampleType\`</code> <INSERT_DESCRIPTION>
<!-- Original author of paragraph: ??? -->
// Reference documentation of aspects such as `storageItems` and `dispatchable` functions should only be
// included in the https://docs.rs Rustdocs for Substrate and not repeated in the README file.
\### Dispatchable Functions
<!-- Original author of paragraph: @AmarRSingh & @joepetrowski -->
// A brief description of dispatchable functions and a link to the rustdoc with their actual documentation.
// <b>MUST</b> have link to Call enum
// <b>MUST</b> have origin information included in function doc
// <b>CAN</b> have more info up to the user
\### Public Functions
<!-- Original author of paragraph: @joepetrowski -->
// A link to the rustdoc and any notes about usage in the pallet, not for specific functions.
// For example, in the Balances Pallet: "Note that when using the publicly exposed functions,
// you (the runtime developer) are responsible for implementing any necessary checks
// (e.g. that the sender is the signer) before calling a function that will affect storage."
<!-- Original author of paragraph: @AmarRSingh -->
// It is up to the writer of the respective pallet (with respect to how much information to provide).
\#### Public Inspection functions - Immutable (getters)
// Insert a subheading for each getter function signature
\##### <code>\`example_getter_name()\`</code>
// What it returns
// Why, when, and how often to call it
// When it could panic or error
// When safety issues to consider
\#### Public Mutable functions (changing state)
// Insert a subheading for each setter function signature
\##### <code>\`example_setter_name(origin, parameter_name: T::ExampleType)\`</code>
// What state it changes
// Why, when, and how often to call it
// When it could panic or error
// When safety issues to consider
// What parameter values are valid and why
\### Storage Items
// Explain any storage items included in this pallet
\### Digest Items
// Explain any digest items included in this pallet
\### Inherent Data
// Explain what inherent data (if any) is defined in the pallet and any other related types
\### Events:
// Insert events for this pallet if any
\### Errors:
// Explain what generates errors
\## Usage
// Insert 2-3 examples of usage and code snippets that show how to
// use <INSERT_CUSTOM_PALLET_NAME> Pallet in a custom pallet.
\### Prerequisites
// Show how to include necessary imports for <INSERT_CUSTOM_PALLET_NAME> and derive
// your pallet configuration trait with the `INSERT_CUSTOM_PALLET_NAME` trait.
\```rust
use <INSERT_CUSTOM_PALLET_NAME>;
pub trait Config: <INSERT_CUSTOM_PALLET_NAME>::Config { }
\```
\### Simple Code Snippet
// Show a simple example (e.g. how to query a public getter function of <INSERT_CUSTOM_PALLET_NAME>)
\### Example from FRAME
// Show a usage example in an actual runtime
// See:
// - Substrate TCR https://github.com/parity-samples/substrate-tcr
// - Substrate Kitties https://shawntabrizi.github.io/substrate-collectables-workshop/#/
\## Genesis Config
<!-- Original author of paragraph: @joepetrowski -->
\## Dependencies
// Dependencies on other FRAME pallets and the genesis config should be mentioned,
// but not the Rust Standard Library.
// Genesis configuration modifications that may be made to incorporate this pallet
// Interaction with other pallets
<!-- Original author of heading: @AmarRSingh -->
\## Related Pallets
// Interaction with other pallets in the form of a bullet point list
\## References
<!-- Original author of paragraph: @joepetrowski -->
// Links to reference material, if applicable. For example, Phragmen, W3F research, etc.
// that the implementation is based on.
</pre></p></details>
License: Unlicense
@@ -0,0 +1,78 @@
// 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.
//! Benchmarking for pallet-example-basic.
#![cfg(feature = "runtime-benchmarks")]
use crate::*;
use frame_benchmarking::{benchmarks, whitelisted_caller};
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
//
// The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file.
// The exact command of how the estimate generated is printed at the top of the file.
// Details on using the benchmarks macro can be seen at:
// https://paritytech.github.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks
benchmarks! {
// This will measure the execution time of `set_dummy` for b in [1..1000] range.
set_dummy_benchmark {
// This is the benchmark setup phase
let b in 1 .. 1000;
}: set_dummy(RawOrigin::Root, b.into()) // The execution phase is just running `set_dummy` extrinsic call
verify {
// This is the optional benchmark verification phase, asserting certain states.
assert_eq!(Pallet::<T>::dummy(), Some(b.into()))
}
// This will measure the execution time of `accumulate_dummy` for b in [1..1000] range.
// The benchmark execution phase is shorthanded. When the name of the benchmark case is the same
// as the extrinsic call. `_(...)` is used to represent the extrinsic name.
// The benchmark verification phase is omitted.
accumulate_dummy {
let b in 1 .. 1000;
// The caller account is whitelisted for DB reads/write by the benchmarking macro.
let caller: T::AccountId = whitelisted_caller();
}: _(RawOrigin::Signed(caller), b.into())
// This will measure the execution time of sorting a vector.
sort_vector {
let x in 0 .. 10000;
let mut m = Vec::<u32>::new();
for i in (0..x).rev() {
m.push(i);
}
}: {
// The benchmark execution phase could also be a closure with custom code
m.sort();
}
// This line generates test cases for benchmarking, and could be run by:
// `cargo test -p pallet-example-basic --all-features`, you will see one line per case:
// `test benchmarking::bench_sort_vector ... ok`
// `test benchmarking::bench_accumulate_dummy ... ok`
// `test benchmarking::bench_set_dummy_benchmark ... ok` in the result.
//
// The line generates three steps per benchmark, with repeat=1 and the three steps are
// [low, mid, high] of the range.
impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test)
}
+751
View File
@@ -0,0 +1,751 @@
// This file is part of Substrate.
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! <!-- markdown-link-check-disable -->
//! # Basic Example Pallet
//!
//! <!-- Original author of paragraph: @gavofyork -->
//! The Example: A simple example of a FRAME pallet demonstrating
//! concepts, APIs and structures common to most FRAME runtimes.
//!
//! Run `cargo doc --package pallet-example-basic --open` to view this pallet's documentation.
//!
//! **This pallet serves as an example and is not meant to be used in production.**
//!
//! ### Documentation Guidelines:
//!
//! <!-- Original author of paragraph: Various. Based on collation of review comments to PRs
//! addressing issues with --> <!-- label 'S3-FRAME' in https://github.com/paritytech/substrate-developer-hub/issues -->
//! <ul>
//! <li>Documentation comments (i.e. <code>/// comment</code>) - should
//! accompany pallet functions and be restricted to the pallet interface,
//! not the internals of the pallet implementation. Only state inputs,
//! outputs, and a brief description that mentions whether calling it
//! requires root, but without repeating the source code details.
//! Capitalize the first word of each documentation comment and end it with
//! a full stop. See
//! <a href="https://github.com/paritytech/substrate#72-contributing-to-documentation-for-substrate-packages"
//! target="_blank"> Generic example of annotating source code with documentation comments</a></li>
//!
//! <li>Self-documenting code - Try to refactor code to be self-documenting.</li>
//!
//! <li>Code comments - Supplement complex code with a brief explanation, not every line of
//! code.</li>
//!
//! <li>Identifiers - surround by backticks (i.e. <code>INHERENT_IDENTIFIER</code>,
//! <code>InherentType</code>, <code>u64</code>)</li>
//!
//! <li>Usage scenarios - should be simple doctests. The compiler should ensure they stay
//! valid.</li>
//!
//! <li>Extended tutorials - should be moved to external files and refer to.</li>
//!
//! <li>Mandatory - include all of the sections/subsections where <b>MUST</b> is specified.</li>
//!
//! <li>Optional - optionally include sections/subsections where <b>CAN</b> is specified.</li>
//! </ul>
//!
//! ### Documentation Template:<br>
//!
//! Copy and paste this template from frame/examples/basic/src/lib.rs into file
//! `frame/<INSERT_CUSTOM_PALLET_NAME>/src/lib.rs` of your own custom pallet and complete it.
//! <details><p><pre>
//! // Add heading with custom pallet name
//!
//! \# <INSERT_CUSTOM_PALLET_NAME> Pallet
//!
//! // Add simple description
//!
//! // Include the following links that shows what trait needs to be implemented to use the pallet
//! // and the supported dispatchables that are documented in the Call enum.
//!
//! - \[`Config`]
//! - \[`Call`]
//! - \[`Pallet`]
//!
//! \## Overview
//!
//! <!-- Original author of paragraph: Various. See https://github.com/paritytech/substrate-developer-hub/issues/44 -->
//! // Short description of pallet's purpose.
//! // Links to Traits that should be implemented.
//! // What this pallet is for.
//! // What functionality the pallet provides.
//! // When to use the pallet (use case examples).
//! // How it is used.
//! // Inputs it uses and the source of each input.
//! // Outputs it produces.
//!
//! <!-- Original author of paragraph: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
//! <!-- and comment https://github.com/paritytech/substrate-developer-hub/issues/44#issuecomment-471982710 -->
//!
//! \## Terminology
//!
//! // Add terminology used in the custom pallet. Include concepts, storage items, or actions that
//! you think // deserve to be noted to give context to the rest of the documentation or pallet
//! usage. The author needs to // use some judgment about what is included. We don't want a list of
//! every storage item nor types - the user // can go to the code for that. For example, "transfer
//! fee" is obvious and should not be included, but // "free balance" and "reserved balance" should
//! be noted to give context to the pallet. // Please do not link to outside resources. The
//! reference docs should be the ultimate source of truth.
//!
//! <!-- Original author of heading: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
//!
//! \## Goals
//!
//! // Add goals that the custom pallet is designed to achieve.
//!
//! <!-- Original author of heading: @Kianenigma in PR https://github.com/paritytech/substrate/pull/1951 -->
//!
//! \### Scenarios
//!
//! <!-- Original author of paragraph: @Kianenigma. Based on PR https://github.com/paritytech/substrate/pull/1951 -->
//!
//! \#### <INSERT_SCENARIO_NAME>
//!
//! // Describe requirements prior to interacting with the custom pallet.
//! // Describe the process of interacting with the custom pallet for this scenario and public API
//! functions used.
//!
//! \## Interface
//!
//! \### Supported Origins
//!
//! // What origins are used and supported in this pallet (root, signed, none)
//! // i.e. root when <code>\`ensure_root\`</code> used
//! // i.e. none when <code>\`ensure_none\`</code> used
//! // i.e. signed when <code>\`ensure_signed\`</code> used
//!
//! <code>\`inherent\`</code> <INSERT_DESCRIPTION>
//!
//! <!-- Original author of paragraph: @Kianenigma in comment -->
//! <!-- https://github.com/paritytech/substrate-developer-hub/issues/44#issuecomment-471982710 -->
//!
//! \### Types
//!
//! // Type aliases. Include any associated types and where the user would typically define them.
//!
//! <code>\`ExampleType\`</code> <INSERT_DESCRIPTION>
//!
//! <!-- Original author of paragraph: ??? -->
//!
//! // Reference documentation of aspects such as `storageItems` and `dispatchable` functions should
//! // only be included in the <https://docs.rs> Rustdocs for Substrate and not repeated in the
//! // README file.
//!
//! \### Dispatchable Functions
//!
//! <!-- Original author of paragraph: @AmarRSingh & @joepetrowski -->
//!
//! // A brief description of dispatchable functions and a link to the rustdoc with their actual
//! documentation.
//!
//! // <b>MUST</b> have link to Call enum
//! // <b>MUST</b> have origin information included in function doc
//! // <b>CAN</b> have more info up to the user
//!
//! \### Public Functions
//!
//! <!-- Original author of paragraph: @joepetrowski -->
//!
//! // A link to the rustdoc and any notes about usage in the pallet, not for specific functions.
//! // For example, in the Balances Pallet: "Note that when using the publicly exposed functions,
//! // you (the runtime developer) are responsible for implementing any necessary checks
//! // (e.g. that the sender is the signer) before calling a function that will affect storage."
//!
//! <!-- Original author of paragraph: @AmarRSingh -->
//!
//! // It is up to the writer of the respective pallet (with respect to how much information to
//! provide).
//!
//! \#### Public Inspection functions - Immutable (getters)
//!
//! // Insert a subheading for each getter function signature
//!
//! \##### <code>\`example_getter_name()\`</code>
//!
//! // What it returns
//! // Why, when, and how often to call it
//! // When it could panic or error
//! // When safety issues to consider
//!
//! \#### Public Mutable functions (changing state)
//!
//! // Insert a subheading for each setter function signature
//!
//! \##### <code>\`example_setter_name(origin, parameter_name: T::ExampleType)\`</code>
//!
//! // What state it changes
//! // Why, when, and how often to call it
//! // When it could panic or error
//! // When safety issues to consider
//! // What parameter values are valid and why
//!
//! \### Storage Items
//!
//! // Explain any storage items included in this pallet
//!
//! \### Digest Items
//!
//! // Explain any digest items included in this pallet
//!
//! \### Inherent Data
//!
//! // Explain what inherent data (if any) is defined in the pallet and any other related types
//!
//! \### Events:
//!
//! // Insert events for this pallet if any
//!
//! \### Errors:
//!
//! // Explain what generates errors
//!
//! \## Usage
//!
//! // Insert 2-3 examples of usage and code snippets that show how to
//! // use <INSERT_CUSTOM_PALLET_NAME> Pallet in a custom pallet.
//!
//! \### Prerequisites
//!
//! // Show how to include necessary imports for <INSERT_CUSTOM_PALLET_NAME> and derive
//! // your pallet configuration trait with the `INSERT_CUSTOM_PALLET_NAME` trait.
//!
//! \```rust
//! use <INSERT_CUSTOM_PALLET_NAME>;
//!
//! pub trait Config: <INSERT_CUSTOM_PALLET_NAME>::Config { }
//! \```
//!
//! \### Simple Code Snippet
//!
//! // Show a simple example (e.g. how to query a public getter function of
//! <INSERT_CUSTOM_PALLET_NAME>)
//!
//! \### Example from FRAME
//!
//! // Show a usage example in an actual runtime
//!
//! // See:
//! // - Substrate TCR <https://github.com/parity-samples/substrate-tcr>
//! // - Substrate Kitties <https://shawntabrizi.github.io/substrate-collectables-workshop/#/>
//!
//! \## Genesis Config
//!
//! <!-- Original author of paragraph: @joepetrowski -->
//!
//! \## Dependencies
//!
//! // Dependencies on other FRAME pallets and the genesis config should be mentioned,
//! // but not the Rust Standard Library.
//! // Genesis configuration modifications that may be made to incorporate this pallet
//! // Interaction with other pallets
//!
//! <!-- Original author of heading: @AmarRSingh -->
//!
//! \## Related Pallets
//!
//! // Interaction with other pallets in the form of a bullet point list
//!
//! \## References
//!
//! <!-- Original author of paragraph: @joepetrowski -->
//!
//! // Links to reference material, if applicable. For example, Phragmen, W3F research, etc.
//! // that the implementation is based on.
//! </pre></p></details>
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::{
dispatch::DispatchResult,
traits::IsSubType,
weights::{ClassifyDispatch, DispatchClass, Pays, PaysFee, WeighData, Weight},
};
use frame_system::ensure_signed;
use log::info;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{Bounded, DispatchInfoOf, SaturatedConversion, Saturating, SignedExtension},
transaction_validity::{
InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
},
};
use sp_std::{marker::PhantomData, prelude::*};
// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;
#[cfg(test)]
mod tests;
mod benchmarking;
pub mod weights;
pub use weights::*;
/// A type alias for the balance type from this pallet's point of view.
type BalanceOf<T> = <T as pallet_balances::Config>::Balance;
const MILLICENTS: u32 = 1_000_000_000;
// A custom weight calculator tailored for the dispatch call `set_dummy()`. This actually examines
// the arguments and makes a decision based upon them.
//
// The `WeightData<T>` trait has access to the arguments of the dispatch that it wants to assign a
// weight to. Nonetheless, the trait itself cannot make any assumptions about what the generic type
// of the arguments (`T`) is. Based on our needs, we could replace `T` with a more concrete type
// while implementing the trait. The `pallet::weight` expects whatever implements `WeighData<T>` to
// replace `T` with a tuple of the dispatch arguments. This is exactly how we will craft the
// implementation below.
//
// The rules of `WeightForSetDummy` are as follows:
// - The final weight of each dispatch is calculated as the argument of the call multiplied by the
// parameter given to the `WeightForSetDummy`'s constructor.
// - 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
//
// 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.
struct WeightForSetDummy<T: pallet_balances::Config>(BalanceOf<T>);
impl<T: pallet_balances::Config> WeighData<(&BalanceOf<T>,)> for WeightForSetDummy<T> {
fn weigh_data(&self, target: (&BalanceOf<T>,)) -> Weight {
let multiplier = self.0;
// *target.0 is the amount passed into the extrinsic
let cents = *target.0 / <BalanceOf<T>>::from(MILLICENTS);
(cents * multiplier).saturated_into::<Weight>()
}
}
impl<T: pallet_balances::Config> ClassifyDispatch<(&BalanceOf<T>,)> for WeightForSetDummy<T> {
fn classify_dispatch(&self, target: (&BalanceOf<T>,)) -> DispatchClass {
if *target.0 > <BalanceOf<T>>::from(1000u32) {
DispatchClass::Operational
} else {
DispatchClass::Normal
}
}
}
impl<T: pallet_balances::Config> PaysFee<(&BalanceOf<T>,)> for WeightForSetDummy<T> {
fn pays_fee(&self, _target: (&BalanceOf<T>,)) -> Pays {
Pays::Yes
}
}
// Definition of the pallet logic, to be aggregated at runtime definition through
// `construct_runtime`.
#[frame_support::pallet]
pub mod pallet {
// Import various types used to declare pallet in scope.
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// Our pallet's configuration trait. All our types and constants go in here. If the
/// pallet is dependent on specific other pallets, then their configuration traits
/// should be added to our implied traits list.
///
/// `frame_system::Config` should always be included.
#[pallet::config]
pub trait Config: pallet_balances::Config + frame_system::Config {
// Setting a constant config parameter from the runtime
#[pallet::constant]
type MagicNumber: Get<Self::Balance>;
/// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
}
// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
// method.
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
// Pallet implements [`Hooks`] trait to define some logic to execute in some context.
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
// `on_initialize` is executed at the beginning of the block before any extrinsic are
// dispatched.
//
// This function must return the weight consumed by `on_initialize` and `on_finalize`.
fn on_initialize(_n: T::BlockNumber) -> Weight {
// Anything that needs to be done at the start of the block.
// We don't do anything here.
0
}
// `on_finalize` is executed at the end of block after all extrinsic are dispatched.
fn on_finalize(_n: T::BlockNumber) {
// Perform necessary data/state clean up here.
}
// A runtime code run after every block and have access to extended set of APIs.
//
// For instance you can generate extrinsics for the upcoming produced block.
fn offchain_worker(_n: T::BlockNumber) {
// We don't do anything here.
// but we could dispatch extrinsic (transaction/unsigned/inherent) using
// sp_io::submit_extrinsic.
// To see example on offchain worker, please refer to example-offchain-worker pallet
// accompanied in this repository.
}
}
// The call declaration. This states the entry points that we handle. The
// macro takes care of the marshalling of arguments and dispatch.
//
// Anyone can have these functions execute by signing and submitting
// an extrinsic. Ensure that calls into each of these execute in a time, memory and
// using storage space proportional to any costs paid for by the caller or otherwise the
// difficulty of forcing the call to happen.
//
// Generally you'll want to split these into three groups:
// - Public calls that are signed by an external account.
// - Root calls that are allowed to be made only by the governance system.
// - Unsigned calls that can be of two kinds:
// * "Inherent extrinsics" that are opinions generally held by the block authors that build
// child blocks.
// * Unsigned Transactions that are of intrinsic recognizable utility to the network, and are
// validated by the runtime.
//
// Information about where this dispatch initiated from is provided as the first argument
// "origin". As such functions must always look like:
//
// `fn foo(origin: OriginFor<T>, bar: Bar, baz: Baz) -> DispatchResultWithPostInfo { ... }`
//
// The `DispatchResultWithPostInfo` is required as part of the syntax (and can be found at
// `pallet_prelude::DispatchResultWithPostInfo`).
//
// There are three entries in the `frame_system::Origin` enum that correspond
// to the above bullets: `::Signed(AccountId)`, `::Root` and `::None`. You should always match
// against them as the first thing you do in your function. There are three convenience calls
// in system that do the matching for you and return a convenient result: `ensure_signed`,
// `ensure_root` and `ensure_none`.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// This is your public interface. Be extremely careful.
/// This is just a simple example of how to interact with the pallet from the external
/// world.
// This just increases the value of `Dummy` by `increase_by`.
//
// Since this is a dispatched function there are two extremely important things to
// remember:
//
// - MUST NOT PANIC: Under no circumstances (save, perhaps, storage getting into an
// irreparably damaged state) must this function panic.
// - NO SIDE-EFFECTS ON ERROR: This function must either complete totally (and return
// `Ok(())` or it must have no side-effects on storage and return `Err('Some reason')`.
//
// The first is relatively easy to audit for - just ensure all panickers are removed from
// logic that executes in production (which you do anyway, right?!). To ensure the second
// is followed, you should do all tests for validity at the top of your function. This
// is stuff like checking the sender (`origin`) or that state is such that the operation
// makes sense.
//
// Once you've determined that it's all good, then enact the operation and change storage.
// If you can't be certain that the operation will succeed without substantial computation
// then you have a classic blockchain attack scenario. The normal way of managing this is
// to attach a bond to the operation. As the first major alteration of storage, reserve
// some value from the sender's account (`Balances` Pallet has a `reserve` function for
// exactly this scenario). This amount should be enough to cover any costs of the
// substantial execution in case it turns out that you can't proceed with the operation.
//
// If it eventually transpires that the operation is fine and, therefore, that the
// expense of the checks should be borne by the network, then you can refund the reserved
// deposit. If, however, the operation turns out to be invalid and the computation is
// wasted, then you can burn it or repatriate elsewhere.
//
// Security bonds ensure that attackers can't game it by ensuring that anyone interacting
// with the system either progresses it or pays for the trouble of faffing around with
// no progress.
//
// If you don't respect these rules, it is likely that your chain will be attackable.
//
// Each transaction must define a `#[pallet::weight(..)]` attribute to convey a set of
// static information about its dispatch. FRAME System and FRAME Executive pallet then use
// this information to properly execute the transaction, whilst keeping the total load of
// the chain in a moderate rate.
//
// The parenthesized value of the `#[pallet::weight(..)]` attribute can be any type that
// implements a set of traits, namely [`WeighData`], [`ClassifyDispatch`], and
// [`PaysFee`]. The first conveys the weight (a numeric representation of pure
// execution time and difficulty) of the transaction and the second demonstrates the
// [`DispatchClass`] of the call, the third gives whereas extrinsic must pay fees or not.
// A higher weight means a larger transaction (less of which can be placed in a single
// block).
//
// The weight for this extrinsic we rely on the auto-generated `WeightInfo` from the
// benchmark toolchain.
#[pallet::weight(
<T as pallet::Config>::WeightInfo::accumulate_dummy((*increase_by).saturated_into())
)]
pub fn accumulate_dummy(origin: OriginFor<T>, increase_by: T::Balance) -> DispatchResult {
// This is a public call, so we ensure that the origin is some signed account.
let _sender = ensure_signed(origin)?;
// Read the value of dummy from storage.
// let dummy = Self::dummy();
// Will also work using the `::get` on the storage item type itself:
// let dummy = <Dummy<T>>::get();
// Calculate the new value.
// let new_dummy = dummy.map_or(increase_by, |dummy| dummy + increase_by);
// Put the new value into storage.
// <Dummy<T>>::put(new_dummy);
// Will also work with a reference:
// <Dummy<T>>::put(&new_dummy);
// Here's the new one of read and then modify the value.
<Dummy<T>>::mutate(|dummy| {
// Using `saturating_add` instead of a regular `+` to avoid overflowing
let new_dummy = dummy.map_or(increase_by, |d| d.saturating_add(increase_by));
*dummy = Some(new_dummy);
});
// Let's deposit an event to let the outside world know this happened.
Self::deposit_event(Event::AccumulateDummy(increase_by));
// All good, no refund.
Ok(())
}
/// A privileged call; in this case it resets our dummy value to something new.
// Implementation of a privileged call. The `origin` parameter is ROOT because
// it's not (directly) from an extrinsic, but rather the system as a whole has decided
// to execute it. Different runtimes have different reasons for allow privileged
// calls to be executed - we don't need to care why. Because it's privileged, we can
// assume it's a one-off operation and substantial processing/storage/memory can be used
// without worrying about gameability or attack scenarios.
//
// The weight for this extrinsic we use our own weight object `WeightForSetDummy` to
// determine its weight
#[pallet::weight(WeightForSetDummy::<T>(<BalanceOf<T>>::from(100u32)))]
pub fn set_dummy(
origin: OriginFor<T>,
#[pallet::compact] new_value: T::Balance,
) -> DispatchResult {
ensure_root(origin)?;
// Print out log or debug message in the console via log::{error, warn, info, debug,
// trace}, accepting format strings similar to `println!`.
// https://paritytech.github.io/substrate/master/sp_io/logging/fn.log.html
// https://paritytech.github.io/substrate/master/frame_support/constant.LOG_TARGET.html
info!("New value is now: {:?}", new_value);
// Put the new value into storage.
<Dummy<T>>::put(new_value);
Self::deposit_event(Event::SetDummy(new_value));
// All good, no refund.
Ok(())
}
}
/// Events are a simple means of reporting specific conditions and
/// circumstances that have happened that users, Dapps and/or chain explorers would find
/// interesting and otherwise difficult to detect.
#[pallet::event]
/// This attribute generate the function `deposit_event` to deposit one of this pallet event,
/// it is optional, it is also possible to provide a custom implementation.
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// Just a normal `enum`, here's a dummy event to ensure it compiles.
/// Dummy event, just here so there's a generic type that's used.
AccumulateDummy(BalanceOf<T>),
SetDummy(BalanceOf<T>),
SetBar(T::AccountId, BalanceOf<T>),
}
// pallet::storage attributes allow for type-safe usage of the Substrate storage database,
// so you can keep things around between blocks.
//
// Any storage must be one of `StorageValue`, `StorageMap` or `StorageDoubleMap`.
// The first generic holds the prefix to use and is generated by the macro.
// The query kind is either `OptionQuery` (the default) or `ValueQuery`.
// - for `type Foo<T> = StorageValue<_, u32, OptionQuery>`:
// - `Foo::put(1); Foo::get()` returns `Some(1)`;
// - `Foo::kill(); Foo::get()` returns `None`.
// - for `type Foo<T> = StorageValue<_, u32, ValueQuery>`:
// - `Foo::put(1); Foo::get()` returns `1`;
// - `Foo::kill(); Foo::get()` returns `0` (u32::default()).
#[pallet::storage]
// The getter attribute generate a function on `Pallet` placeholder:
// `fn getter_name() -> Type` for basic value items or
// `fn getter_name(key: KeyType) -> ValueType` for map items.
#[pallet::getter(fn dummy)]
pub(super) type Dummy<T: Config> = StorageValue<_, T::Balance>;
// A map that has enumerable entries.
#[pallet::storage]
#[pallet::getter(fn bar)]
pub(super) type Bar<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance>;
// this one uses the query kind: `ValueQuery`, we'll demonstrate the usage of 'mutate' API.
#[pallet::storage]
#[pallet::getter(fn foo)]
pub(super) type Foo<T: Config> = StorageValue<_, T::Balance, ValueQuery>;
#[pallet::storage]
pub type CountedMap<T> = CountedStorageMap<_, Blake2_128Concat, u8, u16>;
// The genesis config type.
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub dummy: T::Balance,
pub bar: Vec<(T::AccountId, T::Balance)>,
pub foo: T::Balance,
}
// The default value for the genesis config type.
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { dummy: Default::default(), bar: Default::default(), foo: Default::default() }
}
}
// The build of genesis for the pallet.
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
<Dummy<T>>::put(&self.dummy);
for (a, b) in &self.bar {
<Bar<T>>::insert(a, b);
}
<Foo<T>>::put(&self.foo);
}
}
}
// The main implementation block for the pallet. Functions here fall into three broad
// categories:
// - Public interface. These are functions that are `pub` and generally fall into inspector
// functions that do not write to storage and operation functions that do.
// - Private functions. These are your usual private utilities unavailable to other pallets.
impl<T: Config> Pallet<T> {
// Add public immutables and private mutables.
#[allow(dead_code)]
fn accumulate_foo(origin: T::Origin, increase_by: T::Balance) -> DispatchResult {
let _sender = ensure_signed(origin)?;
let prev = <Foo<T>>::get();
// Because Foo has 'default', the type of 'foo' in closure is the raw type instead of an
// Option<> type.
let result = <Foo<T>>::mutate(|foo| {
*foo = foo.saturating_add(increase_by);
*foo
});
assert!(prev + increase_by == result);
Ok(())
}
}
// Similar to other FRAME pallets, your pallet can also define a signed extension and perform some
// checks and [pre/post]processing [before/after] the transaction. A signed extension can be any
// decodable type that implements `SignedExtension`. See the trait definition for the full list of
// bounds. As a convention, you can follow this approach to create an extension for your pallet:
// - If the extension does not carry any data, then use a tuple struct with just a `marker`
// (needed for the compiler to accept `T: Config`) will suffice.
// - Otherwise, create a tuple struct which contains the external data. Of course, for the entire
// struct to be decodable, each individual item also needs to be decodable.
//
// Note that a signed extension can also indicate that a particular data must be present in the
// _signing payload_ of a transaction by providing an implementation for the `additional_signed`
// method. This example will not cover this type of extension. See `CheckSpecVersion` in
// [FRAME System](https://github.com/paritytech/substrate/tree/master/frame/system#signed-extensions)
// for an example.
//
// Using the extension, you can add some hooks to the life cycle of each transaction. Note that by
// default, an extension is applied to all `Call` functions (i.e. all transactions). the `Call` enum
// variant is given to each function of `SignedExtension`. Hence, you can filter based on pallet or
// a particular call if needed.
//
// Some extra information, such as encoded length, some static dispatch info like weight and the
// sender of the transaction (if signed) are also provided.
//
// The full list of hooks that can be added to a signed extension can be found
// [here](https://crates.parity.io/sp_runtime/traits/trait.SignedExtension.html).
//
// The signed extensions are aggregated in the runtime file of a substrate chain. All extensions
// should be aggregated in a tuple and passed to the `CheckedExtrinsic` and `UncheckedExtrinsic`
// types defined in the runtime. Lookup `pub type SignedExtra = (...)` in `node/runtime` and
// `node-template` for an example of this.
/// A simple signed extension that checks for the `set_dummy` call. In that case, it increases the
/// priority and prints some log.
///
/// Additionally, it drops any transaction with an encoded length higher than 200 bytes. No
/// particular reason why, just to demonstrate the power of signed extensions.
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct WatchDummy<T: Config + Send + Sync>(PhantomData<T>);
impl<T: Config + Send + Sync> sp_std::fmt::Debug for WatchDummy<T> {
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "WatchDummy")
}
}
impl<T: Config + Send + Sync> SignedExtension for WatchDummy<T>
where
<T as frame_system::Config>::Call: IsSubType<Call<T>>,
{
const IDENTIFIER: &'static str = "WatchDummy";
type AccountId = T::AccountId;
type Call = <T as frame_system::Config>::Call;
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
Ok(())
}
fn validate(
&self,
_who: &Self::AccountId,
call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> TransactionValidity {
// if the transaction is too big, just drop it.
if len > 200 {
return InvalidTransaction::ExhaustsResources.into()
}
// check for `set_dummy`
match call.is_sub_type() {
Some(Call::set_dummy { .. }) => {
sp_runtime::print("set_dummy was received.");
let mut valid_tx = ValidTransaction::default();
valid_tx.priority = Bounded::max_value();
Ok(valid_tx)
},
_ => Ok(Default::default()),
}
}
}
+205
View File
@@ -0,0 +1,205 @@
// 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 pallet-example-basic.
use crate::*;
use frame_support::{
assert_ok, parameter_types,
traits::OnInitialize,
weights::{DispatchInfo, GetDispatchInfo},
};
use sp_core::H256;
// The testing primitives are very useful for avoiding having to work with signatures
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};
// Reexport crate as its pallet name for construct_runtime.
use crate as pallet_example_basic;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
// For testing the pallet, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Example: pallet_example_basic::{Pallet, Call, Storage, Config<T>, Event<T>},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(1024);
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Call = Call;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type DustRemoval = ();
type Event = Event;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
}
parameter_types! {
pub const MagicNumber: u64 = 1_000_000_000;
}
impl Config for Test {
type MagicNumber = MagicNumber;
type Event = Event;
type WeightInfo = ();
}
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = GenesisConfig {
// We use default for brevity, but you can configure as desired if needed.
system: Default::default(),
balances: Default::default(),
example: pallet_example_basic::GenesisConfig {
dummy: 42,
// we configure the map with (key, value) pairs.
bar: vec![(1, 2), (2, 3)],
foo: 24,
},
}
.build_storage()
.unwrap();
t.into()
}
#[test]
fn it_works_for_optional_value() {
new_test_ext().execute_with(|| {
// Check that GenesisBuilder works properly.
let val1 = 42;
let val2 = 27;
assert_eq!(Example::dummy(), Some(val1));
// Check that accumulate works when we have Some value in Dummy already.
assert_ok!(Example::accumulate_dummy(Origin::signed(1), val2));
assert_eq!(Example::dummy(), Some(val1 + val2));
// Check that accumulate works when we Dummy has None in it.
<Example as OnInitialize<u64>>::on_initialize(2);
assert_ok!(Example::accumulate_dummy(Origin::signed(1), val1));
assert_eq!(Example::dummy(), Some(val1 + val2 + val1));
});
}
#[test]
fn it_works_for_default_value() {
new_test_ext().execute_with(|| {
assert_eq!(Example::foo(), 24);
assert_ok!(Example::accumulate_foo(Origin::signed(1), 1));
assert_eq!(Example::foo(), 25);
});
}
#[test]
fn set_dummy_works() {
new_test_ext().execute_with(|| {
let test_val = 133;
assert_ok!(Example::set_dummy(Origin::root(), test_val.into()));
assert_eq!(Example::dummy(), Some(test_val));
});
}
#[test]
fn signed_ext_watch_dummy_works() {
new_test_ext().execute_with(|| {
let call = pallet_example_basic::Call::set_dummy { new_value: 10 }.into();
let info = DispatchInfo::default();
assert_eq!(
WatchDummy::<Test>(PhantomData)
.validate(&1, &call, &info, 150)
.unwrap()
.priority,
u64::MAX,
);
assert_eq!(
WatchDummy::<Test>(PhantomData).validate(&1, &call, &info, 250),
InvalidTransaction::ExhaustsResources.into(),
);
})
}
#[test]
fn counted_map_works() {
new_test_ext().execute_with(|| {
assert_eq!(CountedMap::<Test>::count(), 0);
CountedMap::<Test>::insert(3, 3);
assert_eq!(CountedMap::<Test>::count(), 1);
})
}
#[test]
fn weights_work() {
// must have a defined weight.
let default_call = pallet_example_basic::Call::<Test>::accumulate_dummy { increase_by: 10 };
let info1 = default_call.get_dispatch_info();
// aka. `let info = <Call<Test> as GetDispatchInfo>::get_dispatch_info(&default_call);`
assert!(info1.weight > 0);
// `set_dummy` is simpler than `accumulate_dummy`, and the weight
// should be less.
let custom_call = pallet_example_basic::Call::<Test>::set_dummy { new_value: 20 };
let info2 = custom_call.get_dispatch_info();
assert!(info1.weight > info2.weight);
}
@@ -0,0 +1,101 @@
// This file is part of Substrate.
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Autogenerated weights for pallet_example_basic
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
//! DATE: 2021-03-15, STEPS: `[100, ]`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
// ./target/release/substrate
// benchmark
// --chain
// dev
// --execution
// wasm
// --wasm-execution
// compiled
// --pallet
// pallet_example_basic
// --extrinsic
// *
// --steps
// 100
// --repeat
// 10
// --raw
// --output
// ./
// --template
// ./.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_example_basic.
pub trait WeightInfo {
fn set_dummy_benchmark(b: u32, ) -> Weight;
fn accumulate_dummy(b: u32, ) -> Weight;
fn sort_vector(x: u32, ) -> Weight;
}
/// Weights for pallet_example_basic using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
fn set_dummy_benchmark(b: u32, ) -> Weight {
(5_834_000 as Weight)
.saturating_add((24_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
fn accumulate_dummy(b: u32, ) -> Weight {
(51_353_000 as Weight)
.saturating_add((14_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
fn sort_vector(x: u32, ) -> Weight {
(2_569_000 as Weight)
// Standard Error: 0
.saturating_add((4_000 as Weight).saturating_mul(x as Weight))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
fn set_dummy_benchmark(b: u32, ) -> Weight {
(5_834_000 as Weight)
.saturating_add((24_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
fn accumulate_dummy(b: u32, ) -> Weight {
(51_353_000 as Weight)
.saturating_add((14_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
fn sort_vector(x: u32, ) -> Weight {
(2_569_000 as Weight)
// Standard Error: 0
.saturating_add((4_000 as Weight).saturating_mul(x as Weight))
}
}
@@ -0,0 +1,44 @@
[package]
name = "pallet-example-offchain-worker"
version = "4.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Unlicense"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME example pallet for offchain worker"
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
lite-json = { version = "0.1", default-features = false }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" }
sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" }
sp-keystore = { version = "0.10.0-dev", path = "../../../primitives/keystore", optional = true }
sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" }
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" }
[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"frame-support/std",
"frame-system/std",
"lite-json/std",
"sp-core/std",
"sp-io/std",
"sp-keystore",
"sp-runtime/std",
"sp-std/std",
"log/std",
]
try-runtime = ["frame-support/try-runtime"]
@@ -0,0 +1,29 @@
<!-- markdown-link-check-disable -->
# Offchain Worker Example Pallet
The Offchain Worker Example: A simple pallet demonstrating
concepts, APIs and structures common to most offchain workers.
Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's
documentation.
- [`pallet_example_offchain_worker::Trait`](./trait.Trait.html)
- [`Call`](./enum.Call.html)
- [`Module`](./struct.Module.html)
**This pallet serves as an example showcasing Substrate off-chain worker and is not meant to be
used in production.**
## Overview
In this example we are going to build a very simplistic, naive and definitely NOT
production-ready oracle for BTC/USD price.
Offchain Worker (OCW) will be triggered after every block, fetch the current price
and prepare either signed or unsigned transaction to feed the result back on chain.
The on-chain logic will simply aggregate the results and store last `64` values to compute
the average price.
Additional logic in OCW is put in place to prevent spamming the network with both signed
and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only
one unsigned transaction floating in the network.
License: Unlicense
@@ -0,0 +1,725 @@
// This file is part of Substrate.
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! <!-- markdown-link-check-disable -->
//! # Offchain Worker Example Pallet
//!
//! The Offchain Worker Example: A simple pallet demonstrating
//! concepts, APIs and structures common to most offchain workers.
//!
//! Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's
//! documentation.
//!
//! - [`Config`]
//! - [`Call`]
//! - [`Pallet`]
//!
//! **This pallet serves as an example showcasing Substrate off-chain worker and is not meant to
//! be used in production.**
//!
//! ## Overview
//!
//! In this example we are going to build a very simplistic, naive and definitely NOT
//! production-ready oracle for BTC/USD price.
//! Offchain Worker (OCW) will be triggered after every block, fetch the current price
//! and prepare either signed or unsigned transaction to feed the result back on chain.
//! The on-chain logic will simply aggregate the results and store last `64` values to compute
//! the average price.
//! Additional logic in OCW is put in place to prevent spamming the network with both signed
//! and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only
//! one unsigned transaction floating in the network.
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::traits::Get;
use frame_system::{
self as system,
offchain::{
AppCrypto, CreateSignedTransaction, SendSignedTransaction, SendUnsignedTransaction,
SignedPayload, Signer, SigningTypes, SubmitTransaction,
},
};
use lite_json::json::JsonValue;
use sp_core::crypto::KeyTypeId;
use sp_runtime::{
offchain::{
http,
storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
Duration,
},
traits::Zero,
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
RuntimeDebug,
};
use sp_std::vec::Vec;
#[cfg(test)]
mod tests;
/// Defines application identifier for crypto keys of this module.
///
/// Every module that deals with signatures needs to declare its unique identifier for
/// its crypto keys.
/// When offchain worker is signing transactions it's going to request keys of type
/// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction.
/// The keys can be inserted manually via RPC (see `author_insertKey`).
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!");
/// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers.
/// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment
/// the types with this pallet-specific identifier.
pub mod crypto {
use super::KEY_TYPE;
use sp_core::sr25519::Signature as Sr25519Signature;
use sp_runtime::{
app_crypto::{app_crypto, sr25519},
traits::Verify,
MultiSignature, MultiSigner,
};
app_crypto!(sr25519, KEY_TYPE);
pub struct TestAuthId;
impl frame_system::offchain::AppCrypto<MultiSigner, MultiSignature> for TestAuthId {
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
}
// implemented for mock runtime in test
impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature>
for TestAuthId
{
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
}
}
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// This pallet's configuration trait
#[pallet::config]
pub trait Config: CreateSignedTransaction<Call<Self>> + frame_system::Config {
/// The identifier type for an offchain worker.
type AuthorityId: AppCrypto<Self::Public, Self::Signature>;
/// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// The overarching dispatch call type.
type Call: From<Call<Self>>;
// Configuration parameters
/// A grace period after we send transaction.
///
/// To avoid sending too many transactions, we only attempt to send one
/// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate
/// sending between distinct runs of this offchain worker.
#[pallet::constant]
type GracePeriod: Get<Self::BlockNumber>;
/// Number of blocks of cooldown after unsigned transaction is included.
///
/// This ensures that we only accept unsigned transactions once, every `UnsignedInterval`
/// blocks.
#[pallet::constant]
type UnsignedInterval: Get<Self::BlockNumber>;
/// A configuration for base priority of unsigned transactions.
///
/// This is exposed so that it can be tuned for particular runtime, when
/// multiple pallets send unsigned transactions.
#[pallet::constant]
type UnsignedPriority: Get<TransactionPriority>;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
/// Offchain Worker entry point.
///
/// By implementing `fn offchain_worker` you declare a new offchain worker.
/// This function will be called when the node is fully synced and a new best block is
/// succesfuly imported.
/// Note that it's not guaranteed for offchain workers to run on EVERY block, there might
/// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs),
/// so the code should be able to handle that.
/// You can use `Local Storage` API to coordinate runs of the worker.
fn offchain_worker(block_number: T::BlockNumber) {
// Note that having logs compiled to WASM may cause the size of the blob to increase
// significantly. You can use `RuntimeDebug` custom derive to hide details of the types
// in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable
// all logging and thus, remove any logging from the WASM.
log::info!("Hello World from offchain workers!");
// Since off-chain workers are just part of the runtime code, they have direct access
// to the storage and other included pallets.
//
// We can easily import `frame_system` and retrieve a block hash of the parent block.
let parent_hash = <system::Pallet<T>>::block_hash(block_number - 1u32.into());
log::debug!("Current block: {:?} (parent hash: {:?})", block_number, parent_hash);
// It's a good practice to keep `fn offchain_worker()` function minimal, and move most
// of the code to separate `impl` block.
// Here we call a helper function to calculate current average price.
// This function reads storage entries of the current state.
let average: Option<u32> = Self::average_price();
log::debug!("Current price: {:?}", average);
// For this example we are going to send both signed and unsigned transactions
// depending on the block number.
// Usually it's enough to choose one or the other.
let should_send = Self::choose_transaction_type(block_number);
let res = match should_send {
TransactionType::Signed => Self::fetch_price_and_send_signed(),
TransactionType::UnsignedForAny =>
Self::fetch_price_and_send_unsigned_for_any_account(block_number),
TransactionType::UnsignedForAll =>
Self::fetch_price_and_send_unsigned_for_all_accounts(block_number),
TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number),
TransactionType::None => Ok(()),
};
if let Err(e) = res {
log::error!("Error: {}", e);
}
}
}
/// A public part of the pallet.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Submit new price to the list.
///
/// This method is a public function of the module and can be called from within
/// a transaction. It appends given `price` to current list of prices.
/// In our example the `offchain worker` will create, sign & submit a transaction that
/// calls this function passing the price.
///
/// The transaction needs to be signed (see `ensure_signed`) check, so that the caller
/// pays a fee to execute it.
/// This makes sure that it's not easy (or rather cheap) to attack the chain by submitting
/// excesive transactions, but note that it doesn't ensure the price oracle is actually
/// working and receives (and provides) meaningful data.
/// This example is not focused on correctness of the oracle itself, but rather its
/// purpose is to showcase offchain worker capabilities.
#[pallet::weight(0)]
pub fn submit_price(origin: OriginFor<T>, price: u32) -> DispatchResultWithPostInfo {
// Retrieve sender of the transaction.
let who = ensure_signed(origin)?;
// Add the price to the on-chain list.
Self::add_price(who, price);
Ok(().into())
}
/// Submit new price to the list via unsigned transaction.
///
/// Works exactly like the `submit_price` function, but since we allow sending the
/// transaction without a signature, and hence without paying any fees,
/// we need a way to make sure that only some transactions are accepted.
/// This function can be called only once every `T::UnsignedInterval` blocks.
/// Transactions that call that function are de-duplicated on the pool level
/// via `validate_unsigned` implementation and also are rendered invalid if
/// the function has already been called in current "session".
///
/// It's important to specify `weight` for unsigned calls as well, because even though
/// they don't charge fees, we still don't want a single block to contain unlimited
/// number of such transactions.
///
/// This example is not focused on correctness of the oracle itself, but rather its
/// purpose is to showcase offchain worker capabilities.
#[pallet::weight(0)]
pub fn submit_price_unsigned(
origin: OriginFor<T>,
_block_number: T::BlockNumber,
price: u32,
) -> DispatchResultWithPostInfo {
// This ensures that the function can only be called via unsigned transaction.
ensure_none(origin)?;
// Add the price to the on-chain list, but mark it as coming from an empty address.
Self::add_price(Default::default(), price);
// now increment the block number at which we expect next unsigned transaction.
let current_block = <system::Pallet<T>>::block_number();
<NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get());
Ok(().into())
}
#[pallet::weight(0)]
pub fn submit_price_unsigned_with_signed_payload(
origin: OriginFor<T>,
price_payload: PricePayload<T::Public, T::BlockNumber>,
_signature: T::Signature,
) -> DispatchResultWithPostInfo {
// This ensures that the function can only be called via unsigned transaction.
ensure_none(origin)?;
// Add the price to the on-chain list, but mark it as coming from an empty address.
Self::add_price(Default::default(), price_payload.price);
// now increment the block number at which we expect next unsigned transaction.
let current_block = <system::Pallet<T>>::block_number();
<NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get());
Ok(().into())
}
}
/// Events for the pallet.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event generated when new price is accepted to contribute to the average.
/// \[price, who\]
NewPrice(u32, T::AccountId),
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
/// Validate unsigned call to this module.
///
/// By default unsigned transactions are disallowed, but implementing the validator
/// here we make sure that some particular calls (the ones produced by offchain worker)
/// are being whitelisted and marked as valid.
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
// Firstly let's check that we call the right function.
if let Call::submit_price_unsigned_with_signed_payload {
price_payload: ref payload,
ref signature,
} = call
{
let signature_valid =
SignedPayload::<T>::verify::<T::AuthorityId>(payload, signature.clone());
if !signature_valid {
return InvalidTransaction::BadProof.into()
}
Self::validate_transaction_parameters(&payload.block_number, &payload.price)
} else if let Call::submit_price_unsigned { block_number, price: new_price } = call {
Self::validate_transaction_parameters(block_number, new_price)
} else {
InvalidTransaction::Call.into()
}
}
}
/// A vector of recently submitted prices.
///
/// This is used to calculate average price, should have bounded size.
#[pallet::storage]
#[pallet::getter(fn prices)]
pub(super) type Prices<T: Config> = StorageValue<_, Vec<u32>, ValueQuery>;
/// Defines the block when next unsigned transaction will be accepted.
///
/// To prevent spam of unsigned (and unpayed!) transactions on the network,
/// we only allow one transaction every `T::UnsignedInterval` blocks.
/// This storage entry defines when new transaction is going to be accepted.
#[pallet::storage]
#[pallet::getter(fn next_unsigned_at)]
pub(super) type NextUnsignedAt<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
}
/// Payload used by this example crate to hold price
/// data required to submit a transaction.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)]
pub struct PricePayload<Public, BlockNumber> {
block_number: BlockNumber,
price: u32,
public: Public,
}
impl<T: SigningTypes> SignedPayload<T> for PricePayload<T::Public, T::BlockNumber> {
fn public(&self) -> T::Public {
self.public.clone()
}
}
enum TransactionType {
Signed,
UnsignedForAny,
UnsignedForAll,
Raw,
None,
}
impl<T: Config> Pallet<T> {
/// Chooses which transaction type to send.
///
/// This function serves mostly to showcase `StorageValue` helper
/// and local storage usage.
///
/// Returns a type of transaction that should be produced in current run.
fn choose_transaction_type(block_number: T::BlockNumber) -> TransactionType {
/// A friendlier name for the error that is going to be returned in case we are in the grace
/// period.
const RECENTLY_SENT: () = ();
// Start off by creating a reference to Local Storage value.
// Since the local storage is common for all offchain workers, it's a good practice
// to prepend your entry with the module name.
let val = StorageValueRef::persistent(b"example_ocw::last_send");
// The Local Storage is persisted and shared between runs of the offchain workers,
// and offchain workers may run concurrently. We can use the `mutate` function, to
// write a storage entry in an atomic fashion. Under the hood it uses `compare_and_set`
// low-level method of local storage API, which means that only one worker
// will be able to "acquire a lock" and send a transaction if multiple workers
// happen to be executed concurrently.
let res = val.mutate(|last_send: Result<Option<T::BlockNumber>, StorageRetrievalError>| {
match last_send {
// If we already have a value in storage and the block number is recent enough
// we avoid sending another transaction at this time.
Ok(Some(block)) if block_number < block + T::GracePeriod::get() =>
Err(RECENTLY_SENT),
// In every other case we attempt to acquire the lock and send a transaction.
_ => Ok(block_number),
}
});
// The result of `mutate` call will give us a nested `Result` type.
// The first one matches the return of the closure passed to `mutate`, i.e.
// if we return `Err` from the closure, we get an `Err` here.
// In case we return `Ok`, here we will have another (inner) `Result` that indicates
// if the value has been set to the storage correctly - i.e. if it wasn't
// written to in the meantime.
match res {
// The value has been set correctly, which means we can safely send a transaction now.
Ok(block_number) => {
// Depending if the block is even or odd we will send a `Signed` or `Unsigned`
// transaction.
// Note that this logic doesn't really guarantee that the transactions will be sent
// in an alternating fashion (i.e. fairly distributed). Depending on the execution
// order and lock acquisition, we may end up for instance sending two `Signed`
// transactions in a row. If a strict order is desired, it's better to use
// the storage entry for that. (for instance store both block number and a flag
// indicating the type of next transaction to send).
let transaction_type = block_number % 3u32.into();
if transaction_type == Zero::zero() {
TransactionType::Signed
} else if transaction_type == T::BlockNumber::from(1u32) {
TransactionType::UnsignedForAny
} else if transaction_type == T::BlockNumber::from(2u32) {
TransactionType::UnsignedForAll
} else {
TransactionType::Raw
}
},
// We are in the grace period, we should not send a transaction this time.
Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None,
// We wanted to send a transaction, but failed to write the block number (acquire a
// lock). This indicates that another offchain worker that was running concurrently
// most likely executed the same logic and succeeded at writing to storage.
// Thus we don't really want to send the transaction, knowing that the other run
// already did.
Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None,
}
}
/// A helper function to fetch the price and send signed transaction.
fn fetch_price_and_send_signed() -> Result<(), &'static str> {
let signer = Signer::<T, T::AuthorityId>::all_accounts();
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.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// Using `send_signed_transaction` associated type we create and submit a transaction
// representing the call, we've just created.
// Submit signed will return a vector of results for all accounts that were found in the
// local keystore with expected `KEY_TYPE`.
let results = signer.send_signed_transaction(|_account| {
// Received price is wrapped into a call to `submit_price` public function of this
// pallet. This means that the transaction, when executed, will simply call that
// function passing `price` as an argument.
Call::submit_price { price }
});
for (acc, res) in &results {
match res {
Ok(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price),
Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e),
}
}
Ok(())
}
/// A helper function to fetch the price and send a raw unsigned transaction.
fn fetch_price_and_send_raw_unsigned(block_number: T::BlockNumber) -> Result<(), &'static str> {
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
// anyway.
let next_unsigned_at = <NextUnsignedAt<T>>::get();
if next_unsigned_at > block_number {
return Err("Too early to send unsigned transaction")
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// Received price is wrapped into a call to `submit_price_unsigned` public function of this
// pallet. This means that the transaction, when executed, will simply call that function
// passing `price` as an argument.
let call = Call::submit_price_unsigned { block_number, price };
// Now let's create a transaction out of this call and submit it to the pool.
// Here we showcase two ways to send an unsigned transaction / unsigned payload (raw)
//
// By default unsigned transactions are disallowed, so we need to whitelist this case
// by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefuly
// implement unsigned validation logic, as any mistakes can lead to opening DoS or spam
// attack vectors. See validation logic docs for more details.
//
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
.map_err(|()| "Unable to submit unsigned transaction.")?;
Ok(())
}
/// A helper function to fetch the price, sign payload and send an unsigned transaction
fn fetch_price_and_send_unsigned_for_any_account(
block_number: T::BlockNumber,
) -> Result<(), &'static str> {
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
// anyway.
let next_unsigned_at = <NextUnsignedAt<T>>::get();
if next_unsigned_at > block_number {
return Err("Too early to send unsigned transaction")
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// -- Sign using any account
let (_, result) = Signer::<T, T::AuthorityId>::any_account()
.send_unsigned_transaction(
|account| PricePayload { price, block_number, public: account.public.clone() },
|payload, signature| Call::submit_price_unsigned_with_signed_payload {
price_payload: payload,
signature,
},
)
.ok_or("No local accounts accounts available.")?;
result.map_err(|()| "Unable to submit transaction")?;
Ok(())
}
/// A helper function to fetch the price, sign payload and send an unsigned transaction
fn fetch_price_and_send_unsigned_for_all_accounts(
block_number: T::BlockNumber,
) -> Result<(), &'static str> {
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
// anyway.
let next_unsigned_at = <NextUnsignedAt<T>>::get();
if next_unsigned_at > block_number {
return Err("Too early to send unsigned transaction")
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// -- Sign using all accounts
let transaction_results = Signer::<T, T::AuthorityId>::all_accounts()
.send_unsigned_transaction(
|account| PricePayload { price, block_number, public: account.public.clone() },
|payload, signature| Call::submit_price_unsigned_with_signed_payload {
price_payload: payload,
signature,
},
);
for (_account_id, result) in transaction_results.into_iter() {
if result.is_err() {
return Err("Unable to submit transaction")
}
}
Ok(())
}
/// Fetch current price and return the result in cents.
fn fetch_price() -> Result<u32, http::Error> {
// We want to keep the offchain worker execution time reasonable, so we set a hard-coded
// deadline to 2s to complete the external call.
// You can also wait idefinitely for the response, however you may still get a timeout
// coming from the host machine.
let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000));
// Initiate an external HTTP GET request.
// This is using high-level wrappers from `sp_runtime`, for the low-level calls that
// you can find in `sp_io`. The API is trying to be similar to `reqwest`, but
// since we are running in a custom WASM execution environment we can't simply
// import the library here.
let request =
http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD");
// We set the deadline for sending of the request, note that awaiting response can
// have a separate deadline. Next we send the request, before that it's also possible
// to alter request headers or stream body content in case of non-GET requests.
let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?;
// The request is already being processed by the host, we are free to do anything
// else in the worker (we can send multiple concurrent requests too).
// At some point however we probably want to check the response though,
// so we can block current thread and wait for it to finish.
// Note that since the request is being driven by the host, we don't have to wait
// for the request to have it complete, we will just not read the response.
let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??;
// Let's check the status code before we proceed to reading the response.
if response.code != 200 {
log::warn!("Unexpected status code: {}", response.code);
return Err(http::Error::Unknown)
}
// Next we want to fully read the response body and collect it to a vector of bytes.
// Note that the return object allows you to read the body in chunks as well
// with a way to control the deadline.
let body = response.body().collect::<Vec<u8>>();
// Create a str slice from the body.
let body_str = sp_std::str::from_utf8(&body).map_err(|_| {
log::warn!("No UTF8 body");
http::Error::Unknown
})?;
let price = match Self::parse_price(body_str) {
Some(price) => Ok(price),
None => {
log::warn!("Unable to extract price from the response: {:?}", body_str);
Err(http::Error::Unknown)
},
}?;
log::warn!("Got price: {} cents", price);
Ok(price)
}
/// Parse the price from the given JSON string using `lite-json`.
///
/// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful.
fn parse_price(price_str: &str) -> Option<u32> {
let val = lite_json::parse_json(price_str);
let price = match val.ok()? {
JsonValue::Object(obj) => {
let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?;
match v {
JsonValue::Number(number) => number,
_ => return None,
}
},
_ => return None,
};
let exp = price.fraction_length.checked_sub(2).unwrap_or(0);
Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32)
}
/// Add new price to the list.
fn add_price(who: T::AccountId, price: u32) {
log::info!("Adding to the average: {}", price);
<Prices<T>>::mutate(|prices| {
const MAX_LEN: usize = 64;
if prices.len() < MAX_LEN {
prices.push(price);
} else {
prices[price as usize % MAX_LEN] = price;
}
});
let average = Self::average_price()
.expect("The average is not empty, because it was just mutated; qed");
log::info!("Current average price is: {}", average);
// here we are raising the NewPrice event
Self::deposit_event(Event::NewPrice(price, who));
}
/// Calculate current average price.
fn average_price() -> Option<u32> {
let prices = <Prices<T>>::get();
if prices.is_empty() {
None
} else {
Some(prices.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / prices.len() as u32)
}
}
fn validate_transaction_parameters(
block_number: &T::BlockNumber,
new_price: &u32,
) -> TransactionValidity {
// Now let's check if the transaction has any chance to succeed.
let next_unsigned_at = <NextUnsignedAt<T>>::get();
if &next_unsigned_at > block_number {
return InvalidTransaction::Stale.into()
}
// Let's make sure to reject transactions from the future.
let current_block = <system::Pallet<T>>::block_number();
if &current_block < block_number {
return InvalidTransaction::Future.into()
}
// We prioritize transactions that are more far away from current average.
//
// Note this doesn't make much sense when building an actual oracle, but this example
// is here mostly to show off offchain workers capabilities, not about building an
// oracle.
let avg_price = Self::average_price()
.map(|price| if &price > new_price { price - new_price } else { new_price - price })
.unwrap_or(0);
ValidTransaction::with_tag_prefix("ExampleOffchainWorker")
// We set base priority to 2**20 and hope it's included before any other
// transactions in the pool. Next we tweak the priority depending on how much
// it differs from the current average. (the more it differs the more priority it
// has).
.priority(T::UnsignedPriority::get().saturating_add(avg_price as _))
// This transaction does not require anything else to go before into the pool.
// In theory we could require `previous_unsigned_at` transaction to go first,
// but it's not necessary in our case.
//.and_requires()
// We set the `provides` tag to be the same as `next_unsigned_at`. This makes
// sure only one transaction produced after `next_unsigned_at` will ever
// get to the transaction pool and will end up in the block.
// We can still have multiple transactions compete for the same "spot",
// and the one with higher priority will replace other one in the pool.
.and_provides(next_unsigned_at)
// The transaction is only valid for next 5 blocks. After that it's
// going to be revalidated by the pool.
.longevity(5)
// It's fine to propagate that transaction to other peers, which means it can be
// created even by nodes that don't produce blocks.
// Note that sometimes it's better to keep it for yourself (if you are the block
// producer), since for instance in some schemes others may copy your solution and
// claim a reward.
.propagate(true)
.build()
}
}
@@ -0,0 +1,407 @@
// This file is part of Substrate.
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate as example_offchain_worker;
use crate::*;
use codec::Decode;
use frame_support::{assert_ok, parameter_types};
use sp_core::{
offchain::{testing, OffchainWorkerExt, TransactionPoolExt},
sr25519::Signature,
H256,
};
use std::sync::Arc;
use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore};
use sp_runtime::{
testing::{Header, TestXt},
traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify},
RuntimeAppPublic,
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
// For testing the module, we construct a mock runtime.
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Example: example_offchain_worker::{Pallet, Call, Storage, Event<T>, ValidateUnsigned},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(1024);
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = sp_core::sr25519::Public;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
}
type Extrinsic = TestXt<Call, ()>;
type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
impl frame_system::offchain::SigningTypes for Test {
type Public = <Signature as Verify>::Signer;
type Signature = Signature;
}
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Test
where
Call: From<LocalCall>,
{
type OverarchingCall = Call;
type Extrinsic = Extrinsic;
}
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Test
where
Call: From<LocalCall>,
{
fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(
call: Call,
_public: <Signature as Verify>::Signer,
_account: AccountId,
nonce: u64,
) -> Option<(Call, <Extrinsic as ExtrinsicT>::SignaturePayload)> {
Some((call, (nonce, ())))
}
}
parameter_types! {
pub const GracePeriod: u64 = 5;
pub const UnsignedInterval: u64 = 128;
pub const UnsignedPriority: u64 = 1 << 20;
}
impl Config for Test {
type Event = Event;
type AuthorityId = crypto::TestAuthId;
type Call = Call;
type GracePeriod = GracePeriod;
type UnsignedInterval = UnsignedInterval;
type UnsignedPriority = UnsignedPriority;
}
#[test]
fn it_aggregates_the_price() {
sp_io::TestExternalities::default().execute_with(|| {
assert_eq!(Example::average_price(), None);
assert_ok!(Example::submit_price(Origin::signed(Default::default()), 27));
assert_eq!(Example::average_price(), Some(27));
assert_ok!(Example::submit_price(Origin::signed(Default::default()), 43));
assert_eq!(Example::average_price(), Some(35));
});
}
#[test]
fn should_make_http_call_and_parse_result() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
price_oracle_response(&mut state.write());
t.execute_with(|| {
// when
let price = Example::fetch_price().unwrap();
// then
assert_eq!(price, 15523);
});
}
#[test]
fn knows_how_to_mock_several_http_calls() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
{
let mut state = state.write();
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 1}"#.to_vec()),
sent: true,
..Default::default()
});
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 2}"#.to_vec()),
sent: true,
..Default::default()
});
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 3}"#.to_vec()),
sent: true,
..Default::default()
});
}
t.execute_with(|| {
let price1 = Example::fetch_price().unwrap();
let price2 = Example::fetch_price().unwrap();
let price3 = Example::fetch_price().unwrap();
assert_eq!(price1, 100);
assert_eq!(price2, 200);
assert_eq!(price3, 300);
})
}
#[test]
fn should_submit_signed_transaction_on_chain() {
const PHRASE: &str =
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = KeyStore::new();
SyncCryptoStore::sr25519_generate_new(
&keystore,
crate::crypto::Public::ID,
Some(&format!("{}/hunter1", PHRASE)),
)
.unwrap();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt(Arc::new(keystore)));
price_oracle_response(&mut offchain_state.write());
t.execute_with(|| {
// when
Example::fetch_price_and_send_signed().unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert_eq!(tx.signature.unwrap().0, 0);
assert_eq!(tx.call, Call::Example(crate::Call::submit_price { price: 15523 }));
});
}
#[test]
fn should_submit_unsigned_transaction_on_chain_for_any_account() {
const PHRASE: &str =
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = KeyStore::new();
SyncCryptoStore::sr25519_generate_new(
&keystore,
crate::crypto::Public::ID,
Some(&format!("{}/hunter1", PHRASE)),
)
.unwrap();
let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
.get(0)
.unwrap()
.clone();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt(Arc::new(keystore)));
price_oracle_response(&mut offchain_state.write());
let price_payload = PricePayload {
block_number: 1,
price: 15523,
public: <Test as SigningTypes>::Public::from(public_key),
};
// let signature = price_payload.sign::<crypto::TestAuthId>().unwrap();
t.execute_with(|| {
// when
Example::fetch_price_and_send_unsigned_for_any_account(1).unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert_eq!(tx.signature, None);
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload {
price_payload: body,
signature,
}) = tx.call
{
assert_eq!(body, price_payload);
let signature_valid =
<PricePayload<
<Test as SigningTypes>::Public,
<Test as frame_system::Config>::BlockNumber,
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature);
assert!(signature_valid);
}
});
}
#[test]
fn should_submit_unsigned_transaction_on_chain_for_all_accounts() {
const PHRASE: &str =
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = KeyStore::new();
SyncCryptoStore::sr25519_generate_new(
&keystore,
crate::crypto::Public::ID,
Some(&format!("{}/hunter1", PHRASE)),
)
.unwrap();
let public_key = SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID)
.get(0)
.unwrap()
.clone();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt(Arc::new(keystore)));
price_oracle_response(&mut offchain_state.write());
let price_payload = PricePayload {
block_number: 1,
price: 15523,
public: <Test as SigningTypes>::Public::from(public_key),
};
// let signature = price_payload.sign::<crypto::TestAuthId>().unwrap();
t.execute_with(|| {
// when
Example::fetch_price_and_send_unsigned_for_all_accounts(1).unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert_eq!(tx.signature, None);
if let Call::Example(crate::Call::submit_price_unsigned_with_signed_payload {
price_payload: body,
signature,
}) = tx.call
{
assert_eq!(body, price_payload);
let signature_valid =
<PricePayload<
<Test as SigningTypes>::Public,
<Test as frame_system::Config>::BlockNumber,
> as SignedPayload<Test>>::verify::<crypto::TestAuthId>(&price_payload, signature);
assert!(signature_valid);
}
});
}
#[test]
fn should_submit_raw_unsigned_transaction_on_chain() {
let (offchain, offchain_state) = testing::TestOffchainExt::new();
let (pool, pool_state) = testing::TestTransactionPoolExt::new();
let keystore = KeyStore::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.register_extension(TransactionPoolExt::new(pool));
t.register_extension(KeystoreExt(Arc::new(keystore)));
price_oracle_response(&mut offchain_state.write());
t.execute_with(|| {
// when
Example::fetch_price_and_send_raw_unsigned(1).unwrap();
// then
let tx = pool_state.write().transactions.pop().unwrap();
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert_eq!(tx.signature, None);
assert_eq!(
tx.call,
Call::Example(crate::Call::submit_price_unsigned { block_number: 1, price: 15523 })
);
});
}
fn price_oracle_response(state: &mut testing::OffchainState) {
state.expect_request(testing::PendingRequest {
method: "GET".into(),
uri: "https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD".into(),
response: Some(br#"{"USD": 155.23}"#.to_vec()),
sent: true,
..Default::default()
});
}
#[test]
fn parse_price_works() {
let test_data = vec![
("{\"USD\":6536.92}", Some(653692)),
("{\"USD\":65.92}", Some(6592)),
("{\"USD\":6536.924565}", Some(653692)),
("{\"USD\":6536}", Some(653600)),
("{\"USD2\":6536}", None),
("{\"USD\":\"6432\"}", None),
];
for (json, expected) in test_data {
assert_eq!(expected, Example::parse_price(json));
}
}
@@ -0,0 +1,39 @@
[package]
name = "pallet-example-parallel"
version = "3.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Unlicense"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME example pallet using runtime worker threads"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" }
sp-core = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/core" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/io" }
sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/runtime" }
sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" }
sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/tasks" }
[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"frame-support/std",
"frame-system/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"sp-tasks/std",
]
try-runtime = ["frame-support/try-runtime"]
@@ -0,0 +1,7 @@
<!-- markdown-link-check-disable -->
# Parallel Tasks Example Pallet
This example pallet demonstrates parallelizing validation of the enlisted participants (see
`enlist_participants` dispatch).
**This pallet serves as an example and is not meant to be used in production.**
@@ -0,0 +1,150 @@
// This file is part of Substrate.
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! # Parallel Tasks Example Pallet
//!
//! This example pallet demonstrates parallelizing validation of the enlisted participants
//! (see `enlist_participants` dispatch).
//!
//! **This pallet serves as an example and is not meant to be used in production.**
#![cfg_attr(not(feature = "std"), no_std)]
use sp_runtime::RuntimeDebug;
use codec::{Decode, Encode};
use sp_std::vec::Vec;
#[cfg(test)]
mod tests;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching dispatch call type.
type Call: From<Call<Self>>;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
/// A public part of the pallet.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Get the new event running.
#[pallet::weight(0)]
pub fn run_event(origin: OriginFor<T>, id: Vec<u8>) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?;
<Participants<T>>::kill();
<CurrentEventId<T>>::mutate(move |event_id| *event_id = id);
Ok(().into())
}
/// Submit list of participants to the current event.
///
/// The example utilizes parallel execution by checking half of the
/// signatures in spawned task.
#[pallet::weight(0)]
pub fn enlist_participants(
origin: OriginFor<T>,
participants: Vec<EnlistedParticipant>,
) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?;
if validate_participants_parallel(&<CurrentEventId<T>>::get(), &participants[..]) {
for participant in participants {
<Participants<T>>::append(participant.account);
}
}
Ok(().into())
}
}
/// A vector of current participants
///
/// To enlist someone to participate, signed payload should be
/// sent to `enlist`.
#[pallet::storage]
#[pallet::getter(fn participants)]
pub(super) type Participants<T: Config> = StorageValue<_, Vec<Vec<u8>>, ValueQuery>;
/// Current event id to enlist participants to.
#[pallet::storage]
#[pallet::getter(fn get_current_event_id)]
pub(super) type CurrentEventId<T: Config> = StorageValue<_, Vec<u8>, ValueQuery>;
}
/// Request to enlist participant.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)]
pub struct EnlistedParticipant {
pub account: Vec<u8>,
pub signature: Vec<u8>,
}
impl EnlistedParticipant {
fn verify(&self, event_id: &[u8]) -> bool {
use sp_core::Public;
use sp_runtime::traits::Verify;
match sp_core::sr25519::Signature::try_from(&self.signature[..]) {
Ok(signature) => {
let public = sp_core::sr25519::Public::from_slice(self.account.as_ref());
signature.verify(event_id, &public)
},
_ => false,
}
}
}
fn validate_participants_parallel(event_id: &[u8], participants: &[EnlistedParticipant]) -> bool {
fn spawn_verify(data: Vec<u8>) -> Vec<u8> {
let stream = &mut &data[..];
let event_id = Vec::<u8>::decode(stream).expect("Failed to decode");
let participants = Vec::<EnlistedParticipant>::decode(stream).expect("Failed to decode");
for participant in participants {
if !participant.verify(&event_id) {
return false.encode()
}
}
true.encode()
}
let mut async_payload = Vec::new();
event_id.encode_to(&mut async_payload);
participants[..participants.len() / 2].encode_to(&mut async_payload);
let handle = sp_tasks::spawn(spawn_verify, async_payload);
let mut result = true;
for participant in &participants[participants.len() / 2 + 1..] {
if !participant.verify(event_id) {
result = false;
break
}
}
bool::decode(&mut &handle.join()[..]).expect("Failed to decode result") && result
}
@@ -0,0 +1,149 @@
// This file is part of Substrate.
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{self as pallet_example_parallel, *};
use frame_support::parameter_types;
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
Perbill,
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Example: pallet_example_parallel::{Pallet, Call, Storage},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type Origin = Origin;
type Call = Call;
type PalletInfo = PalletInfo;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = sp_core::sr25519::Public;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type DbWeight = ();
type BlockWeights = ();
type BlockLength = ();
type Version = ();
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
}
parameter_types! {
pub const GracePeriod: u64 = 5;
pub const UnsignedInterval: u64 = 128;
pub const UnsignedPriority: u64 = 1 << 20;
}
impl Config for Test {
type Call = Call;
}
#[test]
fn it_can_enlist() {
use sp_core::Pair;
sp_io::TestExternalities::default().execute_with(|| {
let (pair1, _) = sp_core::sr25519::Pair::generate();
let (pair2, _) = sp_core::sr25519::Pair::generate();
let event_name = b"test";
Example::run_event(Origin::signed(Default::default()), event_name.to_vec())
.expect("Failed to enlist");
let participants = vec![
EnlistedParticipant {
account: pair1.public().to_vec(),
signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(),
},
EnlistedParticipant {
account: pair2.public().to_vec(),
signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(),
},
];
Example::enlist_participants(Origin::signed(Default::default()), participants)
.expect("Failed to enlist");
assert_eq!(Example::participants().len(), 2);
});
}
#[test]
fn one_wrong_will_not_enlist_anyone() {
use sp_core::Pair;
sp_io::TestExternalities::default().execute_with(|| {
let (pair1, _) = sp_core::sr25519::Pair::generate();
let (pair2, _) = sp_core::sr25519::Pair::generate();
let (pair3, _) = sp_core::sr25519::Pair::generate();
let event_name = b"test";
Example::run_event(Origin::signed(Default::default()), event_name.to_vec())
.expect("Failed to enlist");
let participants = vec![
EnlistedParticipant {
account: pair1.public().to_vec(),
signature: AsRef::<[u8]>::as_ref(&pair1.sign(event_name)).to_vec(),
},
EnlistedParticipant {
account: pair2.public().to_vec(),
signature: AsRef::<[u8]>::as_ref(&pair2.sign(event_name)).to_vec(),
},
// signing wrong event
EnlistedParticipant {
account: pair3.public().to_vec(),
signature: AsRef::<[u8]>::as_ref(&pair3.sign(&[])).to_vec(),
},
];
Example::enlist_participants(Origin::signed(Default::default()), participants)
.expect("Failed to enlist");
assert_eq!(Example::participants().len(), 0);
});
}