Skip to content

Commit

Permalink
Transaction Weights (#62)
Browse files Browse the repository at this point in the history
* Brainstorm compiles.

* Module fleshed out completely.

* Runtime demonstrating WeightToFee

* One more weight example.

* line width to 100.

* Put some text in recipes. Will be nicer after #43

* Complete links to devhub.

* Revert accidental change to weight module which broke compile.

* Update kitchen/modules/weights/src/lib.rs

Co-Authored-By: Amar Singh <asinghchrony@protonmail.com>

* Update src/design/econsecurity.md

Co-Authored-By: Amar Singh <asinghchrony@protonmail.com>

* Correct example WeightToFee = ConvertInto.

* Update src/design/econsecurity.md

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Add Kian's suggested warning.

* cleanup formatting characters

* Add Amar's suggested warning.
  • Loading branch information
JoshOrndorff authored Nov 2, 2019
1 parent eec3711 commit 6027681
Show file tree
Hide file tree
Showing 13 changed files with 1,023 additions and 22 deletions.
50 changes: 50 additions & 0 deletions kitchen/modules/weights/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[package]
name = "weights"
version = "0.1.0"
authors = ["Anonymous"]
edition = "2018"

[features]
default = ['std']
std = [
'parity-scale-codec/std',
'support/std',
'system/std',
'balances/std',
'runtime-primitives/std',
]

[dependencies.parity-scale-codec]
default-features = false
features = ['derive']
version = '1.0.6'

[dependencies.support]
default_features = false
git = 'https://github.com/paritytech/substrate.git'
package = 'srml-support'
branch = 'master'

[dependencies.system]
default_features = false
git = 'https://github.com/paritytech/substrate.git'
package = 'srml-system'
branch = 'master'

[dependencies.balances]
default_features = false
git = 'https://github.com/paritytech/substrate.git'
package = 'srml-balances'
branch = 'master'

[dependencies.runtime-primitives]
default_features = false
git = 'https://github.com/paritytech/substrate.git'
package = 'sr-primitives'
branch = 'master'

[dev-dependencies.primitives]
default_features = false
git = 'https://github.com/paritytech/substrate.git'
package = 'substrate-primitives'
branch = 'master'
181 changes: 181 additions & 0 deletions kitchen/modules/weights/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#![cfg_attr(not(feature = "std"), no_std)]

// Transaction Weight Examples
// https://crates.parity.io/sr_primitives/weights/index.html
use support::{ensure, decl_module, decl_storage, dispatch::{Result, WeighData}};
use runtime_primitives::weights::{ DispatchClass, Weight, ClassifyDispatch, SimpleDispatchInfo };

pub trait Trait: system::Trait {}

decl_storage! {
trait Store for Module<T: Trait> as SimpleMap {
StoredValue get(stored_value): u32;
}
}

// A "scale" to weigh transactions. This scale can be used with any transactions that take a
// single argument of type u32. The ultimate weight of the transaction is the / product of the
// transaction parameter and the field of this struct.
pub struct Linear(u32);

// The actual weight calculation happens in the
// impl WeighData block
impl WeighData<(&u32,)> for Linear {
fn weigh_data(&self, (x,): (&u32,)) -> Weight {

// Use saturation so that an extremely large parameter value
// Does not cause overflow.
x.saturating_mul(self.0)
}
}

// Any struct that is used to weigh data must also implement ClassifyDispatchInfo. Here we classify
// the transaction as Normal (as opposed to operational.)
impl<T> ClassifyDispatch<T> for Linear {
fn classify_dispatch(&self, _: T) -> DispatchClass {
// Classify all calls as Normal (which is the default)
Default::default()
}
}

// Another scale to weight transactions. This one is more complex. / It computes weight according
// to the formula a*x^2 + b*y + c where / a, b, and c are fields in the struct, and x and y are
// transaction / parameters.
pub struct Quadratic(u32, u32, u32);

impl WeighData<(&u32, &u32)> for Quadratic {
fn weigh_data(&self, (x, y): (&u32, &u32)) -> Weight {

let ax2 = x.saturating_mul(*x).saturating_mul(self.0);
let by = y.saturating_mul(self.1);
let c = self.2;

ax2.saturating_add(by).saturating_add(c)
}
}

impl<T> ClassifyDispatch<T> for Quadratic {
fn classify_dispatch(&self, _: T) -> DispatchClass {
// Classify all calls as Normal (which is the default)
Default::default()
}
}

// A final scale to weight transactions. This one weighs transactions where the first parameter
// is bool. If the bool is true, then the weight is linear in the second parameter. Otherwise
// the weight is constant.
pub struct Conditional(u32);

impl WeighData<(&bool, &u32)> for Conditional {
fn weigh_data(&self, (switch, val): (&bool, &u32)) -> Weight {

if *switch {
val.saturating_mul(self.0)
}
else {
self.0
}
}
}

impl<T> ClassifyDispatch<T> for Conditional {
fn classify_dispatch(&self, _: T) -> DispatchClass {
// Classify all calls as Normal (which is the default)
Default::default()
}
}

decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {

// Store value does not loop at all so a fixed weight is appropriate. Fixed weights can
// be assigned using types available in the Substrate framework. No custom coding is
// necessary.
#[weight = SimpleDispatchInfo::FixedNormal(100)]
fn store_value(_origin, entry: u32) -> Result {

StoredValue::put(entry);

Ok(())
}

// WARNING: The functions that follow, allow the caller to control the
// amount of computation being performed. This is ONLY SAFE when using
// custom weighting structs as shown here.

// add_n sets the storage value n times, so it should cost n times as much as
// store_value. Because it performs both a read and a write, the multiplier is set to 200
// instead of 100 as before.
#[weight = Linear(200)]
fn add_n(_origin, n: u32) -> Result {

let mut old : u32;
for _i in 1..=n {
old = StoredValue::get();
StoredValue::put(old + 1);
}
Ok(())
}

// The actual expense of `double` is proportional to a storage value. Dispatch
// weightings can't use storage values directly, because the weight should be computable
// ahead of time. Instead we have the caller pass in the expected storage value and we
// ensure it is correct.
#[weight = Linear(200)]
fn double(_origin, initial_value: u32) -> Result {

// Ensure the value passed by the caller actually matches storage If this condition
// were not true, the caller would be able to avoid paying appropriate fees.
let initial = StoredValue::get();
ensure!(initial == initial_value, "Storage value did not match parameter");

for _i in 1..=initial {
let old = StoredValue::get();
StoredValue::put(old + 1);
}
Ok(())
}

// This one is quadratic in the first argument plus linear in the second plus a constant.
// This calculation is not meant to do something really useful or common other than
// demonstrate that weights should grow by the same order as the compute required by the
// transaction.
#[weight = Quadratic(200, 30, 100)]
fn complex_calculations(_origin, x: u32, y: u32) -> Result {
// This first part performs a relatively cheap (hence 30)
// in-memory calculations.
let mut part1 = 0;
for _i in 1..=y {
part1 += 2
}

// The second part performs x^2 storage read-writes (hence 200)
for _j in 1..=x {
for _k in 1..=x {
StoredValue::put(StoredValue::get() + 1);
}
}

// One final storage write (hence 100)
StoredValue::put(part1);

Ok(())
}

// Here the first parameter, a boolean has a significant effect on the computational
// intensity of the call.
#[weight = Conditional(200)]
fn add_or_set(_origin, add_flag: bool, val: u32) -> Result {
if add_flag {
StoredValue::put(&val);
}
else {
for _i in 1..=val {
StoredValue::put(StoredValue::get());
}
}

Ok(())
}
}
}
2 changes: 2 additions & 0 deletions kitchen/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ basic-authorship = { package = "substrate-basic-authorship", git = 'https://gith
# Your runtime must have the necessary runtime modules to support consensus (Babe, Grandpa, etc)
runtime = { package = "super-runtime", path = "../runtimes/super-runtime" }
runtime-genesis = { package = "super-genesis", path = "../runtimes/super-genesis" }
# runtime = { package = "weight-fee-runtime", path = "../runtimes/weight-fee-runtime"}
# runtime-genesis = { package = "weight-fee-genesis", path = "../runtimes/weight-fee-genesis"}

[build-dependencies]
vergen = "3.0.4"
2 changes: 1 addition & 1 deletion kitchen/runtimes/super-genesis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "2.0.0"
authors = ["Anonymous"]
edition = "2018"

[dependencies.super-runtime]
[dependencies.runtime]
package = 'super-runtime'
path = '../super-runtime'

Expand Down
2 changes: 1 addition & 1 deletion kitchen/runtimes/super-genesis/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super_runtime::{
use runtime::{
AccountId, BabeConfig, BalancesConfig, GenesisConfig, GrandpaConfig,
SudoConfig, IndicesConfig, SystemConfig, WASM_BINARY,
};
Expand Down
2 changes: 1 addition & 1 deletion kitchen/runtimes/super-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ schedule-on-finalize = { package = "schedule-on-finalize", path = "../../modules
[dependencies.parity-scale-codec]
default-features = false
features = ['derive']
version = '1.0.6' # v1.0.0 indevhub/node-template
version = '1.0.6'

[dependencies.rstd]
default-features = false
Expand Down
28 changes: 14 additions & 14 deletions kitchen/runtimes/super-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,20 @@ impl sudo::Trait for Runtime {
type Proposal = Call;
}

parameter_types! {
pub const TransactionBaseFee: u128 = 0;
pub const TransactionByteFee: u128 = 1;
}

impl transaction_payment::Trait for Runtime {
type Currency = balances::Module<Runtime>;
type OnTransactionPayment = ();
type TransactionBaseFee = TransactionBaseFee;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = ConvertInto;
type FeeMultiplierUpdate = ();
}

// ---------------------- Recipe Runtime Configurations ----------------------
impl simple_event::Trait for Runtime {
type Event = Event;
Expand Down Expand Up @@ -321,20 +335,6 @@ impl schedule_on_finalize::Trait for Runtime {
type ExecutionFrequency = ClearFrequency; // for convenience (can use a different constant)
}

parameter_types! {
pub const TransactionBaseFee: u128 = 0;
pub const TransactionByteFee: u128 = 1;
}

impl transaction_payment::Trait for Runtime {
type Currency = balances::Module<Runtime>;
type OnTransactionPayment = ();
type TransactionBaseFee = TransactionBaseFee;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = ConvertInto;
type FeeMultiplierUpdate = ();
}

construct_runtime!(
pub enum Runtime where
Block = Block,
Expand Down
13 changes: 13 additions & 0 deletions kitchen/runtimes/weight-fee-genesis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = 'weight-fee-genesis'
version = "2.0.0"
authors = ["Anonymous"]
edition = "2018"

[dependencies.runtime]
package = 'weight-fee-runtime'
path = '../weight-fee-runtime'

[dependencies]
babe-primitives = { package = "substrate-consensus-babe-primitives", git = "https://github.com/paritytech/substrate", branch = "master" }
grandpa-primitives = { package = "substrate-finality-grandpa-primitives", git = "https://github.com/paritytech/substrate", branch = "master" }
34 changes: 34 additions & 0 deletions kitchen/runtimes/weight-fee-genesis/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use runtime::{
AccountId, BabeConfig, BalancesConfig, GenesisConfig, GrandpaConfig,
SudoConfig, IndicesConfig, SystemConfig, WASM_BINARY,
};
use babe_primitives::{AuthorityId as BabeId};
use grandpa_primitives::{AuthorityId as GrandpaId};

pub fn testnet_genesis(initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId)>,
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
_enable_println: bool) -> GenesisConfig {
GenesisConfig {
system: Some(SystemConfig {
code: WASM_BINARY.to_vec(),
changes_trie_config: Default::default(),
}),
indices: Some(IndicesConfig {
ids: endowed_accounts.clone(),
}),
balances: Some(BalancesConfig {
balances: endowed_accounts.iter().cloned().map(|k|(k, 1 << 60)).collect(),
vesting: vec![],
}),
sudo: Some(SudoConfig {
key: root_key,
}),
babe: Some(BabeConfig {
authorities: initial_authorities.iter().map(|x| (x.3.clone(), 1)).collect(),
}),
grandpa: Some(GrandpaConfig {
authorities: initial_authorities.iter().map(|x| (x.2.clone(), 1)).collect(),
}),
}
}
Loading

0 comments on commit 6027681

Please sign in to comment.