diff --git a/Cargo.lock b/Cargo.lock index 875610ebe07e5..79b96cde71f88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2351,6 +2351,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -2371,6 +2372,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -2390,6 +2392,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -2452,6 +2455,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -2474,6 +2478,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", "wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2525,6 +2530,7 @@ dependencies = [ "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", diff --git a/demo/cli/src/lib.rs b/demo/cli/src/lib.rs index 485c10570bcbb..2c108a432824e 100644 --- a/demo/cli/src/lib.rs +++ b/demo/cli/src/lib.rs @@ -49,9 +49,9 @@ pub mod error; use std::sync::Arc; use demo_primitives::Hash; -use demo_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig, - SessionConfig, StakingConfig}; -use demo_runtime::{Block, BlockId, UncheckedExtrinsic, BuildStorage}; +use demo_runtime::{Block, BlockId, UncheckedExtrinsic, BuildStorage, GenesisConfig, + ConsensusConfig, CouncilConfig, DemocracyConfig, SessionConfig, StakingConfig, + TimestampConfig}; use futures::{Future, Sink, Stream}; struct DummyPool; @@ -107,10 +107,10 @@ pub fn run(args: I) -> error::Result<()> where authorities: vec![god_key.clone()], }), system: None, - // block_time: 5, // 5 second block time. session: Some(SessionConfig { validators: vec![god_key.clone().into()], session_length: 720, // that's 1 hour per session. + broken_percent_late: 30, }), staking: Some(StakingConfig { current_era: 0, @@ -126,6 +126,8 @@ pub fn run(args: I) -> error::Result<()> where validator_count: 12, sessions_per_era: 24, // 24 hours per era. bonding_duration: 90, // 90 days per bond. + early_era_slash: 10000, + session_reward: 100, }), democracy: Some(DemocracyConfig { launch_period: 120 * 24 * 14, // 2 weeks per public referendum @@ -147,6 +149,9 @@ pub fn run(args: I) -> error::Result<()> where cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal. voting_period: 7 * 120 * 24, // 7 day voting period for council members. }), + timestamp: Some(TimestampConfig { + period: 5, // 5 second block time. + }), }.build_storage(); let client = Arc::new(client::new_in_mem::<_, Block, _>(executor, genesis_storage)?); diff --git a/demo/executor/src/lib.rs b/demo/executor/src/lib.rs index 538a5fc0f2aad..76727240904ad 100644 --- a/demo/executor/src/lib.rs +++ b/demo/executor/src/lib.rs @@ -185,6 +185,7 @@ mod tests { session: Some(SessionConfig { session_length: 2, validators: vec![One.to_raw_public().into(), Two.to_raw_public().into(), three], + broken_percent_late: 100, }), staking: Some(StakingConfig { sessions_per_era: 2, @@ -200,9 +201,12 @@ mod tests { creation_fee: 0, contract_fee: 0, reclaim_rebate: 0, + early_era_slash: 0, + session_reward: 0, }), democracy: Some(Default::default()), council: Some(Default::default()), + timestamp: Some(Default::default()), }.build_storage() } @@ -238,7 +242,7 @@ mod tests { construct_block( 1, [69u8; 32].into(), - hex!("4f7a61bceecddc19d49fbee53f82402c2a8727c1b2aeb5e5070a59f0777a203b").into(), + hex!("786071057714fdd6ea4595eecd4a0f327908d65f462ff5bca0f700fafce588c9").into(), vec![BareExtrinsic { signed: alice(), index: 0, @@ -251,7 +255,7 @@ mod tests { construct_block( 2, block1().1, - hex!("67c588603dd727601263cf8d6138a2003ffc0df793c5ea34e7defc945da24bf0").into(), + hex!("a7f1259cc6b2fa758542f2996e737f8f0de9dec3a9d32641da348178f48b9fc2").into(), vec![ BareExtrinsic { signed: bob(), diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 08529a1974721..d181b7fce6bf6 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -85,9 +85,9 @@ impl consensus::Trait for Concrete { pub type Consensus = consensus::Module; impl timestamp::Trait for Concrete { - const SET_POSITION: u32 = 0; + const TIMESTAMP_SET_POSITION: u32 = 0; - type Value = u64; + type Moment = u64; } /// Timestamp module for this concrete runtime. @@ -103,6 +103,7 @@ impl Convert for SessionKeyConversion { impl session::Trait for Concrete { type ConvertAccountIdToSessionKey = SessionKeyConversion; + type OnSessionChange = Staking; } /// Session module for this concrete runtime. @@ -182,6 +183,7 @@ impl_outer_config! { StakingConfig => staking, DemocracyConfig => democracy, CouncilConfig => council, + TimestampConfig => timestamp, } } diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock index dfaa019b250c8..b53b7289db7b1 100644 --- a/demo/runtime/wasm/Cargo.lock +++ b/demo/runtime/wasm/Cargo.lock @@ -905,6 +905,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -927,6 +928,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -977,6 +979,7 @@ dependencies = [ "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index 702eed6d580d7..f69809afc53e7 100644 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index da26399725e23..e5c77f5310fee 100755 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/polkadot/api/src/full.rs b/polkadot/api/src/full.rs index 46931653e0c14..66f6ceedcd090 100644 --- a/polkadot/api/src/full.rs +++ b/polkadot/api/src/full.rs @@ -112,7 +112,7 @@ impl> PolkadotApi for Client Result { - with_runtime!(self, at, ::runtime::Timestamp::now) + with_runtime!(self, at, ::runtime::Timestamp::get) } fn evaluate_block(&self, at: &CheckedId, block: Block) -> Result { @@ -216,11 +216,13 @@ mod tests { session: Some(SessionConfig { validators: validators(), session_length: 100, + broken_percent_late: 100, }), council: Some(Default::default()), democracy: Some(Default::default()), parachains: Some(Default::default()), staking: Some(Default::default()), + timestamp: Some(Default::default()), }; ::client::new_in_mem(LocalDispatch::new(), genesis_config.build_storage()).unwrap() diff --git a/polkadot/cli/src/preset_config.rs b/polkadot/cli/src/preset_config.rs index e1a914073ee81..5bf44648d92a4 100644 --- a/polkadot/cli/src/preset_config.rs +++ b/polkadot/cli/src/preset_config.rs @@ -22,7 +22,7 @@ use serde_json; use substrate_primitives::{AuthorityId, storage::{StorageKey, StorageData}}; use runtime_primitives::{MakeStorage, BuildStorage, StorageMap}; use polkadot_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig, - SessionConfig, StakingConfig}; + SessionConfig, StakingConfig, TimestampConfig}; use chain_spec::ChainSpec; enum Config { @@ -98,7 +98,8 @@ impl PresetConfig { system: None, session: Some(SessionConfig { validators: initial_authorities.iter().cloned().map(Into::into).collect(), - session_length: 720, // that's 1 hour per session. + session_length: 60, // that's 5 minutes per session. + broken_percent_late: 50, }), staking: Some(StakingConfig { current_era: 0, @@ -110,32 +111,37 @@ impl PresetConfig { creation_fee: 0, contract_fee: 0, reclaim_rebate: 0, + early_era_slash: 10000, + session_reward: 100, balances: endowed_accounts.iter().map(|&k|(k, 1u128 << 60)).collect(), validator_count: 12, - sessions_per_era: 24, // 24 hours per era. - bonding_duration: 90, // 90 days per bond. + sessions_per_era: 12, // 1 hour per era + bonding_duration: 24, // 1 day per bond. }), democracy: Some(DemocracyConfig { - launch_period: 120 * 24 * 14, // 2 weeks per public referendum - voting_period: 120 * 24 * 28, // 4 weeks to discuss & vote on an active referendum - minimum_deposit: 1000, // 1000 as the minimum deposit for a referendum + launch_period: 12 * 60 * 24, // 1 day per public referendum + voting_period: 12 * 60 * 24 * 3, // 3 days to discuss & vote on an active referendum + minimum_deposit: 5000, // 12000 as the minimum deposit for a referendum }), council: Some(CouncilConfig { active_council: vec![], - candidacy_bond: 1000, // 1000 to become a council candidate - voter_bond: 100, // 100 down to vote for a candidate + candidacy_bond: 5000, // 5000 to become a council candidate + voter_bond: 1000, // 1000 down to vote for a candidate present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation. - carry_count: 24, // carry over the 24 runners-up to the next council election - presentation_duration: 120 * 24, // one day for presenting winners. - approval_voting_period: 7 * 120 * 24, // one week period between possible council elections. - term_duration: 180 * 120 * 24, // 180 day term duration for the council. + carry_count: 6, // carry over the 6 runners-up to the next council election + presentation_duration: 12 * 60 * 24, // one day for presenting winners. + approval_voting_period: 12 * 60 * 24 * 2, // two days period between possible council elections. + term_duration: 12 * 60 * 24 * 24, // 24 day term duration for the council. desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit. inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped. - cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal. - voting_period: 7 * 120 * 24, // 7 day voting period for council members. + cooloff_period: 12 * 60 * 24 * 4, // 4 day cooling off period if council member vetoes a proposal. + voting_period: 12 * 60 * 24, // 1 day voting period for council members. }), parachains: Some(Default::default()), + timestamp: Some(TimestampConfig { + period: 5, // 5 second block time. + }), }); let boot_nodes = vec![ "enode://a93a29fa68d965452bf0ff8c1910f5992fe2273a72a1ee8d3a3482f68512a61974211ba32bb33f051ceb1530b8ba3527fc36224ba6b9910329025e6d9153cf50@104.211.54.233:30333".into(), @@ -163,6 +169,7 @@ impl PresetConfig { session: Some(SessionConfig { validators: initial_authorities.iter().cloned().map(Into::into).collect(), session_length: 10, + broken_percent_late: 30, }), staking: Some(StakingConfig { current_era: 0, @@ -178,6 +185,8 @@ impl PresetConfig { validator_count: 2, sessions_per_era: 5, bonding_duration: 2, + early_era_slash: 0, + session_reward: 0, }), democracy: Some(DemocracyConfig { launch_period: 9, @@ -200,6 +209,9 @@ impl PresetConfig { voting_period: 20, }), parachains: Some(Default::default()), + timestamp: Some(TimestampConfig { + period: 5, // 5 second block time. + }), }); let boot_nodes = Vec::new(); PresetConfig { genesis_config, boot_nodes } diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index e0a678993ed08..8aca5da7678ac 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -126,8 +126,8 @@ impl consensus::Trait for Concrete { pub type Consensus = consensus::Module; impl timestamp::Trait for Concrete { - const SET_POSITION: u32 = TIMESTAMP_SET_POSITION; - type Value = u64; + const TIMESTAMP_SET_POSITION: u32 = TIMESTAMP_SET_POSITION; + type Moment = u64; } /// Timestamp module for this concrete runtime. pub type Timestamp = timestamp::Module; @@ -142,6 +142,7 @@ impl Convert for SessionKeyConversion { impl session::Trait for Concrete { type ConvertAccountIdToSessionKey = SessionKeyConversion; + type OnSessionChange = Staking; } /// Session module for this concrete runtime. pub type Session = session::Module; @@ -213,6 +214,7 @@ impl_outer_config! { StakingConfig => staking, DemocracyConfig => democracy, CouncilConfig => council, + TimestampConfig => timestamp, ParachainsConfig => parachains, } } diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index c200648112b8b..b12eaa0765106 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -33,7 +33,7 @@ use rstd::marker::PhantomData; #[cfg(any(feature = "std", test))] use {runtime_io, runtime_primitives}; -pub trait Trait: system::Trait + session::Trait { +pub trait Trait: session::Trait { /// The position of the set_heads call in the block. const SET_POSITION: u32; @@ -232,7 +232,7 @@ mod tests { use runtime_primitives::BuildStorage; use runtime_primitives::traits::{HasPublicAux, Identity, BlakeTwo256}; use runtime_primitives::testing::{Digest, Header}; - use consensus; + use {consensus, timestamp}; #[derive(Clone, Eq, PartialEq)] pub struct Test; @@ -254,6 +254,11 @@ mod tests { } impl session::Trait for Test { type ConvertAccountIdToSessionKey = Identity; + type OnSessionChange = (); + } + impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; } impl Trait for Test { const SET_POSITION: u32 = 0; @@ -272,6 +277,7 @@ mod tests { t.extend(session::GenesisConfig::{ session_length: 1000, validators: vec![1, 2, 3, 4, 5, 6, 7, 8], + broken_percent_late: 100, }.build_storage()); t.extend(GenesisConfig::{ parachains: parachains, diff --git a/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock index 73fab465f692c..74829d6520abe 100644 --- a/polkadot/runtime/wasm/Cargo.lock +++ b/polkadot/runtime/wasm/Cargo.lock @@ -905,6 +905,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -927,6 +928,7 @@ dependencies = [ "substrate-runtime-std 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", ] [[package]] @@ -977,6 +979,7 @@ dependencies = [ "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", + "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index 9fa902edcbcc7..1014b8781bf92 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index a98f50b4ca5d1..1925b939a7881 100755 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm index 2022cf88522fd..1da9d62b94722 100644 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm index 7776affa86622..0bc5c9be1d9cc 100755 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm differ diff --git a/substrate/runtime/consensus/src/lib.rs b/substrate/runtime/consensus/src/lib.rs index d4a84e32ede9c..1fb10d0d9af43 100644 --- a/substrate/runtime/consensus/src/lib.rs +++ b/substrate/runtime/consensus/src/lib.rs @@ -42,7 +42,7 @@ use rstd::prelude::*; use runtime_support::{storage, Parameter}; use runtime_support::dispatch::Result; use runtime_support::storage::unhashed::StorageVec; -use primitives::traits::RefInto; +use primitives::traits::{RefInto, MaybeEmpty}; use primitives::bft::MisbehaviorReport; pub const AUTHORITY_AT: &'static [u8] = b":auth:"; @@ -59,7 +59,7 @@ pub const CODE: &'static [u8] = b":code"; pub type KeyValue = (Vec, Vec); pub trait Trait: system::Trait { - type PublicAux: RefInto; + type PublicAux: RefInto + MaybeEmpty; // MaybeEmpty is for Timestamp's usage. type SessionKey: Parameter + Default; } diff --git a/substrate/runtime/council/Cargo.toml b/substrate/runtime/council/Cargo.toml index 42270c35fca3a..5bfda8899e149 100644 --- a/substrate/runtime/council/Cargo.toml +++ b/substrate/runtime/council/Cargo.toml @@ -22,6 +22,9 @@ substrate-runtime-session = { path = "../session", default_features = false } substrate-runtime-staking = { path = "../staking", default_features = false } substrate-runtime-system = { path = "../system", default_features = false } +[dev-dependencies] +substrate-runtime-timestamp = { path = "../timestamp" } + [features] default = ["std"] std = [ diff --git a/substrate/runtime/council/src/lib.rs b/substrate/runtime/council/src/lib.rs index 8701cb6149447..7a4ff1ccc83c6 100644 --- a/substrate/runtime/council/src/lib.rs +++ b/substrate/runtime/council/src/lib.rs @@ -38,6 +38,8 @@ extern crate substrate_runtime_democracy as democracy; extern crate substrate_runtime_session as session; extern crate substrate_runtime_staking as staking; extern crate substrate_runtime_system as system; +#[cfg(test)] +extern crate substrate_runtime_timestamp as timestamp; use rstd::prelude::*; use primitives::traits::{Zero, One, RefInto, As, AuxLookup}; @@ -646,6 +648,7 @@ mod tests { } impl session::Trait for Test { type ConvertAccountIdToSessionKey = Identity; + type OnSessionChange = staking::Module; } impl staking::Trait for Test { type Balance = u64; @@ -655,6 +658,10 @@ mod tests { impl democracy::Trait for Test { type Proposal = Proposal; } + impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; + } impl Trait for Test {} pub fn new_test_ext(with_council: bool) -> runtime_io::TestExternalities { @@ -666,6 +673,7 @@ mod tests { t.extend(session::GenesisConfig::{ session_length: 1, //??? or 2? validators: vec![10, 20], + broken_percent_late: 100, }.build_storage()); t.extend(staking::GenesisConfig::{ sessions_per_era: 1, @@ -681,6 +689,8 @@ mod tests { creation_fee: 0, contract_fee: 0, reclaim_rebate: 0, + early_era_slash: 0, + session_reward: 0, }.build_storage()); t.extend(democracy::GenesisConfig::{ launch_period: 1, @@ -705,6 +715,7 @@ mod tests { cooloff_period: 2, voting_period: 1, }.build_storage()); + t.extend(timestamp::GenesisConfig::::default().build_storage()); t } diff --git a/substrate/runtime/democracy/Cargo.toml b/substrate/runtime/democracy/Cargo.toml index 2b52f72ff8700..eb0f54671f1d9 100644 --- a/substrate/runtime/democracy/Cargo.toml +++ b/substrate/runtime/democracy/Cargo.toml @@ -19,6 +19,9 @@ substrate-runtime-session = { path = "../session", default_features = false } substrate-runtime-staking = { path = "../staking", default_features = false } substrate-runtime-system = { path = "../system", default_features = false } +[dev-dependencies] +substrate-runtime-timestamp = { path = "../timestamp" } + [features] default = ["std"] std = [ diff --git a/substrate/runtime/democracy/src/lib.rs b/substrate/runtime/democracy/src/lib.rs index c6affbcb2db1f..17b72d68e4095 100644 --- a/substrate/runtime/democracy/src/lib.rs +++ b/substrate/runtime/democracy/src/lib.rs @@ -41,6 +41,8 @@ extern crate substrate_runtime_consensus as consensus; extern crate substrate_runtime_session as session; extern crate substrate_runtime_staking as staking; extern crate substrate_runtime_system as system; +#[cfg(test)] +extern crate substrate_runtime_timestamp as timestamp; use rstd::prelude::*; use rstd::result; @@ -356,6 +358,7 @@ mod tests { use primitives::BuildStorage; use primitives::traits::{HasPublicAux, Identity, BlakeTwo256}; use primitives::testing::{Digest, Header}; + use session::OnSessionChange; impl_outer_dispatch! { #[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialEq)] @@ -387,12 +390,17 @@ mod tests { } impl session::Trait for Test { type ConvertAccountIdToSessionKey = Identity; + type OnSessionChange = staking::Module; } impl staking::Trait for Test { type Balance = u64; type DetermineContractAddress = staking::DummyContractAddressFor; type AccountIndex = u64; } + impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; + } impl Trait for Test { type Proposal = Proposal; } @@ -406,6 +414,7 @@ mod tests { t.extend(session::GenesisConfig::{ session_length: 1, //??? or 2? validators: vec![10, 20], + broken_percent_late: 100, }.build_storage()); t.extend(staking::GenesisConfig::{ sessions_per_era: 1, @@ -421,12 +430,15 @@ mod tests { creation_fee: 0, contract_fee: 0, reclaim_rebate: 0, + early_era_slash: 0, + session_reward: 0, }.build_storage()); t.extend(GenesisConfig::{ launch_period: 1, voting_period: 1, minimum_deposit: 1, }.build_storage()); + t.extend(timestamp::GenesisConfig::::default().build_storage()); t } @@ -481,7 +493,7 @@ mod tests { assert_eq!(Democracy::tally(r), (10, 0)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::era_length(), 2); }); @@ -559,19 +571,19 @@ mod tests { System::set_block_number(1); assert_ok!(Democracy::vote(&1, 0, true)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::bonding_duration(), 4); System::set_block_number(2); assert_ok!(Democracy::vote(&1, 1, true)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::bonding_duration(), 3); System::set_block_number(3); assert_ok!(Democracy::vote(&1, 2, true)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::bonding_duration(), 2); }); } @@ -592,7 +604,7 @@ mod tests { assert_eq!(Democracy::tally(r), (10, 0)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::era_length(), 2); }); @@ -607,7 +619,7 @@ mod tests { assert_ok!(Democracy::cancel_referendum(r)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::era_length(), 1); }); @@ -625,7 +637,7 @@ mod tests { assert_eq!(Democracy::tally(r), (0, 10)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::era_length(), 1); }); @@ -646,7 +658,7 @@ mod tests { assert_eq!(Democracy::tally(r), (110, 100)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::era_length(), 2); }); @@ -663,7 +675,7 @@ mod tests { assert_eq!(Democracy::tally(r), (60, 50)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::era_length(), 1); }); @@ -684,7 +696,7 @@ mod tests { assert_eq!(Democracy::tally(r), (100, 50)); assert_eq!(Democracy::end_block(System::block_number()), Ok(())); - Staking::check_new_era(); + Staking::on_session_change(true, 0); assert_eq!(Staking::era_length(), 2); }); diff --git a/substrate/runtime/executive/Cargo.toml b/substrate/runtime/executive/Cargo.toml index 64e2f89d06eed..4b8307170ab94 100644 --- a/substrate/runtime/executive/Cargo.toml +++ b/substrate/runtime/executive/Cargo.toml @@ -19,6 +19,7 @@ substrate-primitives = { path = "../../primitives" } substrate-runtime-session = { path = "../session" } substrate-runtime-staking = { path = "../staking" } substrate-runtime-consensus = { path = "../consensus" } +substrate-runtime-timestamp = { path = "../timestamp" } [features] default = ["std"] diff --git a/substrate/runtime/executive/src/lib.rs b/substrate/runtime/executive/src/lib.rs index 078632c6c5957..9b69387713f24 100644 --- a/substrate/runtime/executive/src/lib.rs +++ b/substrate/runtime/executive/src/lib.rs @@ -30,6 +30,8 @@ extern crate substrate_runtime_io as runtime_io; extern crate substrate_codec as codec; extern crate substrate_runtime_primitives as primitives; extern crate substrate_runtime_system as system; +#[cfg(test)] +extern crate substrate_runtime_timestamp as timestamp; #[cfg(test)] #[macro_use] @@ -250,12 +252,17 @@ mod tests { } impl session::Trait for Test { type ConvertAccountIdToSessionKey = Identity; + type OnSessionChange = staking::Module; } impl staking::Trait for Test { type Balance = u64; type DetermineContractAddress = staking::DummyContractAddressFor; type AccountIndex = u64; } + impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; + } type TestXt = primitives::testing::TestXt>; type Executive = super::Executive, NullLookup, staking::Module, (session::Module, staking::Module)>; @@ -277,6 +284,8 @@ mod tests { creation_fee: 0, contract_fee: 0, reclaim_rebate: 0, + early_era_slash: 0, + session_reward: 0, }.build_storage()); let xt = primitives::testing::TestXt((1, 0, Call::transfer(2.into(), 69))); with_externalities(&mut t, || { @@ -292,6 +301,7 @@ mod tests { t.extend(consensus::GenesisConfig::::default().build_storage()); t.extend(session::GenesisConfig::::default().build_storage()); t.extend(staking::GenesisConfig::::default().build_storage()); + t.extend(timestamp::GenesisConfig::::default().build_storage()); t } @@ -302,7 +312,7 @@ mod tests { header: Header { parent_hash: [69u8; 32].into(), number: 1, - state_root: hex!("4fd406d2d62a841f7e2f956b52ce9ed98111c9eb6b3a9051aa4667b470030832").into(), + state_root: hex!("b47a0bfc249af6e00c71a45fcd5619c47b6f71cb4d5c62ab7bf1fe9601d5efc4").into(), extrinsics_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), digest: Digest { logs: vec![], }, }, @@ -336,7 +346,7 @@ mod tests { header: Header { parent_hash: [69u8; 32].into(), number: 1, - state_root: hex!("4fd406d2d62a841f7e2f956b52ce9ed98111c9eb6b3a9051aa4667b470030832").into(), + state_root: hex!("b47a0bfc249af6e00c71a45fcd5619c47b6f71cb4d5c62ab7bf1fe9601d5efc4").into(), extrinsics_root: [0u8; 32].into(), digest: Digest { logs: vec![], }, }, diff --git a/substrate/runtime/session/Cargo.toml b/substrate/runtime/session/Cargo.toml index 93938fc3561b7..74edc88b10219 100644 --- a/substrate/runtime/session/Cargo.toml +++ b/substrate/runtime/session/Cargo.toml @@ -17,6 +17,7 @@ substrate-runtime-support = { path = "../../runtime-support", default_features = substrate-runtime-primitives = { path = "../primitives", default_features = false } substrate-runtime-consensus = { path = "../consensus", default_features = false } substrate-runtime-system = { path = "../system", default_features = false } +substrate-runtime-timestamp = { path = "../timestamp", default_features = false } [features] default = ["std"] diff --git a/substrate/runtime/session/src/lib.rs b/substrate/runtime/session/src/lib.rs index b8de8abb1ad5a..fea796a2f0168 100644 --- a/substrate/runtime/session/src/lib.rs +++ b/substrate/runtime/session/src/lib.rs @@ -43,14 +43,26 @@ extern crate substrate_codec as codec; extern crate substrate_runtime_primitives as primitives; extern crate substrate_runtime_consensus as consensus; extern crate substrate_runtime_system as system; +extern crate substrate_runtime_timestamp as timestamp; use rstd::prelude::*; -use primitives::traits::{Zero, One, RefInto, Executable, Convert}; +use primitives::traits::{Zero, One, RefInto, Executable, Convert, As}; use runtime_support::{StorageValue, StorageMap}; use runtime_support::dispatch::Result; -pub trait Trait: consensus::Trait { +/// A session has changed. +pub trait OnSessionChange { + /// Session has changed. + fn on_session_change(normal_rotation: bool, time_elapsed: T); +} + +impl OnSessionChange for () { + fn on_session_change(_: bool, _: T) {} +} + +pub trait Trait: timestamp::Trait { type ConvertAccountIdToSessionKey: Convert; + type OnSessionChange: OnSessionChange; } decl_module! { @@ -64,7 +76,7 @@ decl_module! { #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum PrivCall { fn set_length(new: T::BlockNumber) -> Result = 0; - fn force_new_session() -> Result = 1; + fn force_new_session(normal_rotation: bool) -> Result = 1; } } decl_storage! { @@ -76,6 +88,10 @@ decl_storage! { pub SessionLength get(length): b"ses:len" => required T::BlockNumber; // Current index of the session. pub CurrentIndex get(current_index): b"ses:ind" => required T::BlockNumber; + // Timestamp when current session started. + pub CurrentStart get(current_start): b"ses:current_start" => required T::Moment; + // Percent by which the session must necessarily finish late before we early-exit the session. + pub BrokenPercentLate get(broken_percent_late): b"ses:broken_percent_late" => required T::Moment; // Block at which the session length last changed. LastLengthChange: b"ses:llc" => T::BlockNumber; @@ -111,8 +127,8 @@ impl Module { } /// Forces a new session. - fn force_new_session() -> Result { - Self::rotate_session(); + fn force_new_session(normal_rotation: bool) -> Result { + Self::rotate_session(normal_rotation); Ok(()) } @@ -135,15 +151,20 @@ impl Module { // new set. // check block number and call next_session if necessary. let block_number = >::block_number(); - if ((block_number - Self::last_length_change()) % Self::length()).is_zero() { - Self::rotate_session(); + let is_final_block = ((block_number - Self::last_length_change()) % Self::length()).is_zero(); + if is_final_block || Self::broken_validation() { + Self::rotate_session(is_final_block); } } /// Move onto next session: register the new authority set. - pub fn rotate_session() { + pub fn rotate_session(normal_rotation: bool) { + let now = >::get(); + let time_elapsed = now.clone() - Self::current_start(); + // Increment current session index. >::put(>::get() + One::one()); + >::put(now); // Enact era length change. if let Some(next_len) = >::take() { @@ -152,6 +173,8 @@ impl Module { >::put(block_number); } + T::OnSessionChange::on_session_change(normal_rotation, time_elapsed); + // Update any changes in session keys. Self::validators().iter().enumerate().for_each(|(i, v)| { if let Some(n) = >::take(v) { @@ -159,6 +182,38 @@ impl Module { } }); } + + /// Get the time that should have elapsed over a session if everything was working perfectly. + pub fn ideal_session_duration() -> T::Moment { + let block_period = >::block_period(); + let session_length = >::sa(Self::length()); + session_length * block_period + } + + /// Number of blocks remaining in this session, not counting this one. If the session is + /// due to rotate at the end of this block, then it will return 0. If the just began, then + /// it will return `Self::length() - 1`. + pub fn blocks_remaining() -> T::BlockNumber { + let length = Self::length(); + let length_minus_1 = length - One::one(); + let block_number = >::block_number(); + length_minus_1 - (block_number - Self::last_length_change() + length_minus_1) % length + } + + /// Returns `true` if the current validator set is taking took long to validate blocks. + pub fn broken_validation() -> bool { + let now = >::get(); + let block_period = >::block_period(); + let blocks_remaining = Self::blocks_remaining(); + if blocks_remaining.is_zero() { + false + } else { + let blocks_remaining = >::sa(blocks_remaining); + now + blocks_remaining * block_period > + Self::current_start() + Self::ideal_session_duration() * + (T::Moment::sa(100) + Self::broken_percent_late()) / T::Moment::sa(100) + } + } } impl Executable for Module { @@ -171,6 +226,7 @@ impl Executable for Module { pub struct GenesisConfig { pub session_length: T::BlockNumber, pub validators: Vec, + pub broken_percent_late: T::Moment, } #[cfg(any(feature = "std", test))] @@ -180,6 +236,7 @@ impl Default for GenesisConfig { GenesisConfig { session_length: T::BlockNumber::sa(1000), validators: vec![], + broken_percent_late: T::Moment::sa(30), } } } @@ -194,7 +251,9 @@ impl primitives::BuildStorage for GenesisConfig map![ twox_128(>::key()).to_vec() => self.session_length.encode(), twox_128(>::key()).to_vec() => T::BlockNumber::sa(0).encode(), - twox_128(>::key()).to_vec() => self.validators.encode() + twox_128(>::key()).to_vec() => T::Moment::zero().encode(), + twox_128(>::key()).to_vec() => self.validators.encode(), + twox_128(>::key()).to_vec() => self.broken_percent_late.encode() ] } } @@ -226,12 +285,18 @@ mod tests { type AccountId = u64; type Header = Header; } + impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; + } impl Trait for Test { type ConvertAccountIdToSessionKey = Identity; + type OnSessionChange = (); } type System = system::Module; type Consensus = consensus::Module; + type Timestamp = timestamp::Module; type Session = Module; fn new_test_ext() -> runtime_io::TestExternalities { @@ -240,9 +305,13 @@ mod tests { code: vec![], authorities: vec![1, 2, 3], }.build_storage()); + t.extend(timestamp::GenesisConfig::{ + period: 5, + }.build_storage()); t.extend(GenesisConfig::{ session_length: 2, validators: vec![1, 2, 3], + broken_percent_late: 30, }.build_storage()); t } @@ -256,6 +325,36 @@ mod tests { }); } + #[test] + fn should_identify_broken_validation() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(2); + assert_eq!(Session::blocks_remaining(), 0); + Timestamp::set_timestamp(0); + assert_ok!(Session::set_length(3)); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 1); + assert_eq!(Session::length(), 3); + assert_eq!(Session::current_start(), 0); + assert_eq!(Session::ideal_session_duration(), 15); + // ideal end = 0 + 15 * 3 = 15 + // broken_limit = 15 * 130 / 100 = 19 + + System::set_block_number(3); + assert_eq!(Session::blocks_remaining(), 2); + Timestamp::set_timestamp(9); // earliest end = 9 + 2 * 5 = 19; OK. + assert!(!Session::broken_validation()); + Session::check_rotate_session(); + + System::set_block_number(4); + assert_eq!(Session::blocks_remaining(), 1); + Timestamp::set_timestamp(15); // another 1 second late. earliest end = 15 + 1 * 5 = 20; broken. + assert!(Session::broken_validation()); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 2); + }); + } + #[test] fn session_length_change_should_work() { with_externalities(&mut new_test_ext(), || { diff --git a/substrate/runtime/staking/Cargo.toml b/substrate/runtime/staking/Cargo.toml index b231ed7cf192e..f6dc8d96a4c00 100644 --- a/substrate/runtime/staking/Cargo.toml +++ b/substrate/runtime/staking/Cargo.toml @@ -20,6 +20,7 @@ substrate-runtime-primitives = { path = "../primitives", default_features = fals substrate-runtime-consensus = { path = "../consensus", default_features = false } substrate-runtime-system = { path = "../system", default_features = false } substrate-runtime-session = { path = "../session", default_features = false } +substrate-runtime-timestamp = { path = "../timestamp", default_features = false } [dev-dependencies] wabt = "0.1.7" diff --git a/substrate/runtime/staking/src/account_db.rs b/substrate/runtime/staking/src/account_db.rs new file mode 100644 index 0000000000000..e7d883a656b8a --- /dev/null +++ b/substrate/runtime/staking/src/account_db.rs @@ -0,0 +1,224 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate Demo. + +// Substrate Demo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate Demo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate Demo. If not, see . + +//! Auxilliaries to help with managing partial changes to accounts state. + +use rstd::prelude::*; +use rstd::cell::RefCell; +use rstd::collections::btree_map::{BTreeMap, Entry}; +use runtime_support::StorageMap; +use super::*; + +pub struct ChangeEntry { + balance: Option, + code: Option>, + storage: BTreeMap, Option>>, +} + +// Cannot derive(Default) since it erroneously bounds T by Default. +impl Default for ChangeEntry { + fn default() -> Self { + ChangeEntry { + balance: Default::default(), + code: Default::default(), + storage: Default::default(), + } + } +} + +impl ChangeEntry { + pub fn contract_created(b: T::Balance, c: Vec) -> Self { + ChangeEntry { balance: Some(b), code: Some(c), storage: Default::default() } + } + pub fn balance_changed(b: T::Balance) -> Self { + ChangeEntry { balance: Some(b), code: None, storage: Default::default() } + } +} + +pub type State = BTreeMap<::AccountId, ChangeEntry>; + +pub trait AccountDb { + fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option>; + fn get_code(&self, account: &T::AccountId) -> Vec; + fn get_balance(&self, account: &T::AccountId) -> T::Balance; + + fn merge(&mut self, state: State); +} + +pub struct DirectAccountDb; +impl AccountDb for DirectAccountDb { + fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { + >::get(&(account.clone(), location.to_vec())) + } + fn get_code(&self, account: &T::AccountId) -> Vec { + >::get(account) + } + fn get_balance(&self, account: &T::AccountId) -> T::Balance { + >::get(account) + } + fn merge(&mut self, s: State) { + let ed = >::existential_deposit(); + for (address, changed) in s.into_iter() { + if let Some(balance) = changed.balance { + // If the balance is too low, then the account is reaped. + // NOTE: There are two balances for every account: `reserved_balance` and + // `free_balance`. This contract subsystem only cares about the latter: whenever + // the term "balance" is used *here* it should be assumed to mean "free balance" + // in the rest of the module. + // Free balance can never be less than ED. If that happens, it gets reduced to zero + // and the account information relevant to this subsystem is deleted (i.e. the + // account is reaped). + // NOTE: This is orthogonal to the `Bondage` value that an account has, a high + // value of which makes even the `free_balance` unspendable. + // TODO: enforce this for the other balance-altering functions. + if balance < ed { + >::on_free_too_low(&address); + continue; + } else { + if !>::exists(&address) { + let outcome = >::new_account(&address, balance); + let credit = match outcome { + NewAccountOutcome::GoodHint => balance + >::reclaim_rebate(), + _ => balance, + }; + >::insert(&address, credit); + } else { + >::insert(&address, balance); + } + } + } + if let Some(code) = changed.code { + >::insert(&address, &code); + } + for (k, v) in changed.storage.into_iter() { + if let Some(value) = v { + >::insert((address.clone(), k), &value); + } else { + >::remove((address.clone(), k)); + } + } + } + } +} + +pub struct OverlayAccountDb<'a, T: Trait + 'a> { + local: RefCell>, + underlying: &'a AccountDb, +} +impl<'a, T: Trait> OverlayAccountDb<'a, T> { + pub fn new(underlying: &'a AccountDb) -> OverlayAccountDb<'a, T> { + OverlayAccountDb { + local: RefCell::new(State::new()), + underlying, + } + } + + pub fn into_state(self) -> State { + self.local.into_inner() + } + + fn set_storage(&mut self, account: &T::AccountId, location: Vec, value: Option>) { + self.local + .borrow_mut() + .entry(account.clone()) + .or_insert(Default::default()) + .storage + .insert(location, value); + } + pub fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) { + self.local + .borrow_mut() + .entry(account.clone()) + .or_insert(Default::default()) + .balance = Some(balance); + } +} + +impl<'a, T: Trait> AccountDb for OverlayAccountDb<'a, T> { + fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { + self.local + .borrow() + .get(account) + .and_then(|a| a.storage.get(location)) + .cloned() + .unwrap_or_else(|| self.underlying.get_storage(account, location)) + } + fn get_code(&self, account: &T::AccountId) -> Vec { + self.local + .borrow() + .get(account) + .and_then(|a| a.code.clone()) + .unwrap_or_else(|| self.underlying.get_code(account)) + } + fn get_balance(&self, account: &T::AccountId) -> T::Balance { + self.local + .borrow() + .get(account) + .and_then(|a| a.balance) + .unwrap_or_else(|| self.underlying.get_balance(account)) + } + fn merge(&mut self, s: State) { + let mut local = self.local.borrow_mut(); + + for (address, changed) in s.into_iter() { + match local.entry(address) { + Entry::Occupied(e) => { + let mut value = e.into_mut(); + if changed.balance.is_some() { + value.balance = changed.balance; + } + if changed.code.is_some() { + value.code = changed.code; + } + value.storage.extend(changed.storage.into_iter()); + } + Entry::Vacant(e) => { + e.insert(changed); + } + } + } + } +} + +pub(crate) struct StakingExt<'a, 'b: 'a, T: Trait + 'b> { + pub account_db: &'a mut OverlayAccountDb<'b, T>, + pub account: T::AccountId, +} +impl<'a, 'b: 'a, T: Trait> contract::Ext for StakingExt<'a, 'b, T> { + type AccountId = T::AccountId; + type Balance = T::Balance; + + fn get_storage(&self, key: &[u8]) -> Option> { + self.account_db.get_storage(&self.account, key) + } + fn set_storage(&mut self, key: &[u8], value: Option>) { + self.account_db.set_storage(&self.account, key.to_vec(), value); + } + fn create(&mut self, code: &[u8], value: Self::Balance) { + if let Ok(Some(commit_state)) = + Module::::effect_create(&self.account, code, value, self.account_db) + { + self.account_db.merge(commit_state); + } + } + fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) { + if let Ok(Some(commit_state)) = + Module::::effect_transfer(&self.account, to, value, self.account_db) + { + self.account_db.merge(commit_state); + } + } +} diff --git a/substrate/runtime/staking/src/genesis_config.rs b/substrate/runtime/staking/src/genesis_config.rs new file mode 100644 index 0000000000000..295bd8444ef5b --- /dev/null +++ b/substrate/runtime/staking/src/genesis_config.rs @@ -0,0 +1,155 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Substrate Demo. + +// Substrate Demo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate Demo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate Demo. If not, see . + +//! Build a staking genesis block. + +#![cfg(feature = "std")] + +use rstd::prelude::*; +use runtime_io::twox_128; +use codec::Slicable; +use runtime_support::{StorageValue, StorageMap}; +use primitives::traits::{Zero, As}; +use {runtime_io, primitives}; +use super::{Trait, ENUM_SET_SIZE, EnumSet, NextEnumSet, Intentions, CurrentEra, + BondingDuration, ContractFee, CreationFee, TransferFee, ReclaimRebate, + ExistentialDeposit, TransactionByteFee, TransactionBaseFee, TotalStake, + SessionsPerEra, ValidatorCount, FreeBalance, SessionReward, EarlyEraSlash}; + +pub struct GenesisConfig { + pub sessions_per_era: T::BlockNumber, + pub current_era: T::BlockNumber, + pub balances: Vec<(T::AccountId, T::Balance)>, + pub intentions: Vec, + pub validator_count: u64, + pub bonding_duration: T::BlockNumber, + pub transaction_base_fee: T::Balance, + pub transaction_byte_fee: T::Balance, + pub transfer_fee: T::Balance, + pub creation_fee: T::Balance, + pub contract_fee: T::Balance, + pub reclaim_rebate: T::Balance, + pub existential_deposit: T::Balance, + pub session_reward: T::Balance, + pub early_era_slash: T::Balance, +} + +impl GenesisConfig where T::AccountId: From { + pub fn simple() -> Self { + GenesisConfig { + sessions_per_era: T::BlockNumber::sa(2), + current_era: T::BlockNumber::sa(0), + balances: vec![(T::AccountId::from(1), T::Balance::sa(111))], + intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)], + validator_count: 3, + bonding_duration: T::BlockNumber::sa(0), + transaction_base_fee: T::Balance::sa(0), + transaction_byte_fee: T::Balance::sa(0), + transfer_fee: T::Balance::sa(0), + creation_fee: T::Balance::sa(0), + contract_fee: T::Balance::sa(0), + existential_deposit: T::Balance::sa(0), + reclaim_rebate: T::Balance::sa(0), + session_reward: T::Balance::sa(0), + early_era_slash: T::Balance::sa(0), + } + } + + pub fn extended() -> Self { + GenesisConfig { + sessions_per_era: T::BlockNumber::sa(3), + current_era: T::BlockNumber::sa(1), + balances: vec![ + (T::AccountId::from(1), T::Balance::sa(10)), + (T::AccountId::from(2), T::Balance::sa(20)), + (T::AccountId::from(3), T::Balance::sa(30)), + (T::AccountId::from(4), T::Balance::sa(40)), + (T::AccountId::from(5), T::Balance::sa(50)), + (T::AccountId::from(6), T::Balance::sa(60)), + (T::AccountId::from(7), T::Balance::sa(1)) + ], + intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)], + validator_count: 3, + bonding_duration: T::BlockNumber::sa(0), + transaction_base_fee: T::Balance::sa(1), + transaction_byte_fee: T::Balance::sa(0), + transfer_fee: T::Balance::sa(0), + creation_fee: T::Balance::sa(0), + contract_fee: T::Balance::sa(0), + existential_deposit: T::Balance::sa(0), + reclaim_rebate: T::Balance::sa(0), + session_reward: T::Balance::sa(0), + early_era_slash: T::Balance::sa(0), + } + } +} + +impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + sessions_per_era: T::BlockNumber::sa(1000), + current_era: T::BlockNumber::sa(0), + balances: vec![], + intentions: vec![], + validator_count: 0, + bonding_duration: T::BlockNumber::sa(1000), + transaction_base_fee: T::Balance::sa(0), + transaction_byte_fee: T::Balance::sa(0), + transfer_fee: T::Balance::sa(0), + creation_fee: T::Balance::sa(0), + contract_fee: T::Balance::sa(0), + existential_deposit: T::Balance::sa(0), + reclaim_rebate: T::Balance::sa(0), + session_reward: T::Balance::sa(0), + early_era_slash: T::Balance::sa(0), + } + } +} + +impl primitives::BuildStorage for GenesisConfig { + fn build_storage(self) -> runtime_io::TestExternalities { + let total_stake: T::Balance = self.balances.iter().fold(Zero::zero(), |acc, &(_, n)| acc + n); + + let mut r: runtime_io::TestExternalities = map![ + twox_128(>::key()).to_vec() => T::AccountIndex::sa(self.balances.len() / ENUM_SET_SIZE).encode(), + twox_128(>::key()).to_vec() => self.intentions.encode(), + twox_128(>::key()).to_vec() => self.sessions_per_era.encode(), + twox_128(>::key()).to_vec() => self.validator_count.encode(), + twox_128(>::key()).to_vec() => self.bonding_duration.encode(), + twox_128(>::key()).to_vec() => self.transaction_base_fee.encode(), + twox_128(>::key()).to_vec() => self.transaction_byte_fee.encode(), + twox_128(>::key()).to_vec() => self.transfer_fee.encode(), + twox_128(>::key()).to_vec() => self.creation_fee.encode(), + twox_128(>::key()).to_vec() => self.contract_fee.encode(), + twox_128(>::key()).to_vec() => self.existential_deposit.encode(), + twox_128(>::key()).to_vec() => self.reclaim_rebate.encode(), + twox_128(>::key()).to_vec() => self.current_era.encode(), + twox_128(>::key()).to_vec() => self.session_reward.encode(), + twox_128(>::key()).to_vec() => self.early_era_slash.encode(), + twox_128(>::key()).to_vec() => total_stake.encode() + ]; + + let ids: Vec<_> = self.balances.iter().map(|x| x.0.clone()).collect(); + for i in 0..(ids.len() + ENUM_SET_SIZE - 1) / ENUM_SET_SIZE { + r.insert(twox_128(&>::key_for(T::AccountIndex::sa(i))).to_vec(), + ids[i * ENUM_SET_SIZE..ids.len().min((i + 1) * ENUM_SET_SIZE)].to_owned().encode()); + } + for (who, value) in self.balances.into_iter() { + r.insert(twox_128(&>::key_for(who)).to_vec(), value.encode()); + } + r + } +} diff --git a/substrate/runtime/staking/src/lib.rs b/substrate/runtime/staking/src/lib.rs index 12d591e3a6172..138130886af7d 100644 --- a/substrate/runtime/staking/src/lib.rs +++ b/substrate/runtime/staking/src/lib.rs @@ -43,24 +43,31 @@ extern crate substrate_runtime_consensus as consensus; extern crate substrate_runtime_sandbox as sandbox; extern crate substrate_runtime_session as session; extern crate substrate_runtime_system as system; +extern crate substrate_runtime_timestamp as timestamp; #[cfg(test)] use std::fmt::Debug; +use account_db::State; use rstd::prelude::*; use rstd::{cmp, result}; -use rstd::cell::RefCell; -use rstd::collections::btree_map::{BTreeMap, Entry}; +use rstd::collections::btree_map::BTreeMap; use codec::{Input, Slicable}; use runtime_support::{StorageValue, StorageMap, Parameter}; use runtime_support::dispatch::Result; +use session::OnSessionChange; use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment, As, AuxLookup, Hashing as HashingT, Member}; use address::Address as RawAddress; pub mod address; -#[cfg(test)] mod mock; -#[cfg(test)] mod tests; +mod genesis_config; +mod account_db; + +#[cfg(feature = "std")] +pub use genesis_config::GenesisConfig; + +pub use account_db::*; /// Number of account IDs stored per enum set. const ENUM_SET_SIZE: usize = 64; @@ -90,6 +97,15 @@ pub trait ContractAddressFor { fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId; } +#[cfg(feature = "std")] +pub struct DummyContractAddressFor; +#[cfg(feature = "std")] +impl ContractAddressFor for DummyContractAddressFor { + fn contract_address_for(_code: &[u8], origin: &u64) -> u64 { + origin + 1 + } +} + impl ContractAddressFor for Hashing where Hashing: HashingT, AccountId: Sized + Slicable + From, @@ -119,7 +135,9 @@ decl_module! { pub enum Call where aux: T::PublicAux { fn transfer(aux, dest: RawAddress, value: T::Balance) -> Result = 0; fn stake(aux) -> Result = 1; - fn unstake(aux) -> Result = 2; + fn unstake(aux, index: u32) -> Result = 2; + fn nominate(aux, target: RawAddress) -> Result = 3; + fn unnominate(aux, target_index: u32) -> Result = 4; } #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -141,6 +159,7 @@ decl_storage! { // The length of a staking era in sessions. pub SessionsPerEra get(sessions_per_era): b"sta:spe" => required T::BlockNumber; // The total amount of stake on the system. + // TODO: this doesn't actually track total stake yet - it should do. pub TotalStake get(total_stake): b"sta:tot" => required T::Balance; // The fee to be paid for making a transaction; the base. pub TransactionBaseFee get(transaction_base_fee): b"sta:basefee" => required T::Balance; @@ -156,15 +175,27 @@ decl_storage! { pub CreationFee get(creation_fee): b"sta:creation_fee" => required T::Balance; // The fee required to create a contract. At least as big as ReclaimRebate. pub ContractFee get(contract_fee): b"sta:contract_fee" => required T::Balance; + // Maximum reward, per validator, that is provided per acceptable session. + pub SessionReward get(session_reward): b"sta:session_reward" => required T::Balance; + // Slash, per validator that is taken per abnormal era end. + pub EarlyEraSlash get(early_era_slash): b"sta:early_era_slash" => required T::Balance; // The current era index. pub CurrentEra get(current_era): b"sta:era" => required T::BlockNumber; // All the accounts with a desire to stake. - pub Intentions: b"sta:wil:" => default Vec; + pub Intentions get(intentions): b"sta:wil:" => default Vec; + // All nominator -> nominee relationships. + pub Nominating get(nominating): b"sta:nominating" => map [ T::AccountId => T::AccountId ]; + // Nominators for a particular account. + pub NominatorsFor get(nominators_for): b"sta:nominators_for" => default map [ T::AccountId => Vec ]; + // Nominators for a particular account that is in action right now. + pub CurrentNominatorsFor get(current_nominators_for): b"sta:current_nominators_for" => default map [ T::AccountId => Vec ]; // The next value of sessions per era. pub NextSessionsPerEra get(next_sessions_per_era): b"sta:nse" => T::BlockNumber; - // The block number at which the era length last changed. + // The session index at which the era length last changed. pub LastEraLengthChange get(last_era_length_change): b"sta:lec" => default T::BlockNumber; + // The current era stake threshold + pub StakeThreshold get(stake_threshold): b"sta:stake_threshold" => required T::Balance; // The next free enumeration set. pub NextEnumSet get(next_enum_set): b"sta:next_enum" => required T::AccountIndex; @@ -284,27 +315,82 @@ impl Module { /// /// Effects will be felt at the beginning of the next era. fn stake(aux: &T::PublicAux) -> Result { + let aux = aux.ref_into(); + ensure!(Self::nominating(aux).is_none(), "Cannot stake if already nominating."); let mut intentions = >::get(); // can't be in the list twice. - ensure!(intentions.iter().find(|&t| t == aux.ref_into()).is_none(), "Cannot stake if already staked."); - intentions.push(aux.ref_into().clone()); + ensure!(intentions.iter().find(|&t| t == aux).is_none(), "Cannot stake if already staked."); + intentions.push(aux.clone()); >::put(intentions); - >::insert(aux.ref_into(), T::BlockNumber::max_value()); + >::insert(aux, T::BlockNumber::max_value()); Ok(()) } /// Retract the desire to stake for the transactor. /// /// Effects will be felt at the beginning of the next era. - fn unstake(aux: &T::PublicAux) -> Result { + fn unstake(aux: &T::PublicAux, position: u32) -> Result { + let aux = aux.ref_into(); + let position = position as usize; let mut intentions = >::get(); - let position = intentions.iter().position(|t| t == aux.ref_into()).ok_or("Cannot unstake if not already staked.")?; +// let position = intentions.iter().position(|t| t == aux.ref_into()).ok_or("Cannot unstake if not already staked.")?; + if intentions.get(position) != Some(aux) { + return Err("Invalid index") + } intentions.swap_remove(position); >::put(intentions); >::insert(aux.ref_into(), Self::current_era() + Self::bonding_duration()); Ok(()) } + fn nominate(aux: &T::PublicAux, target: RawAddress) -> Result { + let target = Self::lookup(target)?; + let aux = aux.ref_into(); + + ensure!(Self::nominating(aux).is_none(), "Cannot nominate if already nominating."); + ensure!(Self::intentions().iter().find(|&t| t == aux.ref_into()).is_none(), "Cannot nominate if already staked."); + + // update nominators_for + let mut t = Self::nominators_for(&target); + t.push(aux.clone()); + >::insert(&target, t); + + // update nominating + >::insert(aux, &target); + + // Update bondage + >::insert(aux.ref_into(), T::BlockNumber::max_value()); + + Ok(()) + } + + /// Will panic if called when source isn't currently nominating target. + /// Updates Nominating, NominatorsFor and NominationBalance. + fn unnominate(aux: &T::PublicAux, target_index: u32) -> Result { + let source = aux.ref_into(); + let target_index = target_index as usize; + + let target = >::get(source).ok_or("Account must be nominating")?; + + let mut t = Self::nominators_for(&target); + if t.get(target_index) != Some(source) { + return Err("Invalid target index") + } + + // Ok - all valid. + + // update nominators_for + t.swap_remove(target_index); + >::insert(&target, t); + + // update nominating + >::remove(source); + + // update bondage + >::insert(aux.ref_into(), Self::current_era() + Self::bonding_duration()); + Ok(()) + } + // PRIV DISPATCH /// Set the number of sessions in an era. @@ -327,8 +413,7 @@ impl Module { /// Force there to be a new era. This also forces a new session immediately after. fn force_new_era() -> Result { - Self::new_era(); - >::rotate_session(); + >::rotate_session(false); Ok(()) } @@ -379,6 +464,17 @@ impl Module { } } + /// Adds up to `value` to the free balance of `who`. + /// + /// If `who` doesn't exist, nothing is done and an Err returned. + pub fn reward(who: &T::AccountId, value: T::Balance) -> Result { + if Self::voting_balance(who).is_zero() { + return Err("beneficiary account must pre-exist"); + } + Self::set_free_balance(who, Self::free_balance(who) + value); + Ok(()) + } + /// Moves `value` from balance to reserved balance. /// /// If the free balance is lower than `value`, then no funds will be moved and an `Err` will @@ -453,14 +549,54 @@ impl Module { } } - /// Hook to be called after to transaction processing. - pub fn check_new_era() { - // check block number and call new_era if necessary. - if (>::block_number() - Self::last_era_length_change()) % Self::era_length() == Zero::zero() { + /// Session has just changed. We need to determine whether we pay a reward, slash and/or + /// move to a new era. + fn new_session(normal_rotation: bool, actual_elapsed: T::Moment) { + let session_index = >::current_index(); + + if normal_rotation { + // reward + let ideal_elapsed = >::ideal_session_duration(); + let percent: usize = (T::Moment::sa(65536usize) * ideal_elapsed.clone() / actual_elapsed.max(ideal_elapsed)).as_(); + let reward = Self::session_reward() * T::Balance::sa(percent) / T::Balance::sa(65536usize); + // apply good session reward + for v in >::validators().iter() { + let noms = Self::current_nominators_for(v); + let total = noms.iter().map(Self::voting_balance).fold(Self::voting_balance(v), |acc, x| acc + x); + if !total.is_zero() { + let safe_mul_rational = |b| b * reward / total;// TODO: avoid overflow + for n in noms.iter() { + let _ = Self::reward(n, safe_mul_rational(Self::voting_balance(n))); + } + let _ = Self::reward(v, safe_mul_rational(Self::voting_balance(v))); + } + } + } else { + // slash + let early_era_slash = Self::early_era_slash(); + for v in >::validators().iter() { + if let Some(rem) = Self::slash(v, early_era_slash) { + let noms = Self::current_nominators_for(v); + let total = noms.iter().map(Self::voting_balance).fold(Zero::zero(), |acc, x| acc + x); + for n in noms.iter() { + //let r = Self::voting_balance(n) * reward / total; // correct formula, but might overflow with large slash * total. + let quant = T::Balance::sa(1usize << 31); + let s = (Self::voting_balance(n) * quant / total) * rem / quant; // avoid overflow by using quant as a denominator. + let _ = Self::slash(n, s); // best effort - not much that can be done on fail. + } + } + } + } + if ((session_index - Self::last_era_length_change()) % Self::sessions_per_era()).is_zero() || !normal_rotation { Self::new_era(); } } + /// Balance of a (potential) validator that includes all nominators. + fn nomination_balance(who: &T::AccountId) -> T::Balance { + Self::nominators_for(who).iter().map(Self::voting_balance).fold(Zero::zero(), |acc, x| acc + x) + } + /// The era has changed - enact new staking set. /// /// NOTE: This always happens immediately before a session change to ensure that new validators @@ -473,7 +609,7 @@ impl Module { if let Some(next_spe) = Self::next_sessions_per_era() { if next_spe != Self::sessions_per_era() { >::put(&next_spe); - >::put(&>::block_number()); + >::put(&>::current_index()); } } @@ -481,17 +617,30 @@ impl Module { // combination of validators, then use session::internal::set_validators(). // for now, this just orders would-be stakers by their balances and chooses the top-most // >::get() of them. + // TODO: this is not sound. this should be moved to an off-chain solution mechanism. let mut intentions = >::get() .into_iter() - .map(|v| (Self::voting_balance(&v), v)) + .map(|v| (Self::voting_balance(&v) + Self::nomination_balance(&v), v)) .collect::>(); intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1)); - >::set_validators( - &intentions.into_iter() + + >::put( + if intentions.len() > 0 { + let i = (>::get() as usize).min(intentions.len() - 1); + intentions[i].0.clone() + } else { Zero::zero() } + ); + let vals = &intentions.into_iter() .map(|(_, v)| v) .take(>::get() as usize) - .collect::>() - ); + .collect::>(); + for v in >::validators().iter() { + >::remove(v); + } + for v in vals.iter() { + >::insert(v, Self::nominators_for(v)); + } + >::set_validators(vals); } fn enum_set_size() -> T::AccountIndex { @@ -601,203 +750,7 @@ impl Module { >::remove(who); } } -} - -impl Executable for Module { - fn execute() { - Self::check_new_era(); - } -} - -impl AuxLookup for Module { - type Source = address::Address; - type Target = T::AccountId; - fn lookup(a: Self::Source) -> result::Result { - match a { - address::Address::Id(i) => Ok(i), - address::Address::Index(i) => >::lookup_index(i).ok_or("invalid account index"), - } - } -} - -// Each identity's stake may be in one of three bondage states, given by an integer: -// - n | n <= >::get(): inactive: free to be transferred. -// - ~0: active: currently representing a validator. -// - n | n > >::get(): deactivating: recently representing a validator and not yet -// ready for transfer. - -struct ChangeEntry { - balance: Option, - code: Option>, - storage: BTreeMap, Option>>, -} - -// Cannot derive(Default) since it erroneously bounds T by Default. -impl Default for ChangeEntry { - fn default() -> Self { - ChangeEntry { - balance: Default::default(), - code: Default::default(), - storage: Default::default(), - } - } -} - -impl ChangeEntry { - pub fn contract_created(b: T::Balance, c: Vec) -> Self { - ChangeEntry { balance: Some(b), code: Some(c), storage: Default::default() } - } - pub fn balance_changed(b: T::Balance) -> Self { - ChangeEntry { balance: Some(b), code: None, storage: Default::default() } - } -} - -type State = BTreeMap<::AccountId, ChangeEntry>; - -trait AccountDb { - fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option>; - fn get_code(&self, account: &T::AccountId) -> Vec; - fn get_balance(&self, account: &T::AccountId) -> T::Balance; - - fn merge(&mut self, state: State); -} - -struct DirectAccountDb; -impl AccountDb for DirectAccountDb { - fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { - >::get(&(account.clone(), location.to_vec())) - } - fn get_code(&self, account: &T::AccountId) -> Vec { - >::get(account) - } - fn get_balance(&self, account: &T::AccountId) -> T::Balance { - >::get(account) - } - fn merge(&mut self, s: State) { - let ed = >::existential_deposit(); - for (address, changed) in s.into_iter() { - if let Some(balance) = changed.balance { - // If the balance is too low, then the account is reaped. - // NOTE: There are two balances for every account: `reserved_balance` and - // `free_balance`. This contract subsystem only cares about the latter: whenever - // the term "balance" is used *here* it should be assumed to mean "free balance" - // in the rest of the module. - // Free balance can never be less than ED. If that happens, it gets reduced to zero - // and the account information relevant to this subsystem is deleted (i.e. the - // account is reaped). - // NOTE: This is orthogonal to the `Bondage` value that an account has, a high - // value of which makes even the `free_balance` unspendable. - // TODO: enforce this for the other balance-altering functions. - if balance < ed { - >::on_free_too_low(&address); - continue; - } else { - if !>::exists(&address) { - let outcome = >::new_account(&address, balance); - let credit = match outcome { - NewAccountOutcome::GoodHint => balance + >::reclaim_rebate(), - _ => balance, - }; - >::insert(&address, credit); - } else { - >::insert(&address, balance); - } - } - } - if let Some(code) = changed.code { - >::insert(&address, &code); - } - for (k, v) in changed.storage.into_iter() { - if let Some(value) = v { - >::insert((address.clone(), k), &value); - } else { - >::remove((address.clone(), k)); - } - } - } - } -} - -struct OverlayAccountDb<'a, T: Trait + 'a> { - local: RefCell>, - underlying: &'a AccountDb, -} -impl<'a, T: Trait> OverlayAccountDb<'a, T> { - fn new(underlying: &'a AccountDb) -> OverlayAccountDb<'a, T> { - OverlayAccountDb { - local: RefCell::new(State::new()), - underlying, - } - } - - fn into_state(self) -> State { - self.local.into_inner() - } - - fn set_storage(&mut self, account: &T::AccountId, location: Vec, value: Option>) { - self.local - .borrow_mut() - .entry(account.clone()) - .or_insert(Default::default()) - .storage - .insert(location, value); - } - fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) { - self.local - .borrow_mut() - .entry(account.clone()) - .or_insert(Default::default()) - .balance = Some(balance); - } -} - -impl<'a, T: Trait> AccountDb for OverlayAccountDb<'a, T> { - fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { - self.local - .borrow() - .get(account) - .and_then(|a| a.storage.get(location)) - .cloned() - .unwrap_or_else(|| self.underlying.get_storage(account, location)) - } - fn get_code(&self, account: &T::AccountId) -> Vec { - self.local - .borrow() - .get(account) - .and_then(|a| a.code.clone()) - .unwrap_or_else(|| self.underlying.get_code(account)) - } - fn get_balance(&self, account: &T::AccountId) -> T::Balance { - self.local - .borrow() - .get(account) - .and_then(|a| a.balance) - .unwrap_or_else(|| self.underlying.get_balance(account)) - } - fn merge(&mut self, s: State) { - let mut local = self.local.borrow_mut(); - - for (address, changed) in s.into_iter() { - match local.entry(address) { - Entry::Occupied(e) => { - let mut value = e.into_mut(); - if changed.balance.is_some() { - value.balance = changed.balance; - } - if changed.code.is_some() { - value.code = changed.code; - } - value.storage.extend(changed.storage.into_iter()); - } - Entry::Vacant(e) => { - e.insert(changed); - } - } - } - } -} -impl Module { fn effect_create>( transactor: &T::AccountId, code: &[u8], @@ -894,32 +847,24 @@ impl Module { } } -struct StakingExt<'a, 'b: 'a, T: Trait + 'b> { - account_db: &'a mut OverlayAccountDb<'b, T>, - account: T::AccountId, +impl Executable for Module { + fn execute() { + } } -impl<'a, 'b: 'a, T: Trait> contract::Ext for StakingExt<'a, 'b, T> { - type AccountId = T::AccountId; - type Balance = T::Balance; - fn get_storage(&self, key: &[u8]) -> Option> { - self.account_db.get_storage(&self.account, key) - } - fn set_storage(&mut self, key: &[u8], value: Option>) { - self.account_db.set_storage(&self.account, key.to_vec(), value); +impl OnSessionChange for Module { + fn on_session_change(normal_rotation: bool, elapsed: T::Moment) { + Self::new_session(normal_rotation, elapsed); } - fn create(&mut self, code: &[u8], value: Self::Balance) { - if let Ok(Some(commit_state)) = - Module::::effect_create(&self.account, code, value, self.account_db) - { - self.account_db.merge(commit_state); - } - } - fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) { - if let Ok(Some(commit_state)) = - Module::::effect_transfer(&self.account, to, value, self.account_db) - { - self.account_db.merge(commit_state); +} + +impl AuxLookup for Module { + type Source = address::Address; + type Target = T::AccountId; + fn lookup(a: Self::Source) -> result::Result { + match a { + address::Address::Id(i) => Ok(i), + address::Address::Index(i) => >::lookup_index(i).ok_or("invalid account index"), } } } @@ -935,134 +880,3 @@ impl MakePayment for Module { Ok(()) } } - -#[cfg(any(feature = "std", test))] -pub struct DummyContractAddressFor; -#[cfg(any(feature = "std", test))] -impl ContractAddressFor for DummyContractAddressFor { - fn contract_address_for(_code: &[u8], origin: &u64) -> u64 { - origin + 1 - } -} - -#[cfg(any(feature = "std", test))] -pub struct GenesisConfig { - pub sessions_per_era: T::BlockNumber, - pub current_era: T::BlockNumber, - pub balances: Vec<(T::AccountId, T::Balance)>, - pub intentions: Vec, - pub validator_count: u64, - pub bonding_duration: T::BlockNumber, - pub transaction_base_fee: T::Balance, - pub transaction_byte_fee: T::Balance, - pub transfer_fee: T::Balance, - pub creation_fee: T::Balance, - pub contract_fee: T::Balance, - pub reclaim_rebate: T::Balance, - pub existential_deposit: T::Balance, -} - -#[cfg(any(feature = "std", test))] -impl GenesisConfig where T::AccountId: From { - pub fn simple() -> Self { - GenesisConfig { - sessions_per_era: T::BlockNumber::sa(2), - current_era: T::BlockNumber::sa(0), - balances: vec![(T::AccountId::from(1), T::Balance::sa(111))], - intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)], - validator_count: 3, - bonding_duration: T::BlockNumber::sa(0), - transaction_base_fee: T::Balance::sa(0), - transaction_byte_fee: T::Balance::sa(0), - transfer_fee: T::Balance::sa(0), - creation_fee: T::Balance::sa(0), - contract_fee: T::Balance::sa(0), - existential_deposit: T::Balance::sa(0), - reclaim_rebate: T::Balance::sa(0), - } - } - - pub fn extended() -> Self { - GenesisConfig { - sessions_per_era: T::BlockNumber::sa(3), - current_era: T::BlockNumber::sa(1), - balances: vec![ - (T::AccountId::from(1), T::Balance::sa(10)), - (T::AccountId::from(2), T::Balance::sa(20)), - (T::AccountId::from(3), T::Balance::sa(30)), - (T::AccountId::from(4), T::Balance::sa(40)), - (T::AccountId::from(5), T::Balance::sa(50)), - (T::AccountId::from(6), T::Balance::sa(60)), - (T::AccountId::from(7), T::Balance::sa(1)) - ], - intentions: vec![T::AccountId::from(1), T::AccountId::from(2), T::AccountId::from(3)], - validator_count: 3, - bonding_duration: T::BlockNumber::sa(0), - transaction_base_fee: T::Balance::sa(1), - transaction_byte_fee: T::Balance::sa(0), - transfer_fee: T::Balance::sa(0), - creation_fee: T::Balance::sa(0), - contract_fee: T::Balance::sa(0), - existential_deposit: T::Balance::sa(0), - reclaim_rebate: T::Balance::sa(0), - } - } -} - -#[cfg(any(feature = "std", test))] -impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - sessions_per_era: T::BlockNumber::sa(1000), - current_era: T::BlockNumber::sa(0), - balances: vec![], - intentions: vec![], - validator_count: 0, - bonding_duration: T::BlockNumber::sa(1000), - transaction_base_fee: T::Balance::sa(0), - transaction_byte_fee: T::Balance::sa(0), - transfer_fee: T::Balance::sa(0), - creation_fee: T::Balance::sa(0), - contract_fee: T::Balance::sa(0), - existential_deposit: T::Balance::sa(0), - reclaim_rebate: T::Balance::sa(0), - } - } -} - -#[cfg(any(feature = "std", test))] -impl primitives::BuildStorage for GenesisConfig { - fn build_storage(self) -> runtime_io::TestExternalities { - use runtime_io::twox_128; - use codec::Slicable; - - let total_stake: T::Balance = self.balances.iter().fold(Zero::zero(), |acc, &(_, n)| acc + n); - - let mut r: runtime_io::TestExternalities = map![ - twox_128(>::key()).to_vec() => T::AccountIndex::sa(self.balances.len() / ENUM_SET_SIZE).encode(), - twox_128(>::key()).to_vec() => self.intentions.encode(), - twox_128(>::key()).to_vec() => self.sessions_per_era.encode(), - twox_128(>::key()).to_vec() => self.validator_count.encode(), - twox_128(>::key()).to_vec() => self.bonding_duration.encode(), - twox_128(>::key()).to_vec() => self.transaction_base_fee.encode(), - twox_128(>::key()).to_vec() => self.transaction_byte_fee.encode(), - twox_128(>::key()).to_vec() => self.transfer_fee.encode(), - twox_128(>::key()).to_vec() => self.creation_fee.encode(), - twox_128(>::key()).to_vec() => self.contract_fee.encode(), - twox_128(>::key()).to_vec() => self.existential_deposit.encode(), - twox_128(>::key()).to_vec() => self.reclaim_rebate.encode(), - twox_128(>::key()).to_vec() => self.current_era.encode(), - twox_128(>::key()).to_vec() => total_stake.encode() - ]; - - let ids: Vec<_> = self.balances.iter().map(|x| x.0.clone()).collect(); - for i in 0..(ids.len() + ENUM_SET_SIZE - 1) / ENUM_SET_SIZE { - r.insert(twox_128(&>::key_for(T::AccountIndex::sa(i))).to_vec(), - ids[i * ENUM_SET_SIZE..ids.len().min((i + 1) * ENUM_SET_SIZE)].to_owned().encode()); - } - for (who, value) in self.balances.into_iter() { - r.insert(twox_128(&>::key_for(who)).to_vec(), value.encode()); - } - r - } -} diff --git a/substrate/runtime/staking/src/mock.rs b/substrate/runtime/staking/src/mock.rs index c5a3e3e5af8d7..3facc46bd590d 100644 --- a/substrate/runtime/staking/src/mock.rs +++ b/substrate/runtime/staking/src/mock.rs @@ -23,7 +23,8 @@ use primitives::traits::{HasPublicAux, Identity}; use primitives::testing::{Digest, Header}; use substrate_primitives::H256; use runtime_io; -use {DummyContractAddressFor, GenesisConfig, Module, Trait, consensus, session, system}; +use {GenesisConfig, Module, Trait, consensus, session, system, timestamp}; +use super::DummyContractAddressFor; // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] @@ -46,6 +47,11 @@ impl system::Trait for Test { } impl session::Trait for Test { type ConvertAccountIdToSessionKey = Identity; + type OnSessionChange = Staking; +} +impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; } impl Trait for Test { type Balance = u64; @@ -53,7 +59,7 @@ impl Trait for Test { type AccountIndex = u64; } -pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool) -> runtime_io::TestExternalities { +pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool, reward: u64) -> runtime_io::TestExternalities { let mut t = system::GenesisConfig::::default().build_storage(); let balance_factor = if ext_deposit > 0 { 256 @@ -67,11 +73,20 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64 t.extend(session::GenesisConfig::{ session_length, validators: vec![10, 20], + broken_percent_late: 30, }.build_storage()); t.extend(GenesisConfig::{ sessions_per_era, current_era, - balances: if monied { vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor)] } else { vec![] }, + balances: if monied { + if reward > 0 { + vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor), (10, balance_factor), (20, balance_factor)] + } else { + vec![(1, 10 * balance_factor), (2, 20 * balance_factor), (3, 30 * balance_factor), (4, 40 * balance_factor)] + } + } else { + vec![(10, balance_factor), (20, balance_factor)] + }, intentions: vec![], validator_count: 2, bonding_duration: 3, @@ -82,10 +97,16 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64 creation_fee: 0, contract_fee: 0, reclaim_rebate: 0, + session_reward: reward, + early_era_slash: if monied { 20 } else { 0 }, + }.build_storage()); + t.extend(timestamp::GenesisConfig::{ + period: 5 }.build_storage()); t } pub type System = system::Module; pub type Session = session::Module; +pub type Timestamp = timestamp::Module; pub type Staking = Module; diff --git a/substrate/runtime/staking/src/tests.rs b/substrate/runtime/staking/src/tests.rs index 08479c0696ec7..f19e84079bd63 100644 --- a/substrate/runtime/staking/src/tests.rs +++ b/substrate/runtime/staking/src/tests.rs @@ -16,13 +16,88 @@ //! Tests for the module. +#![cfg(test)] + use super::*; use runtime_io::with_externalities; -use mock::*; +use mock::{Session, Staking, System, Timestamp, Test, new_test_ext}; + +#[test] +fn reward_should_work() { + with_externalities(&mut new_test_ext(0, 3, 3, 0, true, 10), || { + assert_eq!(Staking::voting_balance(&10), 1); + assert_ok!(Staking::reward(&10, 10)); + assert_eq!(Staking::voting_balance(&10), 11); + }); +} + +#[test] +fn rewards_should_work() { + with_externalities(&mut new_test_ext(0, 3, 3, 0, true, 10), || { + assert_eq!(Staking::era_length(), 9); + assert_eq!(Staking::sessions_per_era(), 3); + assert_eq!(Staking::last_era_length_change(), 0); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 0); + assert_eq!(Staking::voting_balance(&10), 1); + + System::set_block_number(3); + Timestamp::set_timestamp(15); // on time. + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 1); + assert_eq!(Staking::voting_balance(&10), 11); + System::set_block_number(6); + Timestamp::set_timestamp(31); // a little late + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 2); + assert_eq!(Staking::voting_balance(&10), 20); // less reward + System::set_block_number(9); + Timestamp::set_timestamp(50); // very late + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 1); + assert_eq!(Session::current_index(), 3); + assert_eq!(Staking::voting_balance(&10), 27); // much less reward + }); +} + +#[test] +fn slashing_should_work() { + with_externalities(&mut new_test_ext(0, 3, 3, 0, true, 10), || { + assert_eq!(Staking::era_length(), 9); + assert_eq!(Staking::sessions_per_era(), 3); + assert_eq!(Staking::last_era_length_change(), 0); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 0); + assert_eq!(Staking::voting_balance(&10), 1); + + System::set_block_number(3); + Timestamp::set_timestamp(15); // on time. + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 1); + assert_eq!(Staking::voting_balance(&10), 11); + + System::set_block_number(6); + Timestamp::set_timestamp(30); // on time. + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 2); + assert_eq!(Staking::voting_balance(&10), 21); + + System::set_block_number(7); + Timestamp::set_timestamp(100); // way too late - early exit. + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 1); + assert_eq!(Session::current_index(), 3); + assert_eq!(Staking::voting_balance(&10), 1); + }); +} #[test] fn indexing_lookup_should_work() { - with_externalities(&mut new_test_ext(10, 1, 2, 0, true), || { + with_externalities(&mut new_test_ext(10, 1, 2, 0, true, 0), || { assert_eq!(Staking::lookup_index(0), Some(1)); assert_eq!(Staking::lookup_index(1), Some(2)); assert_eq!(Staking::lookup_index(2), Some(3)); @@ -33,7 +108,7 @@ fn indexing_lookup_should_work() { #[test] fn default_indexing_on_new_accounts_should_work() { - with_externalities(&mut new_test_ext(10, 1, 2, 0, true), || { + with_externalities(&mut new_test_ext(10, 1, 2, 0, true, 0), || { assert_eq!(Staking::lookup_index(4), None); assert_ok!(Staking::transfer(&1, 5.into(), 10)); assert_eq!(Staking::lookup_index(4), Some(5)); @@ -42,7 +117,7 @@ fn default_indexing_on_new_accounts_should_work() { #[test] fn dust_account_removal_should_work() { - with_externalities(&mut new_test_ext(256 * 10, 1, 2, 0, true), || { + with_externalities(&mut new_test_ext(256 * 10, 1, 2, 0, true, 0), || { System::inc_account_nonce(&2); assert_eq!(System::account_nonce(&2), 1); assert_eq!(Staking::voting_balance(&2), 256 * 20); @@ -56,7 +131,7 @@ fn dust_account_removal_should_work() { #[test] fn reclaim_indexing_on_new_accounts_should_work() { - with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true), || { + with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true, 0), || { assert_eq!(Staking::lookup_index(1), Some(2)); assert_eq!(Staking::lookup_index(4), None); assert_eq!(Staking::voting_balance(&2), 256 * 20); @@ -72,7 +147,7 @@ fn reclaim_indexing_on_new_accounts_should_work() { #[test] fn reserved_balance_should_prevent_reclaim_count() { - with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true), || { + with_externalities(&mut new_test_ext(256 * 1, 1, 2, 0, true, 0), || { System::inc_account_nonce(&2); assert_eq!(Staking::lookup_index(1), Some(2)); assert_eq!(Staking::lookup_index(4), None); @@ -100,7 +175,7 @@ fn reserved_balance_should_prevent_reclaim_count() { #[test] fn staking_should_work() { - with_externalities(&mut new_test_ext(0, 1, 2, 0, true), || { + with_externalities(&mut new_test_ext(0, 1, 2, 0, true, 0), || { assert_eq!(Staking::era_length(), 2); assert_eq!(Staking::validator_count(), 2); assert_eq!(Staking::bonding_duration(), 3); @@ -111,66 +186,177 @@ fn staking_should_work() { assert_ok!(Staking::stake(&1)); assert_ok!(Staking::stake(&2)); assert_ok!(Staking::stake(&4)); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 0); assert_eq!(Session::validators(), vec![10, 20]); // Block 2: New validator set now. System::set_block_number(2); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 1); assert_eq!(Session::validators(), vec![4, 2]); // Block 3: Unstake highest, introduce another staker. No change yet. System::set_block_number(3); assert_ok!(Staking::stake(&3)); - assert_ok!(Staking::unstake(&4)); - Staking::check_new_era(); + assert_ok!(Staking::unstake(&4, Staking::intentions().iter().position(|&x| x == 4).unwrap() as u32)); + assert_eq!(Staking::current_era(), 1); + Session::check_rotate_session(); // Block 4: New era - validators change. System::set_block_number(4); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 2); assert_eq!(Session::validators(), vec![3, 2]); // Block 5: Transfer stake from highest to lowest. No change yet. System::set_block_number(5); assert_ok!(Staking::transfer(&4, 1.into(), 40)); - Staking::check_new_era(); + Session::check_rotate_session(); // Block 6: Lowest now validator. System::set_block_number(6); - Staking::check_new_era(); + Session::check_rotate_session(); assert_eq!(Session::validators(), vec![1, 3]); // Block 7: Unstake three. No change yet. System::set_block_number(7); - assert_ok!(Staking::unstake(&3)); - Staking::check_new_era(); + assert_ok!(Staking::unstake(&3, Staking::intentions().iter().position(|&x| x == 3).unwrap() as u32)); + Session::check_rotate_session(); assert_eq!(Session::validators(), vec![1, 3]); // Block 8: Back to one and two. System::set_block_number(8); - Staking::check_new_era(); + Session::check_rotate_session(); assert_eq!(Session::validators(), vec![1, 2]); }); } +#[test] +fn nominating_and_rewards_should_work() { + with_externalities(&mut new_test_ext(0, 1, 1, 0, true, 10), || { + assert_eq!(Staking::era_length(), 1); + assert_eq!(Staking::validator_count(), 2); + assert_eq!(Staking::bonding_duration(), 3); + assert_eq!(Session::validators(), vec![10, 20]); + + System::set_block_number(1); + assert_ok!(Staking::stake(&1)); + assert_ok!(Staking::stake(&2)); + assert_ok!(Staking::stake(&3)); + assert_ok!(Staking::nominate(&4, 1.into())); + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 1); + assert_eq!(Session::validators(), vec![1, 3]); // 4 + 1, 3 + assert_eq!(Staking::voting_balance(&1), 10); + assert_eq!(Staking::voting_balance(&2), 20); + assert_eq!(Staking::voting_balance(&3), 30); + assert_eq!(Staking::voting_balance(&4), 40); + + System::set_block_number(2); + assert_ok!(Staking::unnominate(&4, 0)); + Session::check_rotate_session(); + assert_eq!(Staking::current_era(), 2); + assert_eq!(Session::validators(), vec![3, 2]); + assert_eq!(Staking::voting_balance(&1), 12); + assert_eq!(Staking::voting_balance(&2), 20); + assert_eq!(Staking::voting_balance(&3), 40); + assert_eq!(Staking::voting_balance(&4), 48); + + System::set_block_number(3); + assert_ok!(Staking::stake(&4)); + assert_ok!(Staking::unstake(&3, Staking::intentions().iter().position(|&x| x == 3).unwrap() as u32)); + assert_ok!(Staking::nominate(&3, 1.into())); + Session::check_rotate_session(); + assert_eq!(Session::validators(), vec![1, 4]); + assert_eq!(Staking::voting_balance(&1), 12); + assert_eq!(Staking::voting_balance(&2), 30); + assert_eq!(Staking::voting_balance(&3), 50); + assert_eq!(Staking::voting_balance(&4), 48); + + System::set_block_number(4); + Session::check_rotate_session(); + assert_eq!(Staking::voting_balance(&1), 13); + assert_eq!(Staking::voting_balance(&2), 30); + assert_eq!(Staking::voting_balance(&3), 58); + assert_eq!(Staking::voting_balance(&4), 58); + }); +} + +#[test] +fn nominating_slashes_should_work() { + with_externalities(&mut new_test_ext(0, 2, 2, 0, true, 10), || { + assert_eq!(Staking::era_length(), 4); + assert_eq!(Staking::validator_count(), 2); + assert_eq!(Staking::bonding_duration(), 3); + assert_eq!(Session::validators(), vec![10, 20]); + + System::set_block_number(2); + Session::check_rotate_session(); + + Timestamp::set_timestamp(15); + System::set_block_number(4); + assert_ok!(Staking::stake(&1)); + assert_ok!(Staking::stake(&3)); + assert_ok!(Staking::nominate(&2, 3.into())); + assert_ok!(Staking::nominate(&4, 1.into())); + Session::check_rotate_session(); + + assert_eq!(Staking::current_era(), 1); + assert_eq!(Session::validators(), vec![1, 3]); // 1 + 4, 3 + 2 + assert_eq!(Staking::voting_balance(&1), 10); + assert_eq!(Staking::voting_balance(&2), 20); + assert_eq!(Staking::voting_balance(&3), 30); + assert_eq!(Staking::voting_balance(&4), 40); + + System::set_block_number(5); + Timestamp::set_timestamp(100); // late + assert_eq!(Session::blocks_remaining(), 1); + assert!(Session::broken_validation()); + Session::check_rotate_session(); + + assert_eq!(Staking::current_era(), 2); + assert_eq!(Staking::voting_balance(&1), 0); + assert_eq!(Staking::voting_balance(&2), 20); + assert_eq!(Staking::voting_balance(&3), 10); + assert_eq!(Staking::voting_balance(&4), 30); + }); +} + +#[test] +fn double_staking_should_fail() { + with_externalities(&mut new_test_ext(0, 1, 2, 0, true, 0), || { + System::set_block_number(1); + assert_ok!(Staking::stake(&1)); + assert_noop!(Staking::stake(&1), "Cannot stake if already staked."); + assert_noop!(Staking::nominate(&1, 1.into()), "Cannot nominate if already staked."); + assert_ok!(Staking::nominate(&2, 1.into())); + assert_noop!(Staking::stake(&2), "Cannot stake if already nominating."); + assert_noop!(Staking::nominate(&2, 1.into()), "Cannot nominate if already nominating."); + }); +} + #[test] fn staking_eras_work() { - with_externalities(&mut new_test_ext(0, 1, 2, 0, true), || { + with_externalities(&mut new_test_ext(0, 1, 2, 0, true, 0), || { assert_eq!(Staking::era_length(), 2); assert_eq!(Staking::sessions_per_era(), 2); assert_eq!(Staking::last_era_length_change(), 0); assert_eq!(Staking::current_era(), 0); + assert_eq!(Session::current_index(), 0); // Block 1: No change. System::set_block_number(1); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 1); assert_eq!(Staking::sessions_per_era(), 2); assert_eq!(Staking::last_era_length_change(), 0); assert_eq!(Staking::current_era(), 0); // Block 2: Simple era change. System::set_block_number(2); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 2); assert_eq!(Staking::sessions_per_era(), 2); assert_eq!(Staking::last_era_length_change(), 0); assert_eq!(Staking::current_era(), 1); @@ -178,35 +364,41 @@ fn staking_eras_work() { // Block 3: Schedule an era length change; no visible changes. System::set_block_number(3); assert_ok!(Staking::set_sessions_per_era(3)); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 3); assert_eq!(Staking::sessions_per_era(), 2); assert_eq!(Staking::last_era_length_change(), 0); assert_eq!(Staking::current_era(), 1); // Block 4: Era change kicks in. System::set_block_number(4); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 4); assert_eq!(Staking::sessions_per_era(), 3); assert_eq!(Staking::last_era_length_change(), 4); assert_eq!(Staking::current_era(), 2); // Block 5: No change. System::set_block_number(5); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 5); assert_eq!(Staking::sessions_per_era(), 3); assert_eq!(Staking::last_era_length_change(), 4); assert_eq!(Staking::current_era(), 2); // Block 6: No change. System::set_block_number(6); - Staking::check_new_era(); + assert!(!Session::broken_validation()); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 6); assert_eq!(Staking::sessions_per_era(), 3); assert_eq!(Staking::last_era_length_change(), 4); assert_eq!(Staking::current_era(), 2); // Block 7: Era increment. System::set_block_number(7); - Staking::check_new_era(); + Session::check_rotate_session(); + assert_eq!(Session::current_index(), 7); assert_eq!(Staking::sessions_per_era(), 3); assert_eq!(Staking::last_era_length_change(), 4); assert_eq!(Staking::current_era(), 3); @@ -215,7 +407,7 @@ fn staking_eras_work() { #[test] fn staking_balance_works() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 42); assert_eq!(Staking::free_balance(&1), 42); assert_eq!(Staking::reserved_balance(&1), 0); @@ -228,7 +420,7 @@ fn staking_balance_works() { #[test] fn staking_balance_transfer_works() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::transfer(&1, 2.into(), 69)); assert_eq!(Staking::voting_balance(&1), 42); @@ -238,7 +430,7 @@ fn staking_balance_transfer_works() { #[test] fn staking_balance_transfer_when_bonded_should_not_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::stake(&1)); assert_noop!(Staking::transfer(&1, 2.into(), 69), "bondage too high to send value"); @@ -247,7 +439,7 @@ fn staking_balance_transfer_when_bonded_should_not_work() { #[test] fn reserving_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_eq!(Staking::voting_balance(&1), 111); @@ -264,7 +456,7 @@ fn reserving_balance_should_work() { #[test] fn staking_balance_transfer_when_reserved_should_not_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::reserve(&1, 69)); assert_noop!(Staking::transfer(&1, 2.into(), 69), "balance too low to send value"); @@ -273,7 +465,7 @@ fn staking_balance_transfer_when_reserved_should_not_work() { #[test] fn deducting_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::reserve(&1, 69)); assert_eq!(Staking::free_balance(&1), 42); @@ -282,7 +474,7 @@ fn deducting_balance_should_work() { #[test] fn deducting_balance_when_bonded_should_not_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); >::insert(1, 2); System::set_block_number(1); @@ -293,7 +485,7 @@ fn deducting_balance_when_bonded_should_not_work() { #[test] fn refunding_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 42); >::insert(1, 69); Staking::unreserve(&1, 69); @@ -304,7 +496,7 @@ fn refunding_balance_should_work() { #[test] fn slashing_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::reserve(&1, 69)); assert!(Staking::slash(&1, 69).is_none()); @@ -315,7 +507,7 @@ fn slashing_balance_should_work() { #[test] fn slashing_incomplete_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 42); assert_ok!(Staking::reserve(&1, 21)); assert!(Staking::slash(&1, 69).is_some()); @@ -326,7 +518,7 @@ fn slashing_incomplete_balance_should_work() { #[test] fn unreserving_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::reserve(&1, 111)); Staking::unreserve(&1, 42); @@ -337,7 +529,7 @@ fn unreserving_balance_should_work() { #[test] fn slashing_reserved_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::reserve(&1, 111)); assert!(Staking::slash_reserved(&1, 42).is_none()); @@ -348,7 +540,7 @@ fn slashing_reserved_balance_should_work() { #[test] fn slashing_incomplete_reserved_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::reserve(&1, 42)); assert!(Staking::slash_reserved(&1, 69).is_some()); @@ -359,7 +551,7 @@ fn slashing_incomplete_reserved_balance_should_work() { #[test] fn transferring_reserved_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 110); >::insert(2, 1); assert_ok!(Staking::reserve(&1, 110)); @@ -373,7 +565,7 @@ fn transferring_reserved_balance_should_work() { #[test] fn transferring_reserved_balance_to_nonexistent_should_fail() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 111); assert_ok!(Staking::reserve(&1, 111)); assert_noop!(Staking::transfer_reserved(&1, &2, 42), "beneficiary account must pre-exist"); @@ -382,7 +574,7 @@ fn transferring_reserved_balance_to_nonexistent_should_fail() { #[test] fn transferring_incomplete_reserved_balance_should_work() { - with_externalities(&mut new_test_ext(0, 1, 3, 1, false), || { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { >::insert(1, 110); >::insert(2, 1); assert_ok!(Staking::reserve(&1, 41)); diff --git a/substrate/runtime/timestamp/Cargo.toml b/substrate/runtime/timestamp/Cargo.toml index d90f961b6c69b..1b5637a2c33a6 100644 --- a/substrate/runtime/timestamp/Cargo.toml +++ b/substrate/runtime/timestamp/Cargo.toml @@ -14,6 +14,7 @@ substrate-runtime-primitives = { path = "../primitives", default_features = fals substrate-codec = { path = "../../codec", default_features = false } substrate-primitives = { path = "../../primitives", default_features = false } substrate-runtime-system = { path = "../system", default_features = false } +substrate-runtime-consensus = { path = "../consensus", default_features = false } [dev-dependencies] substrate-runtime-io = { path = "../../runtime-io", default_features = true } diff --git a/substrate/runtime/timestamp/src/lib.rs b/substrate/runtime/timestamp/src/lib.rs index 73f30c27aeb13..018f8c615f6b2 100644 --- a/substrate/runtime/timestamp/src/lib.rs +++ b/substrate/runtime/timestamp/src/lib.rs @@ -35,17 +35,20 @@ extern crate serde_derive; extern crate substrate_primitives; extern crate substrate_runtime_primitives as runtime_primitives; extern crate substrate_runtime_system as system; +extern crate substrate_runtime_consensus as consensus; extern crate substrate_codec as codec; use runtime_support::{StorageValue, Parameter}; use runtime_support::dispatch::Result; -use runtime_primitives::traits::{HasPublicAux, Executable, MaybeEmpty}; +use runtime_primitives::traits::{Executable, MaybeEmpty, SimpleArithmetic, As, Zero}; -pub trait Trait: HasPublicAux + system::Trait { +pub trait Trait: consensus::Trait where + ::PublicAux: MaybeEmpty +{ // the position of the required timestamp-set extrinsic. - const SET_POSITION: u32; + const TIMESTAMP_SET_POSITION: u32; - type Value: Parameter + Default; + type Moment: Parameter + Default + SimpleArithmetic + As; } decl_module! { @@ -53,36 +56,48 @@ decl_module! { #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum Call where aux: T::PublicAux { - fn set(aux, now: T::Value) -> Result = 0; + fn set(aux, now: T::Moment) -> Result = 0; } } decl_storage! { trait Store for Module; - pub Now get(now): b"tim:val" => required T::Value; + pub Now get(now): b"tim:val" => required T::Moment; + // The minimum (and advised) period between blocks. + pub BlockPeriod get(block_period): b"tim:block_period" => required T::Moment; // Did the timestamp get updated in this block? DidUpdate: b"tim:did" => default bool; } impl Module { - pub fn get() -> T::Value { - ::Now::get() + pub fn get() -> T::Moment { + Self::now() } /// Set the current time. - fn set(aux: &T::PublicAux, now: T::Value) -> Result { + fn set(aux: &T::PublicAux, now: T::Moment) -> Result { assert!(aux.is_empty()); assert!(!::DidUpdate::exists(), "Timestamp must be updated only once in the block"); assert!( - >::extrinsic_index() == T::SET_POSITION, + >::extrinsic_index() == T::TIMESTAMP_SET_POSITION, "Timestamp extrinsic must be at position {} in the block", - T::SET_POSITION + T::TIMESTAMP_SET_POSITION + ); + assert!( + Self::now().is_zero() || now >= Self::now() + Self::block_period(), + "Timestamp but increment by at least between sequential blocks" ); ::Now::put(now); ::DidUpdate::put(true); Ok(()) } + + /// Set the timestamp to something in particular. Only used for tests. + #[cfg(any(feature = "std", test))] + pub fn set_timestamp(now: T::Moment) { + ::Now::put(now); + } } impl Executable for Module { @@ -92,9 +107,17 @@ impl Executable for Module { } #[cfg(any(feature = "std", test))] -#[derive(Default)] pub struct GenesisConfig { - pub now: T::Value, + pub period: T::Moment, +} + +#[cfg(any(feature = "std", test))] +impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + period: T::Moment::sa(5), + } + } } #[cfg(any(feature = "std", test))] @@ -104,7 +127,8 @@ impl runtime_primitives::BuildStorage for GenesisConfig use runtime_io::twox_128; use codec::Slicable; map![ - twox_128(>::key()).to_vec() => self.now.encode() + twox_128(>::key()).to_vec() => self.period.encode(), + twox_128(>::key()).to_vec() => T::Moment::sa(0).encode() ] } } @@ -114,7 +138,6 @@ mod tests { use super::*; use runtime_io::with_externalities; - use runtime_support::storage::StorageValue; use substrate_primitives::H256; use runtime_primitives::BuildStorage; use runtime_primitives::traits::{HasPublicAux, BlakeTwo256}; @@ -134,21 +157,50 @@ mod tests { type AccountId = u64; type Header = Header; } + impl consensus::Trait for Test { + type PublicAux = u64; + type SessionKey = u64; + } impl Trait for Test { - const SET_POSITION: u32 = 0; - type Value = u64; + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; } type Timestamp = Module; #[test] fn timestamp_works() { let mut t = system::GenesisConfig::::default().build_storage(); - t.extend(GenesisConfig:: { now: 42 }.build_storage()); + t.extend(GenesisConfig:: { period: 0 }.build_storage()); with_externalities(&mut t, || { - assert_eq!(::Now::get(), 42); + Timestamp::set_timestamp(42); assert_ok!(Timestamp::aux_dispatch(Call::set(69), &0)); assert_eq!(Timestamp::now(), 69); }); } + + #[test] + #[should_panic(expected = "Timestamp must be updated only once in the block")] + fn double_timestamp_should_fail() { + let mut t = system::GenesisConfig::::default().build_storage(); + t.extend(GenesisConfig:: { period: 5 }.build_storage()); + + with_externalities(&mut t, || { + Timestamp::set_timestamp(42); + assert_ok!(Timestamp::aux_dispatch(Call::set(69), &0)); + let _ = Timestamp::aux_dispatch(Call::set(70), &0); + }); + } + + #[test] + #[should_panic(expected = "Timestamp but increment by at least between sequential blocks")] + fn block_period_is_enforced() { + let mut t = system::GenesisConfig::::default().build_storage(); + t.extend(GenesisConfig:: { period: 5 }.build_storage()); + + with_externalities(&mut t, || { + Timestamp::set_timestamp(42); + let _ = Timestamp::aux_dispatch(Call::set(46), &0); + }); + } } diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index e1bdf7abaf56e..abb496c04d9e9 100644 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm index 7f7322384f277..140933b9b2093 100755 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm differ