diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index c2d4a9eca0..c3fa82eab7 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -1800,7 +1800,6 @@ dependencies = [ "node-primitives 0.1.0", "node-runtime 0.1.0", "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 0.1.0", "sr-primitives 0.1.0", @@ -1808,7 +1807,6 @@ dependencies = [ "substrate-cli 0.3.0", "substrate-client 0.1.0", "substrate-consensus-aura 0.1.0", - "substrate-consensus-common 0.1.0", "substrate-finality-grandpa 0.1.0", "substrate-keystore 0.1.0", "substrate-network 0.1.0", @@ -1877,6 +1875,7 @@ dependencies = [ "sr-primitives 0.1.0", "sr-std 0.1.0", "sr-version 0.1.0", + "srml-aura 0.1.0", "srml-balances 0.1.0", "srml-consensus 0.1.0", "srml-contract 0.1.0", @@ -1892,7 +1891,7 @@ dependencies = [ "srml-treasury 0.1.0", "srml-upgrade-key 0.1.0", "substrate-client 0.1.0", - "substrate-finality-grandpa-primitives 0.1.0", + "substrate-consensus-aura-primitives 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", ] @@ -2800,6 +2799,27 @@ dependencies = [ "substrate-primitives 0.1.0", ] +[[package]] +name = "srml-aura" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 0.1.0", + "sr-primitives 0.1.0", + "sr-std 0.1.0", + "srml-consensus 0.1.0", + "srml-staking 0.1.0", + "srml-support 0.1.0", + "srml-system 0.1.0", + "srml-timestamp 0.1.0", + "substrate-primitives 0.1.0", +] + [[package]] name = "srml-balances" version = "0.1.0" @@ -3330,6 +3350,7 @@ dependencies = [ "srml-consensus 0.1.0", "srml-support 0.1.0", "substrate-client 0.1.0", + "substrate-consensus-aura-primitives 0.1.0", "substrate-consensus-common 0.1.0", "substrate-executor 0.1.0", "substrate-keyring 0.1.0", @@ -3340,6 +3361,19 @@ dependencies = [ "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "substrate-consensus-aura-primitives" +version = "0.1.0" +dependencies = [ + "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 0.1.0", + "sr-primitives 0.1.0", + "sr-version 0.1.0", + "srml-support 0.1.0", + "substrate-client 0.1.0", + "substrate-primitives 0.1.0", +] + [[package]] name = "substrate-consensus-common" version = "0.1.0" @@ -3713,6 +3747,7 @@ dependencies = [ "sr-version 0.1.0", "srml-support 0.1.0", "substrate-client 0.1.0", + "substrate-consensus-aura-primitives 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", ] diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index 085af16ce5..09e015dddc 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -44,6 +44,7 @@ members = [ "srml/support/procedural/tools", "srml/support/procedural/tools/derive", "srml/assets", + "srml/aura", "srml/balances", "srml/consensus", "srml/contract", diff --git a/substrate/core/client/db/src/lib.rs b/substrate/core/client/db/src/lib.rs index 420c1fca9d..72ee2c772e 100644 --- a/substrate/core/client/db/src/lib.rs +++ b/substrate/core/client/db/src/lib.rs @@ -642,6 +642,30 @@ fn apply_state_commit(transaction: &mut DBTransaction, commit: state_db::CommitS } } +impl client::backend::AuxStore for Backend where Block: BlockT { + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >(&self, insert: I, delete: D) -> client::error::Result<()> { + let mut transaction = DBTransaction::new(); + for (k, v) in insert { + transaction.put(columns::AUX, k, v); + } + for k in delete { + transaction.delete(columns::AUX, k); + } + self.storage.db.write(transaction).map_err(db_err)?; + Ok(()) + } + + fn get_aux(&self, key: &[u8]) -> Result>, client::error::Error> { + Ok(self.storage.db.get(columns::AUX, key).map(|r| r.map(|v| v.to_vec())).map_err(db_err)?) + } +} + impl client::backend::Backend for Backend where Block: BlockT { type BlockImportOperation = BlockImportOperation; type Blockchain = BlockchainDb; @@ -883,24 +907,6 @@ impl client::backend::Backend for Backend whe _ => Err(client::error::ErrorKind::UnknownBlock(format!("{:?}", block)).into()), } } - - fn insert_aux<'a, 'b: 'a, 'c: 'a, I: IntoIterator, D: IntoIterator> - (&self, insert: I, delete: D) -> Result<(), client::error::Error> - { - let mut transaction = DBTransaction::new(); - for (k, v) in insert { - transaction.put(columns::AUX, k, v); - } - for k in delete { - transaction.delete(columns::AUX, k); - } - self.storage.db.write(transaction).map_err(db_err)?; - Ok(()) - } - - fn get_aux(&self, key: &[u8]) -> Result>, client::error::Error> { - Ok(self.storage.db.get(columns::AUX, key).map(|r| r.map(|v| v.to_vec())).map_err(db_err)?) - } } impl client::backend::LocalBackend for Backend diff --git a/substrate/core/client/src/backend.rs b/substrate/core/client/src/backend.rs index 5fa5ec8933..290159bdc5 100644 --- a/substrate/core/client/src/backend.rs +++ b/substrate/core/client/src/backend.rs @@ -79,6 +79,20 @@ pub trait BlockImportOperation where where I: IntoIterator, Option>)>; } +/// Provides access to an auxiliary database. +pub trait AuxStore { + /// Insert auxiliary data into key-value store. Deletions occur after insertions. + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >(&self, insert: I, delete: D) -> error::Result<()>; + /// Query auxiliary data from key-value store. + fn get_aux(&self, key: &[u8]) -> error::Result>>; +} + /// Client backend. Manages the data layer. /// /// Note on state pruning: while an object from `state_at` is alive, the state @@ -87,7 +101,7 @@ pub trait BlockImportOperation where /// /// The same applies for live `BlockImportOperation`s: while an import operation building on a parent `P` /// is alive, the state for `P` should not be pruned. -pub trait Backend: Send + Sync where +pub trait Backend: AuxStore + Send + Sync where Block: BlockT, H: Hasher, { @@ -117,10 +131,22 @@ pub trait Backend: Send + Sync where /// Attempts to revert the chain by `n` blocks. Returns the number of blocks that were /// successfully reverted. fn revert(&self, n: NumberFor) -> error::Result>; + /// Insert auxiliary data into key-value store. - fn insert_aux<'a, 'b: 'a, 'c: 'a, I: IntoIterator, D: IntoIterator>(&self, insert: I, delete: D) -> error::Result<()>; + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >(&self, insert: I, delete: D) -> error::Result<()> + { + AuxStore::insert_aux(self, insert, delete) + } /// Query auxiliary data from key-value store. - fn get_aux(&self, key: &[u8]) -> error::Result>>; + fn get_aux(&self, key: &[u8]) -> error::Result>> { + AuxStore::get_aux(self, key) + } } /// Mark for all Backend implementations, that are making use of state data, stored locally. diff --git a/substrate/core/client/src/client.rs b/substrate/core/client/src/client.rs index a88aa6422c..a37bd163b2 100644 --- a/substrate/core/client/src/client.rs +++ b/substrate/core/client/src/client.rs @@ -1170,6 +1170,27 @@ impl BlockBody for Client } } +impl backend::AuxStore for Client + where + B: backend::Backend, + E: CallExecutor, + Block: BlockT, +{ + /// Insert auxiliary data into key-value store. + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >(&self, insert: I, delete: D) -> error::Result<()> { + ::backend::AuxStore::insert_aux(&*self.backend, insert, delete) + } + /// Query auxiliary data from key-value store. + fn get_aux(&self, key: &[u8]) -> error::Result>> { + ::backend::AuxStore::get_aux(&*self.backend, key) + } +} #[cfg(test)] pub(crate) mod tests { use std::collections::HashMap; diff --git a/substrate/core/client/src/in_mem.rs b/substrate/core/client/src/in_mem.rs index 32303fb73f..224d070838 100644 --- a/substrate/core/client/src/in_mem.rs +++ b/substrate/core/client/src/in_mem.rs @@ -498,6 +498,34 @@ where } } +impl backend::AuxStore for Backend +where + Block: BlockT, + H: Hasher, + H::Out: HeapSizeOf + Ord, +{ + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >(&self, insert: I, delete: D) -> error::Result<()> { + let mut storage = self.blockchain.storage.write(); + for (k, v) in insert { + storage.aux.insert(k.to_vec(), v.to_vec()); + } + for k in delete { + storage.aux.remove(*k); + } + Ok(()) + } + + fn get_aux(&self, key: &[u8]) -> error::Result>> { + Ok(self.blockchain.storage.read().aux.get(key).cloned()) + } +} + impl backend::Backend for Backend where Block: BlockT, @@ -578,21 +606,6 @@ where fn revert(&self, _n: NumberFor) -> error::Result> { Ok(As::sa(0)) } - - fn insert_aux<'a, 'b: 'a, 'c: 'a, I: IntoIterator, D: IntoIterator>(&self, insert: I, delete: D) -> error::Result<()> { - let mut storage = self.blockchain.storage.write(); - for (k, v) in insert { - storage.aux.insert(k.to_vec(), v.to_vec()); - } - for k in delete { - storage.aux.remove(*k); - } - Ok(()) - } - - fn get_aux(&self, key: &[u8]) -> error::Result>> { - Ok(self.blockchain.storage.read().aux.get(key).cloned()) - } } impl backend::LocalBackend for Backend diff --git a/substrate/core/client/src/light/backend.rs b/substrate/core/client/src/light/backend.rs index 89c247b6af..44a918e8b5 100644 --- a/substrate/core/client/src/light/backend.rs +++ b/substrate/core/client/src/light/backend.rs @@ -70,6 +70,22 @@ impl Backend { } } +impl ::backend::AuxStore for Backend { + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >(&self, _insert: I, _delete: D) -> ClientResult<()> { + Err(ClientErrorKind::NotAvailableOnLightClient.into()) + } + + fn get_aux(&self, _key: &[u8]) -> ClientResult>> { + Err(ClientErrorKind::NotAvailableOnLightClient.into()) + } +} + impl ClientBackend for Backend where Block: BlockT, S: BlockchainStorage, @@ -131,14 +147,6 @@ impl ClientBackend for Backend where fn revert(&self, _n: NumberFor) -> ClientResult> { Err(ClientErrorKind::NotAvailableOnLightClient.into()) } - - fn insert_aux<'a, 'b: 'a, 'c: 'a, I: IntoIterator, D: IntoIterator>(&self, _insert: I, _delete: D) -> ClientResult<()> { - Err(ClientErrorKind::NotAvailableOnLightClient.into()) - } - - fn get_aux(&self, _key: &[u8]) -> ClientResult>> { - Err(ClientErrorKind::NotAvailableOnLightClient.into()) - } } impl RemoteBackend for Backend diff --git a/substrate/core/consensus/aura/Cargo.toml b/substrate/core/consensus/aura/Cargo.toml index 0f403ab2ff..7994afc984 100644 --- a/substrate/core/consensus/aura/Cargo.toml +++ b/substrate/core/consensus/aura/Cargo.toml @@ -2,41 +2,33 @@ name = "substrate-consensus-aura" version = "0.1.0" authors = ["Parity Technologies "] -description = "Rhododendron Round-Based consensus-algorithm for substrate" +description = "Aura consensus algorithm for substrate" [dependencies] -futures = "0.1.17" -parity-codec = { version = "2.1" } -substrate-consensus-common = { path = "../common" } +parity-codec = "2.1" substrate-client = { path = "../../client" } substrate-primitives = { path = "../../primitives" } -substrate-network = { path = "../../network" } srml-support = { path = "../../../srml/support" } sr-primitives = { path = "../../sr-primitives" } sr-version = { path = "../../sr-version" } sr-io = { path = "../../sr-io" } +substrate-consensus-aura-primitives = { path = "primitives" } + srml-consensus = { path = "../../../srml/consensus" } +futures = "0.1.17" tokio = "0.1.7" parking_lot = "0.4" error-chain = "0.12" log = "0.3" +substrate-consensus-common = { path = "../common" } +substrate-network = { path = "../../network" } [dev-dependencies] substrate-keyring = { path = "../../keyring" } substrate-executor = { path = "../../executor" } substrate-service = { path = "../../service" } substrate-test-client = { path = "../../test-client" } -env_logger = { version = "0.4" } +env_logger = "0.4" [target.'cfg(test)'.dependencies] -substrate-network = { path = "../../network", features = ["test-helpers"] } - -[features] -default = ["std"] -std = [ - "substrate-primitives/std", - "srml-support/std", - "sr-primitives/std", - "sr-version/std", -] - +substrate-network = { path = "../../network", features = ["test-helpers"], optional = true } diff --git a/substrate/core/consensus/aura/primitives/Cargo.toml b/substrate/core/consensus/aura/primitives/Cargo.toml new file mode 100644 index 0000000000..34dac7013d --- /dev/null +++ b/substrate/core/consensus/aura/primitives/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "substrate-consensus-aura-primitives" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Primitives for Aura consensus" + +[dependencies] +parity-codec = { version = "2.1", default-features = false } +substrate-client = { path = "../../../client", default-features = false } +substrate-primitives = { path = "../../../primitives", default-features = false } +srml-support = { path = "../../../../srml/support", default-features = false } +sr-primitives = { path = "../../../sr-primitives", default-features = false } +sr-version = { path = "../../../sr-version", default-features = false } +sr-io = { path = "../../../sr-io", default-features = false } + +[features] +default = ["std"] +std = [ + "parity-codec/std", + "substrate-client/std", + "substrate-primitives/std", + "srml-support/std", + "sr-primitives/std", + "sr-version/std", + "sr-io/std", +] diff --git a/substrate/core/consensus/aura/primitives/src/lib.rs b/substrate/core/consensus/aura/primitives/src/lib.rs new file mode 100644 index 0000000000..8b8c494441 --- /dev/null +++ b/substrate/core/consensus/aura/primitives/src/lib.rs @@ -0,0 +1,50 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Primitives for Aura. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate parity_codec as codec; +extern crate substrate_client as client; +extern crate substrate_primitives as primitives; +extern crate srml_support as runtime_support; +extern crate sr_io as runtime_io; +extern crate sr_primitives as runtime_primitives; + +/// The ApiIds for Aura authorship API. +pub mod id { + use client::runtime_api::ApiId; + + /// ApiId for the AuraApi trait. + pub const AURA_API: ApiId = *b"aura_api"; +} + +/// Runtime-APIs +pub mod api { + use client::decl_runtime_apis; + decl_runtime_apis! { + /// API necessary for block authorship with aura. + pub trait AuraApi { + /// Return the slot duration in seconds for Aura. + /// Currently, only the value provided by this type at genesis + /// will be used. + /// + /// Dynamic slot duration may be supported in the future. + fn slot_duration() -> u64; + } + } +} diff --git a/substrate/core/consensus/aura/src/lib.rs b/substrate/core/consensus/aura/src/lib.rs index 74ff9db7ac..b183c6346e 100644 --- a/substrate/core/consensus/aura/src/lib.rs +++ b/substrate/core/consensus/aura/src/lib.rs @@ -27,15 +27,22 @@ //! far in the future they are. extern crate parity_codec as codec; -extern crate substrate_consensus_common as consensus_common; extern crate substrate_client as client; extern crate substrate_primitives as primitives; -extern crate substrate_network as network; extern crate srml_support as runtime_support; -extern crate sr_primitives as runtime_primitives; -extern crate sr_version as runtime_version; extern crate sr_io as runtime_io; +extern crate sr_primitives as runtime_primitives; +extern crate substrate_consensus_aura_primitives as aura_primitives; + +extern crate substrate_consensus_common as consensus_common; extern crate tokio; +extern crate sr_version as runtime_version; +extern crate substrate_network as network; +extern crate futures; +extern crate parking_lot; + +#[macro_use] +extern crate log; #[cfg(test)] extern crate substrate_keyring as keyring; @@ -46,12 +53,7 @@ extern crate substrate_test_client as test_client; #[cfg(test)] extern crate env_logger; -extern crate parking_lot; - -#[macro_use] -extern crate log; - -extern crate futures; +pub use aura_primitives::*; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -59,14 +61,16 @@ use std::time::{Duration, Instant}; use codec::Encode; use consensus_common::{Authorities, BlockImport, Environment, Proposer}; use client::ChainHead; +use client::block_builder::api::BlockBuilder as BlockBuilderApi; use consensus_common::{ImportBlock, BlockOrigin}; -use runtime_primitives::{generic, generic::BlockId, Justification}; -use runtime_primitives::traits::{Block, Header, Digest, DigestItemFor}; +use runtime_primitives::{generic, generic::BlockId, Justification, BasicInherentData}; +use runtime_primitives::traits::{Block, Header, Digest, DigestItemFor, ProvideRuntimeApi}; use network::import_queue::{Verifier, BasicQueue}; use primitives::{AuthorityId, ed25519}; use futures::{Stream, Future, IntoFuture, future::{self, Either}}; -use tokio::timer::Interval; +use tokio::timer::{Delay, Timeout}; +use api::AuraApi; pub use consensus_common::SyncOracle; @@ -82,15 +86,6 @@ pub trait Network: Clone { fn send_message(&self, slot: u64, message: Vec); } -/// Configuration for Aura consensus. -#[derive(Clone)] -pub struct Config { - /// The local authority keypair. Can be none if this is just an observer. - pub local_key: Option>, - /// The slot duration in seconds. - pub slot_duration: u64 -} - /// Get slot author for given block along with authorities. fn slot_author(slot_num: u64, authorities: &[AuthorityId]) -> Option { if authorities.is_empty() { return None } @@ -115,6 +110,13 @@ fn duration_now() -> Option { }).ok() } +fn timestamp_and_slot_now(slot_duration: u64) -> Option<(u64, u64)> { + duration_now().map(|s| { + let s = s.as_secs(); + (s, s / slot_duration) + }) +} + /// Get the slot for now. fn slot_now(slot_duration: u64) -> Option { duration_now().map(|s| s.as_secs() / slot_duration) @@ -145,15 +147,56 @@ impl CompatibleDigestItem for generic::DigestItem( - config: Config, +/// Start the aura worker in a separate thread. +pub fn start_aura_thread( + slot_duration: SlotDuration, + local_key: Arc, client: Arc, block_import: Arc, env: Arc, sync_oracle: SO, -) - -> impl Future where +) where + B: Block + 'static, + C: Authorities + ChainHead + Send + Sync + 'static, + E: Environment + Send + Sync + 'static, + E::Proposer: Proposer + 'static, + I: BlockImport + Send + Sync + 'static, + Error: From + From + 'static, + SO: SyncOracle + Send + Clone + 'static, + DigestItemFor: CompatibleDigestItem + 'static, + Error: ::std::error::Error + Send + From<::consensus_common::Error> + 'static, +{ + use tokio::runtime::current_thread::Runtime; + + ::std::thread::spawn(move || { + let mut runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + warn!("Unable to start authorship: {:?}", e); + return; + } + }; + + runtime.block_on(start_aura( + slot_duration, + local_key, + client, + block_import, + env, + sync_oracle, + )).expect("aura authorship never returns error; qed"); + }); +} + +/// Start the aura worker. The returned future should be run in a tokio runtime. +pub fn start_aura( + slot_duration: SlotDuration, + local_key: Arc, + client: Arc, + block_import: Arc, + env: Arc, + sync_oracle: SO, +) -> impl Future where B: Block, C: Authorities + ChainHead, E: Environment, @@ -165,99 +208,138 @@ pub fn start_aura( Error: ::std::error::Error + Send + 'static + From<::consensus_common::Error>, { let make_authorship = move || { - let config = config.clone(); + use futures::future; + let client = client.clone(); + let pair = local_key.clone(); let block_import = block_import.clone(); let env = env.clone(); let sync_oracle = sync_oracle.clone(); + let SlotDuration(slot_duration) = slot_duration; - let local_keys = config.local_key.map(|pair| (pair.public(), pair)); - let slot_duration = config.slot_duration; - let mut last_authored_slot = 0; - let next_slot_start = duration_now().map(|now| { + fn time_until_next(now: Duration, slot_duration: u64) -> Duration { let remaining_full_secs = slot_duration - (now.as_secs() % slot_duration) - 1; let remaining_nanos = 1_000_000_000 - now.subsec_nanos(); - Instant::now() + Duration::new(remaining_full_secs, remaining_nanos) - }).unwrap_or_else(|| Instant::now()); + Duration::new(remaining_full_secs, remaining_nanos) + }; - Interval::new(next_slot_start, Duration::from_secs(slot_duration)) - .filter(move |_| !sync_oracle.is_major_syncing()) // only propose when we are not syncing. - .filter_map(move |_| local_keys.clone()) // skip if not authoring. - .map_err(|e| debug!(target: "aura", "Faulty timer: {:?}", e)) - .for_each(move |(public_key, key)| { - use futures::future; + // rather than use an interval, we schedule our waits ourselves + future::loop_fn((), move |()| { + let next_slot_start = duration_now() + .map(|now| Instant::now() + time_until_next(now, slot_duration)) + .unwrap_or_else(|| Instant::now()); - let slot_num = match slot_now(slot_duration) { - Some(n) => n, - None => return Either::B(future::err(())), - }; + let client = client.clone(); + let pair = pair.clone(); + let block_import = block_import.clone(); + let env = env.clone(); + let sync_oracle = sync_oracle.clone(); + let public_key = pair.public(); - if last_authored_slot >= slot_num { return Either::B(future::ok(())) } - last_authored_slot = slot_num; - - let chain_head = match client.best_block_header() { - Ok(x) => x, - Err(e) => { - warn!(target:"aura", "Unable to author block in slot {}. no best block header: {:?}", slot_num, e); - return Either::B(future::ok(())) - } - }; - - let authorities = match client.authorities(&BlockId::Hash(chain_head.hash())){ - Ok(authorities) => authorities, - Err(e) => { - warn!("Unable to fetch authorities at block {:?}: {:?}", chain_head.hash(), e); + Delay::new(next_slot_start) + .map_err(|e| debug!(target: "aura", "Faulty timer: {:?}", e)) + .and_then(move |_| { + // only propose when we are not syncing. + if sync_oracle.is_major_syncing() { + debug!(target: "aura", "Skipping proposal slot due to sync."); return Either::B(future::ok(())); } - }; - let proposal_work = match slot_author(slot_num, &authorities) { - None => return Either::B(future::ok(())), - Some(author) => if author.0 == public_key.0 { - // we are the slot author. make a block and sign it. - let proposer = match env.init(&chain_head, &authorities, key.clone()) { - Ok(p) => p, - Err(e) => { - warn!("Unable to author block in slot {:?}: {:?}", slot_num, e); - return Either::B(future::ok(())) - } - }; + let pair = pair.clone(); + let (timestamp, slot_num) = match timestamp_and_slot_now(slot_duration) { + Some(n) => n, + None => return Either::B(future::err(())), + }; - proposer.propose().into_future() - } else { - return Either::B(future::ok(())); - } - }; - - let block_import = block_import.clone(); - Either::A(proposal_work - .map(move |b| { - let (header, body) = b.deconstruct(); - let pre_hash = header.hash(); - let parent_hash = header.parent_hash().clone(); - - // sign the pre-sealed hash of the block and then - // add it to a digest item. - let to_sign = (slot_num, pre_hash).encode(); - let signature = key.sign(&to_sign[..]); - let item = as CompatibleDigestItem>::aura_seal(slot_num, signature); - let import_block = ImportBlock { - origin: BlockOrigin::Own, - header, - justification: None, - post_digests: vec![item], - body: Some(body), - finalized: false, - auxiliary: Vec::new(), - }; - - if let Err(e) = block_import.import_block(import_block, None) { - warn!(target: "aura", "Error with block built on {:?}: {:?}", parent_hash, e); + let chain_head = match client.best_block_header() { + Ok(x) => x, + Err(e) => { + warn!(target:"aura", "Unable to author block in slot {}. \ + no best block header: {:?}", slot_num, e); + return Either::B(future::ok(())) } - }) - .map_err(|e| warn!("Failed to construct block: {:?}", e)) - ) - }) + }; + + let authorities = match client.authorities(&BlockId::Hash(chain_head.hash())) { + Ok(authorities) => authorities, + Err(e) => { + warn!("Unable to fetch authorities at\ + block {:?}: {:?}", chain_head.hash(), e); + return Either::B(future::ok(())); + } + }; + + let proposal_work = match slot_author(slot_num, &authorities) { + None => return Either::B(future::ok(())), + Some(author) => if author.0 == public_key.0 { + debug!(target: "aura", "Starting authorship at slot {}; timestamp = {}", + slot_num, timestamp); + + // we are the slot author. make a block and sign it. + let proposer = match env.init(&chain_head, &authorities, pair.clone()) { + Ok(p) => p, + Err(e) => { + warn!("Unable to author block in slot {:?}: {:?}", slot_num, e); + return Either::B(future::ok(())) + } + }; + + // deadline our production to approx. the end of the + // slot + Timeout::new( + proposer.propose().into_future(), + time_until_next(Duration::from_secs(timestamp), slot_duration), + ) + } else { + return Either::B(future::ok(())); + } + }; + + let block_import = block_import.clone(); + Either::A(proposal_work + .map(move |b| { + // minor hack since we don't have access to the timestamp + // that is actually set by the proposer. + let slot_after_building = slot_now(slot_duration); + if slot_after_building != Some(slot_num) { + info!("Discarding proposal for slot {}; block production took too long", + slot_num); + return + } + + let (header, body) = b.deconstruct(); + let pre_hash = header.hash(); + let parent_hash = header.parent_hash().clone(); + + // sign the pre-sealed hash of the block and then + // add it to a digest item. + let to_sign = (slot_num, pre_hash).encode(); + let signature = pair.sign(&to_sign[..]); + let item = as CompatibleDigestItem>::aura_seal( + slot_num, + signature, + ); + + let import_block = ImportBlock { + origin: BlockOrigin::Own, + header, + justification: None, + post_digests: vec![item], + body: Some(body), + finalized: false, + auxiliary: Vec::new(), + }; + + if let Err(e) = block_import.import_block(import_block, None) { + warn!(target: "aura", "Error with block built on {:?}: {:?}", + parent_hash, e); + } + }) + .map_err(|e| warn!("Failed to construct block: {:?}", e)) + ) + }) + .map(|_| future::Loop::Continue(())) + }) }; future::loop_fn((), move |()| { @@ -337,7 +419,19 @@ pub trait ExtraVerification: Send + Sync { type Verified: IntoFuture; /// Do additional verification for this block. - fn verify(&self, header: &B::Header, body: Option<&[B::Extrinsic]>) -> Self::Verified; + fn verify( + &self, + header: &B::Header, + body: Option<&[B::Extrinsic]>, + ) -> Self::Verified; +} + +/// A verifier for Aura blocks. +pub struct AuraVerifier { + slot_duration: SlotDuration, + client: Arc, + make_inherent: MakeInherent, + extra: E, } /// No-op extra verification. @@ -351,33 +445,35 @@ impl ExtraVerification for NothingExtra { Ok(()) } } -/// A verifier for Aura blocks. -pub struct AuraVerifier { - config: Config, - client: Arc, - extra: E, -} -impl Verifier for AuraVerifier where - C: Authorities + BlockImport + Send + Sync, +impl Verifier for AuraVerifier where + C: Authorities + BlockImport + ProvideRuntimeApi + Send + Sync, + C::Api: BlockBuilderApi, DigestItemFor: CompatibleDigestItem, E: ExtraVerification, + MakeInherent: Fn(u64, u64) -> Inherent + Send + Sync, { fn verify( &self, origin: BlockOrigin, header: B::Header, justification: Option, - body: Option> + mut body: Option>, ) -> Result<(ImportBlock, Option>), String> { - let slot_now = slot_now(self.config.slot_duration) + use runtime_primitives::CheckInherentError; + const MAX_TIMESTAMP_DRIFT_SECS: u64 = 60; + + let (timestamp_now, slot_now) = timestamp_and_slot_now(self.slot_duration.0) .ok_or("System time is before UnixTime?".to_owned())?; let hash = header.hash(); let parent_hash = *header.parent_hash(); let authorities = self.client.authorities(&BlockId::Hash(parent_hash)) .map_err(|e| format!("Could not fetch authorities at {:?}: {:?}", parent_hash, e))?; - let extra_verification = self.extra.verify(&header, body.as_ref().map(|x| &x[..])); + let extra_verification = self.extra.verify( + &header, + body.as_ref().map(|x| &x[..]), + ); // we add one to allow for some small drift. // FIXME: in the future, alter this queue to allow deferring of headers @@ -387,7 +483,40 @@ impl Verifier for AuraVerifier where CheckedHeader::Checked(pre_header, slot_num, sig) => { let item = >::aura_seal(slot_num, sig); - debug!(target: "aura", "Checked {:?}; importing.", pre_header); + // if the body is passed through, we need to use the runtime + // to check that the internally-set timestamp in the inherents + // actually matches the slot set in the seal. + if let Some(inner_body) = body.take() { + let inherent = (self.make_inherent)(timestamp_now, slot_num); + let block = Block::new(pre_header.clone(), inner_body); + + let inherent_res = self.client.runtime_api().check_inherents( + &BlockId::Hash(parent_hash), + &block, + &inherent, + ).map_err(|e| format!("{:?}", e))?; + + match inherent_res { + Ok(()) => {} + Err(CheckInherentError::ValidAtTimestamp(timestamp)) => { + // halt import until timestamp is valid. + // reject when too far ahead. + if timestamp > timestamp_now + MAX_TIMESTAMP_DRIFT_SECS { + return Err("Rejecting block too far in future".into()); + } + + let diff = timestamp.saturating_sub(timestamp_now); + info!(target: "aura", "halting for block {} seconds in the future", diff); + ::std::thread::sleep(Duration::from_secs(diff)); + }, + Err(CheckInherentError::Other(s)) => return Err(s.into_owned()), + } + + let (_, inner_body) = block.deconstruct(); + body = Some(inner_body); + } + + trace!(target: "aura", "Checked {:?}; importing.", pre_header); extra_verification.into_future().wait()?; @@ -412,17 +541,72 @@ impl Verifier for AuraVerifier where } } +/// A utility for making the basic-inherent data. +pub fn make_basic_inherent(timestamp: u64, slot_now: u64) -> BasicInherentData { + BasicInherentData::new(timestamp, slot_now) +} + +/// A type for a function which produces inherent. +pub type InherentProducingFn = fn(u64, u64) -> I; + /// The Aura import queue type. -pub type AuraImportQueue = BasicQueue>; +pub type AuraImportQueue = BasicQueue>; + +/// A slot duration. Create with `get_or_compute`. +// The internal member should stay private here. +#[derive(Clone, Copy, Debug)] +pub struct SlotDuration(u64); + +impl SlotDuration { + /// Either fetch the slot duration from disk or compute it from the genesis + /// state. + pub fn get_or_compute(client: &C) -> ::client::error::Result where + C: ::client::backend::AuxStore, + C: ProvideRuntimeApi, + C::Api: AuraApi, + { + use codec::Decode; + const SLOT_KEY: &[u8] = b"aura_slot_duration"; + + match client.get_aux(SLOT_KEY)? { + Some(v) => u64::decode(&mut &v[..]) + .map(SlotDuration) + .ok_or_else(|| ::client::error::ErrorKind::Backend( + format!("Aura slot duration kept in invalid format"), + ).into()), + None => { + use runtime_primitives::traits::Zero; + let genesis_slot_duration = client.runtime_api() + .slot_duration(&BlockId::number(Zero::zero()))?; + + info!("Loaded block-time = {:?} seconds from genesis on first-launch", + genesis_slot_duration); + + genesis_slot_duration.using_encoded(|s| { + client.insert_aux(&[(SLOT_KEY, &s[..])], &[]) + })?; + + Ok(SlotDuration(genesis_slot_duration)) + } + } + } +} /// Start an import queue for the Aura consensus algorithm. -pub fn import_queue(config: Config, client: Arc, extra: E) -> AuraImportQueue where +pub fn import_queue( + slot_duration: SlotDuration, + client: Arc, + extra: E, + make_inherent: MakeInherent, +) -> AuraImportQueue where B: Block, - C: Authorities + BlockImport + Send + Sync, + C: Authorities + BlockImport + ProvideRuntimeApi + Send + Sync, + C::Api: BlockBuilderApi, DigestItemFor: CompatibleDigestItem, E: ExtraVerification, + MakeInherent: Fn(u64, u64) -> Inherent + Send + Sync, { - let verifier = Arc::new(AuraVerifier { config, client: client.clone(), extra, }); + let verifier = Arc::new(AuraVerifier { slot_duration, client: client.clone(), extra, make_inherent }); BasicQueue::new(verifier, client) } @@ -440,9 +624,9 @@ mod tests { use client::BlockchainEvents; use test_client; - type Error = client::error::Error; + type Error = ::client::error::Error; - type TestClient = client::Client; + type TestClient = ::client::Client; struct DummyFactory(Arc); struct DummyProposer(u64, Arc); @@ -471,12 +655,16 @@ mod tests { const TEST_ROUTING_INTERVAL: Duration = Duration::from_millis(50); pub struct AuraTestNet { - peers: Vec, ()>>>, + peers: Vec, + >, ()>>>, started: bool } impl TestNetFactory for AuraTestNet { - type Verifier = AuraVerifier; + type Verifier = AuraVerifier>; type PeerData = (); /// Create new test network with peers and given config. @@ -490,8 +678,17 @@ mod tests { fn make_verifier(&self, client: Arc, _cfg: &ProtocolConfig) -> Arc { - let config = Config { local_key: None, slot_duration: SLOT_DURATION }; - Arc::new(AuraVerifier { client, config, extra: NothingExtra }) + fn make_inherent(_: u64, _: u64) { () } + let slot_duration = SlotDuration::get_or_compute(&*client) + .expect("slot duration available"); + + assert_eq!(slot_duration.0, SLOT_DURATION); + Arc::new(AuraVerifier { + client, + slot_duration, + extra: NothingExtra, + make_inherent: make_inherent as _, + }) } fn peer(&self, i: usize) -> &Peer { @@ -542,11 +739,13 @@ mod tests { }) .for_each(move |_| Ok(())) ); + + let slot_duration = SlotDuration::get_or_compute(&*client) + .expect("slot duration available"); + let aura = start_aura( - Config { - local_key: Some(Arc::new(key.clone().into())), - slot_duration: SLOT_DURATION - }, + slot_duration, + Arc::new(key.clone().into()), client.clone(), client, environ.clone(), diff --git a/substrate/core/finality-grandpa/src/lib.rs b/substrate/core/finality-grandpa/src/lib.rs index c6f55e89ad..acaaab9ad4 100644 --- a/substrate/core/finality-grandpa/src/lib.rs +++ b/substrate/core/finality-grandpa/src/lib.rs @@ -505,9 +505,8 @@ impl, N, RA> voter::Environment, E, RA>( let last_completed: LastCompleted<_, _> = (0, round_state); let encoded = last_completed.encode(); - client.backend().insert_aux( + Backend::insert_aux( + &**client.backend(), &[ (AUTHORITY_SET_KEY, &encoded_set[..]), (LAST_COMPLETED_KEY, &encoded[..]), @@ -745,7 +745,7 @@ fn finalize_block, E, RA>( &[] ) } else { - client.backend().insert_aux(&[(AUTHORITY_SET_KEY, &encoded_set[..])], &[]) + Backend::insert_aux(&**client.backend(), &[(AUTHORITY_SET_KEY, &encoded_set[..])], &[]) }; if let Err(e) = write_result { @@ -954,7 +954,7 @@ impl, RA, PRA> BlockImport )?; let encoded = authorities.encode(); - self.inner.backend().insert_aux(&[(AUTHORITY_SET_KEY, &encoded[..])], &[])?; + Backend::insert_aux(&**self.inner.backend(), &[(AUTHORITY_SET_KEY, &encoded[..])], &[])?; }; let enacts_change = self.authority_set.inner().read().enacts_change(number, |canon_number| { @@ -1066,6 +1066,19 @@ where } } +impl, RA, PRA> ProvideRuntimeApi for GrandpaBlockImport +where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + PRA: ProvideRuntimeApi, +{ + type Api = PRA::Api; + + fn runtime_api<'a>(&'a self) -> ::runtime_primitives::traits::ApiRef<'a, Self::Api> { + self.api.runtime_api() + } +} + /// Half of a link between a block-import worker and a the background voter. // This should remain non-clone. pub struct LinkHalf, RA> { @@ -1131,7 +1144,7 @@ pub fn block_import, RA, PRA>( PRA::Api: GrandpaApi { use runtime_primitives::traits::Zero; - let authority_set = match client.backend().get_aux(AUTHORITY_SET_KEY)? { + let authority_set = match Backend::get_aux(&**client.backend(), AUTHORITY_SET_KEY)? { None => { info!(target: "afg", "Loading GRANDPA authorities \ from genesis on what appears to be first startup."); @@ -1144,7 +1157,7 @@ pub fn block_import, RA, PRA>( let authority_set = SharedAuthoritySet::genesis(genesis_authorities); let encoded = authority_set.inner().read().encode(); - client.backend().insert_aux(&[(AUTHORITY_SET_KEY, &encoded[..])], &[])?; + Backend::insert_aux(&**client.backend(), &[(AUTHORITY_SET_KEY, &encoded[..])], &[])?; authority_set } @@ -1261,7 +1274,7 @@ pub fn run_grandpa, N, RA>( let chain_info = client.info()?; let genesis_hash = chain_info.chain.genesis_hash; - let (last_round_number, last_state) = match client.backend().get_aux(LAST_COMPLETED_KEY)? { + let (last_round_number, last_state) = match Backend::get_aux(&**client.backend(), LAST_COMPLETED_KEY)? { None => (0, RoundState::genesis((genesis_hash, >::zero()))), Some(raw) => LastCompleted::decode(&mut &raw[..]) .ok_or_else(|| ::client::error::ErrorKind::Backend( diff --git a/substrate/core/service/src/consensus.rs b/substrate/core/service/src/consensus.rs index a879324403..fd3c44ae07 100644 --- a/substrate/core/service/src/consensus.rs +++ b/substrate/core/service/src/consensus.rs @@ -19,23 +19,19 @@ // FIXME: move this into substrate-consensus-common - https://github.com/paritytech/substrate/issues/1021 use std::sync::Arc; -use std::time::{self, Duration, Instant}; +use std::time; use std; use client::{self, error, Client as SubstrateClient, CallExecutor}; use client::{block_builder::api::BlockBuilder as BlockBuilderApi, runtime_api::Core}; use codec::{Decode, Encode}; -use consensus_common::{self, evaluation, offline_tracker::OfflineTracker}; +use consensus_common::{self, evaluation}; use primitives::{H256, AuthorityId, ed25519, Blake2Hasher}; use runtime_primitives::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, ProvideRuntimeApi}; use runtime_primitives::generic::BlockId; use runtime_primitives::BasicInherentData; use transaction_pool::txpool::{self, Pool as TransactionPool}; -use parking_lot::RwLock; - -/// Shared offline validator tracker. -pub type SharedOfflineTracker = Arc>; type Timestamp = u64; // block size limit. @@ -113,10 +109,6 @@ pub struct ProposerFactory where A: txpool::ChainApi { pub client: Arc, /// The transaction pool. pub transaction_pool: Arc>, - /// Offline-tracker. - pub offline: SharedOfflineTracker, - /// Force delay in evaluation this long. - pub force_delay: Timestamp, } impl consensus_common::Environment<::Block> for ProposerFactory where @@ -138,22 +130,14 @@ impl consensus_common::Environment<::Block> for Propose let id = BlockId::hash(parent_hash); - let authorities: Vec = self.client.runtime_api().authorities(&id)?; - self.offline.write().note_new_block(&authorities[..]); - info!("Starting consensus session on top of parent {:?}", parent_hash); - let now = Instant::now(); let proposer = Proposer { client: self.client.clone(), - start: now, parent_hash, parent_id: id, parent_number: *parent_header.number(), transaction_pool: self.transaction_pool.clone(), - offline: self.offline.clone(), - authorities, - minimum_timestamp: current_timestamp() + self.force_delay, }; Ok(proposer) @@ -163,14 +147,10 @@ impl consensus_common::Environment<::Block> for Propose /// The proposer logic. pub struct Proposer { client: Arc, - start: Instant, parent_hash: ::Hash, parent_id: BlockId, parent_number: <::Header as HeaderT>::Number, transaction_pool: Arc>, - offline: SharedOfflineTracker, - authorities: Vec, - minimum_timestamp: u64, } impl consensus_common::Proposer<::Block> for Proposer where @@ -186,24 +166,8 @@ impl consensus_common::Proposer<::Block> for Pro fn propose(&self) -> Result<::Block, error::Error> { use runtime_primitives::traits::BlakeTwo256; - const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60); - - let timestamp = ::std::cmp::max(self.minimum_timestamp, current_timestamp()); - - let elapsed_since_start = self.start.elapsed(); - let offline_indices = if elapsed_since_start > MAX_VOTE_OFFLINE_SECONDS { - Vec::new() - } else { - self.offline.read().reports(&self.authorities[..]) - }; - - if !offline_indices.is_empty() { - info!("Submitting offline authorities {:?} for slash-vote", - offline_indices.iter().map(|&i| self.authorities[i as usize]).collect::>(), - ) - } - - let inherent_data = BasicInherentData::new(timestamp, offline_indices); + let timestamp = current_timestamp(); + let inherent_data = BasicInherentData::new(timestamp, 0); let block = self.client.build_block( &self.parent_id, diff --git a/substrate/core/sr-primitives/src/lib.rs b/substrate/core/sr-primitives/src/lib.rs index 95f13b1e74..dfd34ed01f 100644 --- a/substrate/core/sr-primitives/src/lib.rs +++ b/substrate/core/sr-primitives/src/lib.rs @@ -480,16 +480,19 @@ macro_rules! impl_outer_log { pub struct BasicInherentData { /// Current timestamp. pub timestamp: u64, - /// Indices of offline validators. - pub consensus: Vec, + /// Blank report. + pub consensus: (), + /// Aura expected slot. Can take any value during block construction. + pub aura_expected_slot: u64, } impl BasicInherentData { /// Create a new `BasicInherentData` instance. - pub fn new(timestamp: u64, consensus: Vec) -> Self { + pub fn new(timestamp: u64, expected_slot: u64) -> Self { Self { timestamp, - consensus, + consensus: (), + aura_expected_slot: expected_slot, } } } @@ -506,6 +509,22 @@ pub enum CheckInherentError { Other(RuntimeString), } +impl CheckInherentError { + /// Combine two results, taking the "worse" of the two. + pub fn combine_results Result<(), Self>>(this: Result<(), Self>, other: F) -> Result<(), Self> { + match this { + Ok(()) => other(), + Err(CheckInherentError::Other(s)) => Err(CheckInherentError::Other(s)), + Err(CheckInherentError::ValidAtTimestamp(x)) => match other() { + Ok(()) => Err(CheckInherentError::ValidAtTimestamp(x)), + Err(CheckInherentError::ValidAtTimestamp(y)) + => Err(CheckInherentError::ValidAtTimestamp(rstd::cmp::max(x, y))), + Err(CheckInherentError::Other(s)) => Err(CheckInherentError::Other(s)), + } + } + } +} + #[cfg(test)] mod tests { use substrate_primitives::hash::H256; diff --git a/substrate/core/test-runtime/Cargo.toml b/substrate/core/test-runtime/Cargo.toml index 42f4d1edd1..bc5bfe4905 100644 --- a/substrate/core/test-runtime/Cargo.toml +++ b/substrate/core/test-runtime/Cargo.toml @@ -13,6 +13,7 @@ parity-codec-derive = { version = "2.1", default-features = false } substrate-keyring = { path = "../keyring", optional = true } substrate-client = { path = "../client", optional = true } substrate-primitives = { path = "../primitives", default-features = false } +substrate-consensus-aura-primitives = { path = "../consensus/aura/primitives", default-features = false } sr-std = { path = "../sr-std", default-features = false } sr-io = { path = "../sr-io", default-features = false } sr-primitives = { path = "../sr-primitives", default-features = false } @@ -35,4 +36,5 @@ std = [ "substrate-primitives/std", "sr-primitives/std", "sr-version/std", + "substrate-consensus-aura-primitives/std", ] diff --git a/substrate/core/test-runtime/src/lib.rs b/substrate/core/test-runtime/src/lib.rs index 469a44c195..cceeee74d1 100644 --- a/substrate/core/test-runtime/src/lib.rs +++ b/substrate/core/test-runtime/src/lib.rs @@ -24,6 +24,7 @@ extern crate serde; extern crate sr_std as rstd; extern crate parity_codec as codec; extern crate sr_primitives as runtime_primitives; +extern crate substrate_consensus_aura_primitives as consensus_aura; #[macro_use] extern crate substrate_client as client; @@ -64,6 +65,7 @@ use primitives::AuthorityId; use primitives::OpaqueMetadata; #[cfg(any(feature = "std", test))] use runtime_version::NativeVersion; +use consensus_aura::api as aura_api; /// Test runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { @@ -234,7 +236,7 @@ impl_runtime_apis! { } fn check_inherents(_block: Block, _data: ()) -> Result<(), CheckInherentError> { - unimplemented!() + Ok(()) } fn random_seed() -> ::Hash { @@ -247,4 +249,8 @@ impl_runtime_apis! { system::balance_of(id) } } + + impl aura_api::AuraApi for Runtime { + fn slot_duration() -> u64 { 1 } + } } diff --git a/substrate/core/test-runtime/wasm/Cargo.lock b/substrate/core/test-runtime/wasm/Cargo.lock index 6574dd9d80..03a7ef0a00 100644 --- a/substrate/core/test-runtime/wasm/Cargo.lock +++ b/substrate/core/test-runtime/wasm/Cargo.lock @@ -1087,6 +1087,19 @@ dependencies = [ "substrate-trie 0.4.0", ] +[[package]] +name = "substrate-consensus-aura-primitives" +version = "0.1.0" +dependencies = [ + "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 0.1.0", + "sr-primitives 0.1.0", + "sr-version 0.1.0", + "srml-support 0.1.0", + "substrate-client 0.1.0", + "substrate-primitives 0.1.0", +] + [[package]] name = "substrate-consensus-common" version = "0.1.0" @@ -1208,6 +1221,7 @@ dependencies = [ "sr-version 0.1.0", "srml-support 0.1.0", "substrate-client 0.1.0", + "substrate-consensus-aura-primitives 0.1.0", "substrate-primitives 0.1.0", ] diff --git a/substrate/core/test-runtime/wasm/Cargo.toml b/substrate/core/test-runtime/wasm/Cargo.toml index 02b97a2c7e..0d8894e8cf 100644 --- a/substrate/core/test-runtime/wasm/Cargo.toml +++ b/substrate/core/test-runtime/wasm/Cargo.toml @@ -9,6 +9,7 @@ hex-literal = { version = "0.1.0", optional = true } parity-codec = { version = "2.1", default-features = false } parity-codec-derive = { version = "2.1", default-features = false } substrate-primitives = { path = "../../primitives", default-features = false } +substrate-consensus-aura-primitives = { path = "../../consensus/aura/primitives", default-features = false } substrate-client = { path = "../../client", default-features = false } sr-std = { path = "../../sr-std", default-features = false } sr-io = { path = "../../sr-io", default-features = false } @@ -28,7 +29,8 @@ std = [ "sr-version/std", "substrate-primitives/std", "substrate-client/std", - "sr-primitives/std" + "sr-primitives/std", + "substrate-consensus-aura-primitives/std", ] [lib] diff --git a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index c481cd193a..15ca3ed130 100644 Binary files a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/node/cli/Cargo.toml b/substrate/node/cli/Cargo.toml index 8ad41a263c..438d72173d 100644 --- a/substrate/node/cli/Cargo.toml +++ b/substrate/node/cli/Cargo.toml @@ -12,7 +12,6 @@ futures = "0.1" exit-future = "0.1" substrate-cli = { path = "../../core/cli" } parity-codec = { version = "2.1" } -parking_lot = "0.4" slog = "^2" sr-io = { path = "../../core/sr-io" } substrate-client = { path = "../../core/client" } @@ -24,7 +23,6 @@ substrate-service = { path = "../../core/service" } substrate-transaction-pool = { path = "../../core/transaction-pool" } substrate-network = { path = "../../core/network" } substrate-consensus-aura = { path = "../../core/consensus/aura" } -substrate-consensus-common = { path = "../../core/consensus/common" } substrate-finality-grandpa = { path = "../../core/finality-grandpa" } sr-primitives = { path = "../../core/sr-primitives" } node-executor = { path = "../executor" } diff --git a/substrate/node/cli/src/chain_spec.rs b/substrate/node/cli/src/chain_spec.rs index 6775c730f2..b6aa1ac0cd 100644 --- a/substrate/node/cli/src/chain_spec.rs +++ b/substrate/node/cli/src/chain_spec.rs @@ -50,7 +50,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig { const CENTS: u128 = 1_000 * MILLICENTS; // assume this is worth about a cent. const DOLLARS: u128 = 100 * CENTS; - const SECS_PER_BLOCK: u64 = 4; + const SECS_PER_BLOCK: u64 = 6; const MINUTES: u64 = 60 / SECS_PER_BLOCK; const HOURS: u64 = MINUTES * 60; const DAYS: u64 = HOURS * 24; @@ -111,7 +111,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig { enact_delay_period: 0, }), timestamp: Some(TimestampConfig { - period: SECS_PER_BLOCK, + period: SECS_PER_BLOCK / 2, // due to the nature of aura the slots are 2*period }), treasury: Some(TreasuryConfig { proposal_bond: Permill::from_percent(5), @@ -234,7 +234,7 @@ pub fn testnet_genesis( enact_delay_period: 0, }), timestamp: Some(TimestampConfig { - period: 5, // 5 second block time. + period: 2, // 2*2=4 second block time. }), treasury: Some(TreasuryConfig { proposal_bond: Permill::from_percent(5), diff --git a/substrate/node/cli/src/lib.rs b/substrate/node/cli/src/lib.rs index b968286de1..6df7eaaaeb 100644 --- a/substrate/node/cli/src/lib.rs +++ b/substrate/node/cli/src/lib.rs @@ -33,7 +33,6 @@ extern crate substrate_transaction_pool as transaction_pool; #[macro_use] extern crate substrate_network as network; extern crate substrate_consensus_aura as consensus; -extern crate substrate_consensus_common as consensus_common; extern crate substrate_client as client; extern crate substrate_finality_grandpa as grandpa; extern crate node_primitives; @@ -45,7 +44,6 @@ extern crate substrate_keystore; #[macro_use] extern crate log; extern crate structopt; -extern crate parking_lot; pub use cli::error; pub mod chain_spec; diff --git a/substrate/node/cli/src/service.rs b/substrate/node/cli/src/service.rs index 6be160d901..0645e1ca5d 100644 --- a/substrate/node/cli/src/service.rs +++ b/substrate/node/cli/src/service.rs @@ -21,22 +21,18 @@ use std::sync::Arc; use transaction_pool::{self, txpool::{Pool as TransactionPool}}; use node_runtime::{GenesisConfig, RuntimeApi}; -use node_primitives::Block; +use node_primitives::{Block, InherentData}; use substrate_service::{ FactoryFullConfiguration, LightComponents, FullComponents, FullBackend, FullClient, LightClient, LightBackend, FullExecutor, LightExecutor, TaskExecutor }; use node_executor; -use consensus::{import_queue, start_aura, Config as AuraConfig, AuraImportQueue, NothingExtra}; -use consensus_common::offline_tracker::OfflineTracker; +use consensus::{import_queue, start_aura, AuraImportQueue, SlotDuration, NothingExtra}; use primitives::ed25519::Pair; use client; use std::time::Duration; -use parking_lot::RwLock; use grandpa; -const AURA_SLOT_DURATION: u64 = 6; - construct_simple_protocol! { /// Demo protocol attachment for substrate. pub struct NodeProtocol where Block = Block { } @@ -105,15 +101,13 @@ construct_service_factory! { let proposer = Arc::new(substrate_service::ProposerFactory { client: service.client(), transaction_pool: service.transaction_pool(), - offline: Arc::new(RwLock::new(OfflineTracker::new())), - force_delay: 0 // FIXME: allow this to be configured https://github.com/paritytech/substrate/issues/1170 }); + + let client = service.client(); executor.spawn(start_aura( - AuraConfig { - local_key: Some(key), - slot_duration: AURA_SLOT_DURATION, - }, - service.client(), + SlotDuration::get_or_compute(&*client)?, + key, + client, block_import.clone(), proposer, service.network(), @@ -124,31 +118,39 @@ construct_service_factory! { }, LightService = LightComponents { |config, executor| >::new(config, executor) }, - FullImportQueue = AuraImportQueue, NothingExtra> + FullImportQueue = AuraImportQueue< + Self::Block, + grandpa::BlockImportForService, + NothingExtra, + ::consensus::InherentProducingFn, + > { |config: &mut FactoryFullConfiguration , client: Arc>| { + let slot_duration = SlotDuration::get_or_compute(&*client)?; let (block_import, link_half) = grandpa::block_import::<_, _, _, RuntimeApi, FullClient>(client.clone(), client)?; let block_import = Arc::new(block_import); config.custom.grandpa_import_setup = Some((block_import.clone(), link_half)); Ok(import_queue( - AuraConfig { - local_key: None, - slot_duration: 5 - }, + slot_duration, block_import, NothingExtra, + ::consensus::make_basic_inherent as _, )) }}, - LightImportQueue = AuraImportQueue, NothingExtra> - { |ref mut config, client| Ok( - import_queue(AuraConfig { - local_key: None, - slot_duration: 5 - }, - client, - NothingExtra, - )) + LightImportQueue = AuraImportQueue< + Self::Block, + LightClient, + NothingExtra, + ::consensus::InherentProducingFn, + > + { |ref mut config, client: Arc>| + Ok(import_queue( + SlotDuration::get_or_compute(&*client)?, + client, + NothingExtra, + ::consensus::make_basic_inherent as _, + )) }, } } @@ -165,7 +167,6 @@ mod tests { let bob: Arc = Arc::new(Keyring::Bob.into()); let validators = vec![alice.public().0.into(), bob.public().0.into()]; let keys: Vec<&ed25519::Pair> = vec![&*alice, &*bob]; - let offline = Arc::new(RwLock::new(OfflineTracker::new())); let dummy_runtime = ::tokio::runtime::Runtime::new().unwrap(); let block_factory = |service: &::FullService| { let block_id = BlockId::number(service.client().info().unwrap().chain.best_number); @@ -175,7 +176,6 @@ mod tests { client: service.client().clone(), transaction_pool: service.transaction_pool().clone(), network: consensus_net, - offline: offline.clone(), force_delay: 0, handle: dummy_runtime.executor(), }; diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 5949a89344..d743303fef 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -311,13 +311,13 @@ mod tests { 1, GENESIS_HASH.into(), if support_changes_trie { - hex!("7c00d30974b6d709766e5b231295b6b5ff7ffd42ef1385853c0a29859723d147").into() + hex!("dd0ab9b04b84b2a0704a6c38968d97a41e693345a9019dae880a80cf8176676c").into() } else { - hex!("e96a29fe7f7aba0e4a06837d4a5a4201f60bf613b9947ce5772559ef525f4268").into() + hex!("f5d85f3baebdd85aff8acfdeab0c6e00322d8b64b10a277a0231f4ca10dca7ee").into() }, if support_changes_trie { vec![changes_trie_log( - hex!("1f8f44dcae8982350c14dee720d34b147e73279f5a2ce1f9781195a991970978").into(), + hex!("cda28e5c630db8eb0e4309b58ce504597c6cbb59bda43fd65e96bb2be73a4586").into(), )] } else { vec![] @@ -339,7 +339,7 @@ mod tests { construct_block( 2, block1(false).1, - hex!("80e77e443da5f81fab7265acae6cbdfff79e02eaa90306d9dd14dabddad5f99d").into(), + hex!("807f9952d1962fcc62e8ee58fb4a3b5dc0247f314e9920a25354c1a1915e6941").into(), vec![ // session changes here, so we add a grandpa change signal log. Log::from(::grandpa::RawLog::AuthoritiesChangeSignal(0, vec![ (Keyring::One.to_raw_public().into(), 1), @@ -368,7 +368,7 @@ mod tests { construct_block( 1, GENESIS_HASH.into(), - hex!("337c94adf2041fa953d5afaf8919032e3b88ee440c88c48a231856306991dca1").into(), + hex!("5cc895b7c1d5fa824a2b6aefdcf34088156b3762509050bc384853106d48c7e4").into(), vec![], vec![ CheckedExtrinsic { @@ -658,7 +658,7 @@ mod tests { let b = construct_block( 1, GENESIS_HASH.into(), - hex!("10cb18e5a4e000690aaa3e2f0165c1cc563d38eb6736aa79c5a0ea4868042671").into(), + hex!("06d7654fe2799df26ef3ae932b04d17c163b5a89e6e5bc4ad514acced17bf6c4").into(), vec![], vec![ CheckedExtrinsic { diff --git a/substrate/node/runtime/Cargo.toml b/substrate/node/runtime/Cargo.toml index 774513d76b..65c2faa01f 100644 --- a/substrate/node/runtime/Cargo.toml +++ b/substrate/node/runtime/Cargo.toml @@ -13,9 +13,10 @@ parity-codec-derive = "2.1" sr-std = { path = "../../core/sr-std" } srml-support = { path = "../../srml/support" } substrate-primitives = { path = "../../core/primitives" } -substrate-finality-grandpa-primitives = { path = "../../core/finality-grandpa/primitives" } +substrate-consensus-aura-primitives = { path = "../../core/consensus/aura/primitives", default-features = false } substrate-client = { path = "../../core/client" } substrate-keyring = { path = "../../core/keyring" } +srml-aura = { path = "../../srml/aura" } srml-balances = { path = "../../srml/balances" } srml-consensus = { path = "../../srml/consensus" } srml-contract = { path = "../../srml/contract" } @@ -59,5 +60,5 @@ std = [ "serde/std", "safe-mix/std", "substrate-client/std", - "substrate-finality-grandpa-primitives/std", + "substrate-consensus-aura-primitives/std", ] diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index f2346b6282..aaef785f64 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -37,6 +37,7 @@ extern crate parity_codec_derive; extern crate parity_codec as codec; extern crate sr_std as rstd; +extern crate srml_aura as aura; extern crate srml_balances as balances; extern crate srml_consensus as consensus; extern crate srml_contract as contract; @@ -53,6 +54,7 @@ extern crate srml_upgrade_key as upgrade_key; #[macro_use] extern crate sr_version as version; extern crate node_primitives; +extern crate substrate_consensus_aura_primitives as consensus_aura; use rstd::prelude::*; use substrate_primitives::u32_trait::{_2, _4}; @@ -76,6 +78,7 @@ use council::seats as council_seats; #[cfg(any(feature = "std", test))] use version::NativeVersion; use substrate_primitives::OpaqueMetadata; +use consensus_aura::api as aura_api; #[cfg(any(feature = "std", test))] pub use runtime_primitives::BuildStorage; @@ -83,7 +86,6 @@ pub use consensus::Call as ConsensusCall; pub use timestamp::Call as TimestampCall; pub use balances::Call as BalancesCall; pub use runtime_primitives::{Permill, Perbill}; -pub use timestamp::BlockPeriod; pub use srml_support::{StorageValue, RuntimeMetadata}; const TIMESTAMP_SET_POSITION: u32 = 0; @@ -121,6 +123,10 @@ impl system::Trait for Runtime { type Log = Log; } +impl aura::Trait for Runtime { + type HandleReport = aura::StakingSlasher; +} + impl balances::Trait for Runtime { type Balance = Balance; type AccountIndex = AccountIndex; @@ -133,12 +139,16 @@ impl consensus::Trait for Runtime { const NOTE_OFFLINE_POSITION: u32 = NOTE_OFFLINE_POSITION; type Log = Log; type SessionKey = SessionKey; - type OnOfflineValidator = Staking; + + // the aura module handles offline-reports internally + // rather than using an explicit report system. + type InherentOfflineReport = (); } impl timestamp::Trait for Runtime { const TIMESTAMP_SET_POSITION: u32 = TIMESTAMP_SET_POSITION; type Moment = u64; + type OnTimestampSet = Aura; } /// Session key conversion. @@ -208,6 +218,7 @@ construct_runtime!( InherentData = BasicInherentData { System: system::{default, Log(ChangesTrieRoot)}, + Aura: aura::{Module}, Timestamp: timestamp::{Module, Call, Storage, Config, Inherent}, Consensus: consensus::{Module, Call, Storage, Config, Log(AuthoritiesChange), Inherent}, Balances: balances, @@ -299,7 +310,26 @@ impl_runtime_apis! { } fn check_inherents(block: Block, data: BasicInherentData) -> Result<(), CheckInherentError> { - Runtime::check_inherents(block, data) + let expected_slot = data.aura_expected_slot; + + // draw timestamp out from extrinsics. + let set_timestamp = block.extrinsics() + .get(TIMESTAMP_SET_POSITION as usize) + .and_then(|xt: &UncheckedExtrinsic| match xt.function { + Call::Timestamp(TimestampCall::set(ref t)) => Some(t.clone()), + _ => None, + }) + .ok_or_else(|| CheckInherentError::Other("No valid timestamp in block.".into()))?; + + // take the "worse" result of normal verification and the timestamp vs. seal + // check. + CheckInherentError::combine_results( + Runtime::check_inherents(block, data), + || { + Aura::verify_inherent(set_timestamp.into(), expected_slot) + .map_err(|s| CheckInherentError::Other(s.into())) + }, + ) } fn random_seed() -> ::Hash { @@ -332,4 +362,10 @@ impl_runtime_apis! { Grandpa::grandpa_authorities() } } + + impl aura_api::AuraApi for Runtime { + fn slot_duration() -> u64 { + Aura::slot_duration() + } + } } diff --git a/substrate/node/runtime/wasm/Cargo.lock b/substrate/node/runtime/wasm/Cargo.lock index 6dcbba2400..bf81321064 100644 --- a/substrate/node/runtime/wasm/Cargo.lock +++ b/substrate/node/runtime/wasm/Cargo.lock @@ -507,6 +507,7 @@ dependencies = [ "sr-primitives 0.1.0", "sr-std 0.1.0", "sr-version 0.1.0", + "srml-aura 0.1.0", "srml-balances 0.1.0", "srml-consensus 0.1.0", "srml-contract 0.1.0", @@ -522,6 +523,7 @@ dependencies = [ "srml-treasury 0.1.0", "srml-upgrade-key 0.1.0", "substrate-client 0.1.0", + "substrate-consensus-aura-primitives 0.1.0", "substrate-primitives 0.1.0", ] @@ -1063,6 +1065,25 @@ dependencies = [ "sr-std 0.1.0", ] +[[package]] +name = "srml-aura" +version = "0.1.0" +dependencies = [ + "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 0.1.0", + "sr-primitives 0.1.0", + "sr-std 0.1.0", + "srml-consensus 0.1.0", + "srml-staking 0.1.0", + "srml-support 0.1.0", + "srml-system 0.1.0", + "srml-timestamp 0.1.0", + "substrate-primitives 0.1.0", +] + [[package]] name = "srml-balances" version = "0.1.0" @@ -1391,6 +1412,19 @@ dependencies = [ "substrate-trie 0.4.0", ] +[[package]] +name = "substrate-consensus-aura-primitives" +version = "0.1.0" +dependencies = [ + "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 0.1.0", + "sr-primitives 0.1.0", + "sr-version 0.1.0", + "srml-support 0.1.0", + "substrate-client 0.1.0", + "substrate-primitives 0.1.0", +] + [[package]] name = "substrate-consensus-common" version = "0.1.0" diff --git a/substrate/node/runtime/wasm/Cargo.toml b/substrate/node/runtime/wasm/Cargo.toml index e460fcf234..400ec9d0f9 100644 --- a/substrate/node/runtime/wasm/Cargo.toml +++ b/substrate/node/runtime/wasm/Cargo.toml @@ -15,6 +15,7 @@ substrate-primitives = { path = "../../../core/primitives", default-features = f substrate-client = { path = "../../../core/client", default-features = false } sr-std = { path = "../../../core/sr-std", default-features = false } srml-support = { path = "../../../srml/support", default-features = false } +srml-aura = { path = "../../../srml/aura", default-features = false } srml-balances = { path = "../../../srml/balances", default-features = false } srml-consensus = { path = "../../../srml/consensus", default-features = false } srml-contract = { path = "../../../srml/contract", default-features = false } @@ -31,6 +32,7 @@ srml-upgrade-key = { path = "../../../srml/upgrade-key", default-features = fals srml-grandpa = { path = "../../../srml/grandpa", default-features = false } sr-version = { path = "../../../core/sr-version", default-features = false } node-primitives = { path = "../../primitives", default-features = false } +substrate-consensus-aura-primitives = { path = "../../../core/consensus/aura/primitives", default-features = false } [features] default = [] diff --git a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm index ad51eaf013..eaecba6231 100644 Binary files a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm and b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm differ diff --git a/substrate/srml/aura/Cargo.toml b/substrate/srml/aura/Cargo.toml new file mode 100644 index 0000000000..2c8ea28eb8 --- /dev/null +++ b/substrate/srml/aura/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "srml-aura" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +hex-literal = "0.1.0" +parity-codec = { version = "2.1", default-features = false } +parity-codec-derive = { version = "2.1", default-features = false } +serde = { version = "1.0", default-features = false } +substrate-primitives = { path = "../../core/primitives", default-features = false } +sr-std = { path = "../../core/sr-std", default-features = false } +sr-io = { path = "../../core/sr-io", default-features = false } +sr-primitives = { path = "../../core/sr-primitives", default-features = false } +srml-support = { path = "../support", default-features = false } +srml-system = { path = "../system", default-features = false } +srml-consensus = { path = "../consensus", default-features = false } +srml-timestamp = { path = "../timestamp", default-features = false } +srml-staking = { path = "../staking", default-features = false } + +[dev-dependencies] +lazy_static = "1.0" +parking_lot = "0.6" + +[features] +default = ["std"] +std = [ + "serde/std", + "parity-codec/std", + "parity-codec-derive/std", + "substrate-primitives/std", + "sr-std/std", + "sr-io/std", + "srml-support/std", + "sr-primitives/std", + "srml-system/std", + "srml-consensus/std", + "srml-timestamp/std", + "srml-staking/std", +] diff --git a/substrate/srml/aura/src/lib.rs b/substrate/srml/aura/src/lib.rs new file mode 100644 index 0000000000..b0bc50d69b --- /dev/null +++ b/substrate/srml/aura/src/lib.rs @@ -0,0 +1,199 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Consensus extension module for Aura consensus. This manages offline reporting. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[allow(unused_imports)] +#[macro_use] +extern crate sr_std as rstd; + +#[macro_use] +extern crate parity_codec_derive; +extern crate parity_codec; + +#[macro_use] +extern crate srml_support as runtime_support; + +extern crate sr_primitives as primitives; +extern crate srml_system as system; +extern crate srml_timestamp as timestamp; +extern crate srml_staking as staking; +extern crate substrate_primitives; + +#[cfg(test)] +extern crate srml_consensus as consensus; + +#[cfg(test)] +extern crate sr_io as runtime_io; + +#[cfg(test)] +#[macro_use] +extern crate lazy_static; + +#[cfg(test)] +extern crate parking_lot; + +use rstd::prelude::*; +use runtime_support::storage::StorageValue; +use runtime_support::dispatch::Result; +use primitives::traits::{As, Zero}; +use timestamp::OnTimestampSet; + +mod mock; +mod tests; + +/// Something which can handle Aura consensus reports. +pub trait HandleReport { + fn handle_report(report: AuraReport); +} + +impl HandleReport for () { + fn handle_report(_report: AuraReport) { } +} + +pub trait Trait: timestamp::Trait { + /// The logic for handling reports. + type HandleReport: HandleReport; +} + +decl_storage! { + trait Store for Module as Aura { + // The last timestamp. + LastTimestamp get(last) build(|_| T::Moment::sa(0)): T::Moment; + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { } +} + +/// A report of skipped authorities in aura. +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct AuraReport { + // The first skipped slot. + start_slot: usize, + // The number of times authorities were skipped. + skipped: usize, +} + +impl AuraReport { + /// Call the closure with (validator_indices, punishment_count) for each + /// validator to punish. + pub fn punish(&self, validator_count: usize, mut punish_with: F) + where F: FnMut(usize, usize) + { + let start_slot = self.start_slot % validator_count; + + // the number of times everyone was skipped. + let skipped_all = self.skipped / validator_count; + // the number of validators who were skipped once after that. + let skipped_after = self.skipped % validator_count; + + let iter = (start_slot..validator_count).into_iter() + .chain(0..start_slot) + .enumerate(); + + for (rel_index, actual_index) in iter { + let slash_count = skipped_all + if rel_index < skipped_after { + 1 + } else { + // avoid iterating over all authorities when skipping a couple. + if skipped_all == 0 { break } + 0 + }; + + if slash_count > 0 { + punish_with(actual_index, slash_count); + } + } + } +} + +impl Module { + /// Determine the Aura slot-duration based on the timestamp module configuration. + pub fn slot_duration() -> u64 { + // we double the minimum block-period so each author can always propose within + // the majority of their slot. + >::block_period().as_().saturating_mul(2) + } + + /// Verify an inherent slot that is used in a block seal against a timestamp + /// extracted from the block. + // TODO: ensure `ProvideInherent` can deal with dependencies like this. + // https://github.com/paritytech/substrate/issues/1228 + pub fn verify_inherent(timestamp: T::Moment, seal_slot: u64) -> Result { + let timestamp_based_slot = timestamp.as_() / Self::slot_duration(); + + if timestamp_based_slot == seal_slot { + Ok(()) + } else { + Err("timestamp set in block doesn't match slot in seal".into()) + } + } + + fn on_timestamp_set(now: T::Moment, slot_duration: T::Moment) { + let last = Self::last(); + ::LastTimestamp::put(now.clone()); + + if last == T::Moment::zero() { + return; + } + + assert!(slot_duration > T::Moment::zero(), "Aura slot duration cannot be zero."); + + let last_slot = last / slot_duration.clone(); + let first_skipped = last_slot.clone() + T::Moment::sa(1); + let cur_slot = now / slot_duration; + + assert!(last_slot < cur_slot, "Only one block may be authored per slot."); + if cur_slot == first_skipped { return } + + let slot_to_usize = |slot: T::Moment| { slot.as_() as usize }; + + let skipped_slots = cur_slot - last_slot - T::Moment::sa(1); + + H::handle_report(AuraReport { + start_slot: slot_to_usize(first_skipped), + skipped: slot_to_usize(skipped_slots), + }) + } +} + +impl OnTimestampSet for Module { + fn on_timestamp_set(moment: T::Moment) { + Self::on_timestamp_set::(moment, T::Moment::sa(Self::slot_duration())) + } +} + +/// A type for performing slashing based on aura reports. +pub struct StakingSlasher(::rstd::marker::PhantomData); + +impl HandleReport for StakingSlasher { + fn handle_report(report: AuraReport) { + let validators = staking::Module::::validators(); + + report.punish( + validators.len(), + |idx, slash_count| { + let v = validators[idx].clone(); + staking::Module::::on_offline_validator(v, slash_count); + } + ); + } +} diff --git a/substrate/srml/aura/src/mock.rs b/substrate/srml/aura/src/mock.rs new file mode 100644 index 0000000000..125466c9c0 --- /dev/null +++ b/substrate/srml/aura/src/mock.rs @@ -0,0 +1,78 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Test utilities + +#![cfg(test)] + +use primitives::{BuildStorage, testing::{Digest, DigestItem, Header}}; +use runtime_io; +use substrate_primitives::{H256, Blake2Hasher}; +use {Trait, Module, consensus, system, timestamp}; + +impl_outer_origin!{ + pub enum Origin for Test {} +} + +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Test; + +impl consensus::Trait for Test { + const NOTE_OFFLINE_POSITION: u32 = 1; + type Log = DigestItem; + type SessionKey = u64; + type InherentOfflineReport = (); +} + +impl system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::primitives::traits::BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + type Event = (); + type Log = DigestItem; +} + +impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + + type Moment = u64; + type OnTimestampSet = Aura; +} + +impl Trait for Test { + type HandleReport = (); +} + +pub fn new_test_ext(authorities: Vec) -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_storage().unwrap().0; + t.extend(consensus::GenesisConfig::{ + code: vec![], + authorities, + }.build_storage().unwrap().0); + t.extend(timestamp::GenesisConfig::{ + period: 1, + }.build_storage().unwrap().0); + t.into() +} + +pub type System = system::Module; +pub type Aura = Module; diff --git a/substrate/srml/aura/src/tests.rs b/substrate/srml/aura/src/tests.rs new file mode 100644 index 0000000000..6f215b8fe2 --- /dev/null +++ b/substrate/srml/aura/src/tests.rs @@ -0,0 +1,79 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Tests for the module. + +#![cfg(test)] + +use mock::{System, Aura, new_test_ext}; +use primitives::traits::Header; +use runtime_io::with_externalities; +use parking_lot::Mutex; +use {AuraReport, HandleReport}; + +#[test] +fn aura_report_gets_skipped_correctly() { + let mut report = AuraReport { + start_slot: 0, + skipped: 30, + }; + + let mut validators = vec![0; 10]; + report.punish(10, |idx, count| validators[idx] += count); + + assert_eq!(validators, vec![3; 10]); + + let mut validators = vec![0; 4]; + report.punish(4, |idx, count| validators[idx] += count); + assert_eq!(validators, vec![8, 8, 7, 7]); + + report.start_slot = 2; + report.punish(4, |idx, count| validators[idx] += count); + assert_eq!(validators, vec![15, 15, 15, 15]); +} + +#[test] +fn aura_reports_offline() { + lazy_static! { + static ref SLASH_COUNTS: Mutex> = Mutex::new(vec![0; 4]); + } + + struct HandleTestReport; + impl HandleReport for HandleTestReport { + fn handle_report(report: AuraReport) { + let mut counts = SLASH_COUNTS.lock(); + report.punish(counts.len(), |idx, count| counts[idx] += count); + } + } + + with_externalities(&mut new_test_ext(vec![0, 1, 2, 3]), || { + System::initialise(&1, &Default::default(), &Default::default()); + let slot_duration = Aura::slot_duration(); + + Aura::on_timestamp_set::(5 * slot_duration, slot_duration); + let header = System::finalise(); + + // no slashing when last step was 0. + assert_eq!(SLASH_COUNTS.lock().as_slice(), &[0, 0, 0, 0]); + + System::initialise(&2, &header.hash(), &Default::default()); + Aura::on_timestamp_set::(8 * slot_duration, slot_duration); + let _header = System::finalise(); + + // Steps 6 and 7 were skipped. + assert_eq!(SLASH_COUNTS.lock().as_slice(), &[0, 0, 1, 1]); + }); +} diff --git a/substrate/srml/consensus/src/lib.rs b/substrate/srml/consensus/src/lib.rs index 0502e505ea..2c72642cf6 100644 --- a/substrate/srml/consensus/src/lib.rs +++ b/substrate/srml/consensus/src/lib.rs @@ -61,12 +61,63 @@ impl StorageVec for AuthorityStorageVec { pub type KeyValue = (Vec, Vec); -pub trait OnOfflineValidator { - fn on_offline_validator(validator_index: usize); +/// Handling offline validator reports in a generic way. +pub trait OnOfflineReport { + fn handle_report(offline: Offline); } -impl OnOfflineValidator for () { - fn on_offline_validator(_validator_index: usize) {} +impl OnOfflineReport for () { + fn handle_report(_: T) {} +} + +/// Describes the offline-reporting extrinsic. +pub trait InherentOfflineReport { + /// The report data type passed to the runtime during block authorship. + type Inherent: codec::Codec + Parameter; + + /// Whether an inherent is empty and doesn't need to be included. + fn is_empty(inherent: &Self::Inherent) -> bool; + + /// Handle the report. + fn handle_report(report: Self::Inherent); + + /// Whether two reports are compatible. + fn check_inherent(contained: &Self::Inherent, expected: &Self::Inherent) -> Result<(), &'static str>; +} + +impl InherentOfflineReport for () { + type Inherent = (); + + fn is_empty(_inherent: &()) -> bool { true } + fn handle_report(_: ()) { } + fn check_inherent(_: &(), _: &()) -> Result<(), &'static str> { + Err("Explicit reporting not allowed") + } +} + +/// A variant of the `OfflineReport` which is useful for instant-finality blocks. +/// +/// This assumes blocks are only finalized +pub struct InstantFinalityReportVec(::rstd::marker::PhantomData); + +impl>> InherentOfflineReport for InstantFinalityReportVec { + type Inherent = Vec; + + fn is_empty(inherent: &Self::Inherent) -> bool { inherent.is_empty() } + + fn handle_report(report: Vec) { + T::handle_report(report) + } + + fn check_inherent(contained: &Self::Inherent, expected: &Self::Inherent) -> Result<(), &'static str> { + contained.iter().try_for_each(|n| + if !expected.contains(n) { + Err("Node we believe online marked offline") + } else { + Ok(()) + } + ) + } } pub type Log = RawLog< @@ -111,7 +162,9 @@ pub trait Trait: system::Trait { type Log: From> + Into>; type SessionKey: Parameter + Default + MaybeSerializeDebug; - type OnOfflineValidator: OnOfflineValidator; + /// Defines the offline-report type of the trait. + /// Set to `()` if offline-reports aren't needed for this runtime. + type InherentOfflineReport: InherentOfflineReport; } decl_storage! { @@ -143,23 +196,20 @@ decl_module! { /// Report some misbehaviour. fn report_misbehavior(origin, _report: Vec) { ensure_signed(origin)?; - // TODO. + // TODO: requires extension trait. } /// Note the previous block's validator missed their opportunity to propose a block. - /// This only comes in if 2/3+1 of the validators agree that no proposal was submitted. - /// It's only relevant for the previous block. - fn note_offline(origin, offline_val_indices: Vec) { + fn note_offline(origin, offline: ::Inherent) { ensure_inherent(origin)?; + assert!( >::extrinsic_index() == Some(T::NOTE_OFFLINE_POSITION), "note_offline extrinsic must be at position {} in the block", T::NOTE_OFFLINE_POSITION ); - for validator_index in offline_val_indices.into_iter() { - T::OnOfflineValidator::on_offline_validator(validator_index as usize); - } + T::InherentOfflineReport::handle_report(offline); } /// Make some on-chain remark. @@ -239,29 +289,34 @@ impl Module { } impl ProvideInherent for Module { - type Inherent = Vec; + type Inherent = ::Inherent; type Call = Call; fn create_inherent_extrinsics(data: Self::Inherent) -> Vec<(u32, Self::Call)> { - vec![(T::NOTE_OFFLINE_POSITION, Call::note_offline(data))] + if ::is_empty(&data) { + vec![] + } else { + vec![(T::NOTE_OFFLINE_POSITION, Call::note_offline(data))] + } } fn check_inherent Option<&Self::Call>>( - block: &Block, data: Self::Inherent, extract_function: &F + block: &Block, expected: Self::Inherent, extract_function: &F ) -> result::Result<(), CheckInherentError> { let noted_offline = block - .extrinsics().get(T::NOTE_OFFLINE_POSITION as usize) + .extrinsics() + .get(T::NOTE_OFFLINE_POSITION as usize) .and_then(|xt| match extract_function(&xt) { - Some(Call::note_offline(ref x)) => Some(&x[..]), + Some(Call::note_offline(ref x)) => Some(x), _ => None, - }).unwrap_or(&[]); + }); - noted_offline.iter().try_for_each(|n| - if !data.contains(n) { - Err(CheckInherentError::Other("Online node marked offline".into())) - } else { - Ok(()) - } - ) + // REVIEW: perhaps we should be passing a `None` to check_inherent. + if let Some(noted_offline) = noted_offline { + ::check_inherent(¬ed_offline, &expected) + .map_err(|e| CheckInherentError::Other(e.into()))?; + } + + Ok(()) } } diff --git a/substrate/srml/consensus/src/mock.rs b/substrate/srml/consensus/src/mock.rs index 9957c0002f..5945a89018 100644 --- a/substrate/srml/consensus/src/mock.rs +++ b/substrate/srml/consensus/src/mock.rs @@ -34,7 +34,7 @@ impl Trait for Test { const NOTE_OFFLINE_POSITION: u32 = 1; type Log = DigestItem; type SessionKey = u64; - type OnOfflineValidator = (); + type InherentOfflineReport = ::InstantFinalityReportVec<()>; } impl system::Trait for Test { type Origin = Origin; diff --git a/substrate/srml/consensus/src/tests.rs b/substrate/srml/consensus/src/tests.rs index b823367c15..9c57739fbc 100644 --- a/substrate/srml/consensus/src/tests.rs +++ b/substrate/srml/consensus/src/tests.rs @@ -18,7 +18,7 @@ #![cfg(test)] -use primitives::{generic, testing, traits::OnFinalise}; +use primitives::{generic, testing, traits::{OnFinalise, ProvideInherent}}; use runtime_io::with_externalities; use substrate_primitives::H256; use mock::{Consensus, System, new_test_ext}; @@ -63,3 +63,12 @@ fn authorities_change_is_not_logged_when_changed_back_to_original() { }); }); } + +#[test] +fn offline_report_can_be_excluded() { + with_externalities(&mut new_test_ext(vec![1, 2, 3]), || { + System::initialise(&1, &Default::default(), &Default::default()); + assert!(Consensus::create_inherent_extrinsics(Vec::new()).is_empty()); + assert_eq!(Consensus::create_inherent_extrinsics(vec![0]).len(), 1); + }); +} diff --git a/substrate/srml/session/src/lib.rs b/substrate/srml/session/src/lib.rs index 56398823c1..8fc58feab2 100644 --- a/substrate/srml/session/src/lib.rs +++ b/substrate/srml/session/src/lib.rs @@ -261,7 +261,7 @@ mod tests { const NOTE_OFFLINE_POSITION: u32 = 1; type Log = DigestItem; type SessionKey = u64; - type OnOfflineValidator = (); + type InherentOfflineReport = (); } impl system::Trait for Test { type Origin = Origin; @@ -278,6 +278,7 @@ mod tests { impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; type Moment = u64; + type OnTimestampSet = (); } impl Trait for Test { type ConvertAccountIdToSessionKey = Identity; diff --git a/substrate/srml/staking/src/lib.rs b/substrate/srml/staking/src/lib.rs index 2d78ae5af7..4982b24de2 100644 --- a/substrate/srml/staking/src/lib.rs +++ b/substrate/srml/staking/src/lib.rs @@ -333,6 +333,11 @@ impl Module { } } + /// Get the current validators. + pub fn validators() -> Vec { + session::Module::::validators() + } + // PUBLIC MUTABLES (DANGEROUS) /// Slash a given validator by a specific amount. Removes the slash from their balance by preference, @@ -490,6 +495,38 @@ impl Module { >::put(Self::offline_slash().times(stake_range.1)); >::put(Self::session_reward().times(stake_range.1)); } + + /// Call when a validator is determined to be offline. `count` is the + /// number of offences the validator has committed. + pub fn on_offline_validator(v: T::AccountId, count: usize) { + for _ in 0..count { + let slash_count = Self::slash_count(&v); + >::insert(v.clone(), slash_count + 1); + let grace = Self::offline_slash_grace(); + + let event = if slash_count >= grace { + let instances = slash_count - grace; + let slash = Self::current_offline_slash() << instances; + let next_slash = slash << 1u32; + let _ = Self::slash_validator(&v, slash); + if instances >= Self::validator_preferences(&v).unstake_threshold + || Self::slashable_balance(&v) < next_slash + { + if let Some(pos) = Self::intentions().into_iter().position(|x| &x == &v) { + Self::apply_unstake(&v, pos) + .expect("pos derived correctly from Self::intentions(); \ + apply_unstake can only fail if pos wrong; \ + Self::intentions() doesn't change; qed"); + } + let _ = Self::apply_force_new_era(false); + } + RawEvent::OfflineSlash(v.clone(), slash) + } else { + RawEvent::OfflineWarning(v.clone(), slash_count) + }; + Self::deposit_event(event); + } + } } impl OnSessionChange for Module { @@ -514,33 +551,11 @@ impl balances::OnFreeBalanceZero for Module { } } -impl consensus::OnOfflineValidator for Module { - fn on_offline_validator(validator_index: usize) { - let v = >::validators()[validator_index].clone(); - let slash_count = Self::slash_count(&v); - >::insert(v.clone(), slash_count + 1); - let grace = Self::offline_slash_grace(); - - let event = if slash_count >= grace { - let instances = slash_count - grace; - let slash = Self::current_offline_slash() << instances; - let next_slash = slash << 1u32; - let _ = Self::slash_validator(&v, slash); - if instances >= Self::validator_preferences(&v).unstake_threshold - || Self::slashable_balance(&v) < next_slash - { - if let Some(pos) = Self::intentions().into_iter().position(|x| &x == &v) { - Self::apply_unstake(&v, pos) - .expect("pos derived correctly from Self::intentions(); \ - apply_unstake can only fail if pos wrong; \ - Self::intentions() doesn't change; qed"); - } - let _ = Self::apply_force_new_era(false); - } - RawEvent::OfflineSlash(v, slash) - } else { - RawEvent::OfflineWarning(v, slash_count) - }; - Self::deposit_event(event); +impl consensus::OnOfflineReport> for Module { + fn handle_report(reported_indices: Vec) { + for validator_index in reported_indices { + let v = >::validators()[validator_index as usize].clone(); + Self::on_offline_validator(v, 1); + } } } diff --git a/substrate/srml/staking/src/mock.rs b/substrate/srml/staking/src/mock.rs index 7a1956253c..a434b4510b 100644 --- a/substrate/srml/staking/src/mock.rs +++ b/substrate/srml/staking/src/mock.rs @@ -36,7 +36,7 @@ impl consensus::Trait for Test { const NOTE_OFFLINE_POSITION: u32 = 1; type Log = DigestItem; type SessionKey = u64; - type OnOfflineValidator = (); + type InherentOfflineReport = (); } impl system::Trait for Test { type Origin = Origin; @@ -65,6 +65,7 @@ impl session::Trait for Test { impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; type Moment = u64; + type OnTimestampSet = (); } impl Trait for Test { type OnRewardMinted = (); diff --git a/substrate/srml/staking/src/tests.rs b/substrate/srml/staking/src/tests.rs index 67e793f7bc..1e296ba53f 100644 --- a/substrate/srml/staking/src/tests.rs +++ b/substrate/srml/staking/src/tests.rs @@ -19,7 +19,6 @@ #![cfg(test)] use super::*; -use consensus::OnOfflineValidator; use runtime_io::with_externalities; use mock::{Balances, Session, Staking, System, Timestamp, Test, new_test_ext, Origin}; @@ -44,7 +43,7 @@ fn note_offline_should_work() { assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Balances::free_balance(&10), 70); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); + Staking::on_offline_validator(10, 1); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Balances::free_balance(&10), 50); assert!(Staking::forcing_new_era().is_none()); @@ -59,11 +58,11 @@ fn note_offline_exponent_should_work() { assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Balances::free_balance(&10), 150); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); + Staking::on_offline_validator(10, 1); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Balances::free_balance(&10), 130); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); + Staking::on_offline_validator(10, 1); assert_eq!(Staking::slash_count(&10), 2); assert_eq!(Balances::free_balance(&10), 90); assert!(Staking::forcing_new_era().is_none()); @@ -82,15 +81,15 @@ fn note_offline_grace_should_work() { assert_eq!(Balances::free_balance(&10), 70); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); + Staking::on_offline_validator(10, 1); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Balances::free_balance(&10), 70); assert_eq!(Staking::slash_count(&20), 0); assert_eq!(Balances::free_balance(&20), 70); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); - Staking::on_offline_validator(1); + Staking::on_offline_validator(10, 1); + Staking::on_offline_validator(20, 1); assert_eq!(Staking::slash_count(&10), 2); assert_eq!(Balances::free_balance(&10), 50); assert_eq!(Staking::slash_count(&20), 1); @@ -105,20 +104,20 @@ fn note_offline_force_unstake_session_change_should_work() { Balances::set_free_balance(&10, 70); Balances::set_free_balance(&20, 70); assert_ok!(Staking::stake(Origin::signed(1))); - + assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Balances::free_balance(&10), 70); assert_eq!(Staking::intentions(), vec![10, 20, 1]); assert_eq!(Session::validators(), vec![10, 20]); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); + Staking::on_offline_validator(10, 1); assert_eq!(Balances::free_balance(&10), 50); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Staking::intentions(), vec![10, 20, 1]); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); + Staking::on_offline_validator(10, 1); assert_eq!(Staking::intentions(), vec![1, 20]); assert_eq!(Balances::free_balance(&10), 10); assert!(Staking::forcing_new_era().is_some()); @@ -131,33 +130,33 @@ fn note_offline_auto_unstake_session_change_should_work() { Balances::set_free_balance(&10, 7000); Balances::set_free_balance(&20, 7000); assert_ok!(Staking::register_preferences(Origin::signed(10), 0.into(), ValidatorPrefs { unstake_threshold: 1, validator_payment: 0 })); - + assert_eq!(Staking::intentions(), vec![10, 20]); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); - Staking::on_offline_validator(1); + Staking::on_offline_validator(10, 1); + Staking::on_offline_validator(20, 1); assert_eq!(Balances::free_balance(&10), 6980); assert_eq!(Balances::free_balance(&20), 6980); assert_eq!(Staking::intentions(), vec![10, 20]); assert!(Staking::forcing_new_era().is_none()); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); - Staking::on_offline_validator(1); + Staking::on_offline_validator(10, 1); + Staking::on_offline_validator(20, 1); assert_eq!(Balances::free_balance(&10), 6940); assert_eq!(Balances::free_balance(&20), 6940); assert_eq!(Staking::intentions(), vec![20]); assert!(Staking::forcing_new_era().is_some()); System::set_extrinsic_index(1); - Staking::on_offline_validator(1); + Staking::on_offline_validator(20, 1); assert_eq!(Balances::free_balance(&10), 6940); assert_eq!(Balances::free_balance(&20), 6860); assert_eq!(Staking::intentions(), vec![20]); System::set_extrinsic_index(1); - Staking::on_offline_validator(1); + Staking::on_offline_validator(20, 1); assert_eq!(Balances::free_balance(&10), 6940); assert_eq!(Balances::free_balance(&20), 6700); assert_eq!(Staking::intentions(), vec![0u64; 0]); @@ -220,8 +219,8 @@ fn slashing_should_work() { System::set_block_number(7); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); - Staking::on_offline_validator(1); + Staking::on_offline_validator(10, 1); + Staking::on_offline_validator(20, 1); assert_eq!(Balances::total_balance(&10), 1); }); } @@ -235,7 +234,7 @@ fn staking_should_work() { assert_eq!(Staking::era_length(), 2); assert_eq!(Staking::validator_count(), 2); assert_eq!(Session::validators(), vec![10, 20]); - + assert_ok!(Staking::set_bonding_duration(2.into())); assert_eq!(Staking::bonding_duration(), 2); @@ -395,8 +394,8 @@ fn nominating_slashes_should_work() { System::set_block_number(5); System::set_extrinsic_index(1); - Staking::on_offline_validator(0); - Staking::on_offline_validator(1); + Staking::on_offline_validator(1, 1); + Staking::on_offline_validator(3, 1); assert_eq!(Balances::total_balance(&1), 0); //slashed assert_eq!(Balances::total_balance(&2), 20); //not slashed assert_eq!(Balances::total_balance(&3), 10); //slashed diff --git a/substrate/srml/timestamp/src/lib.rs b/substrate/srml/timestamp/src/lib.rs index 4b63a34b01..d89bb50a64 100644 --- a/substrate/srml/timestamp/src/lib.rs +++ b/substrate/srml/timestamp/src/lib.rs @@ -56,12 +56,32 @@ use runtime_primitives::traits::{ use system::ensure_inherent; use rstd::{result, ops::{Mul, Div}, vec::Vec}; +/// A trait which is called when the timestamp is set. +pub trait OnTimestampSet { + fn on_timestamp_set(moment: Moment); +} + +impl OnTimestampSet for () { + fn on_timestamp_set(_moment: Moment) { } +} + +impl OnTimestampSet for (A, B) + where A: OnTimestampSet, B: OnTimestampSet +{ + fn on_timestamp_set(moment: Moment) { + A::on_timestamp_set(moment.clone()); + B::on_timestamp_set(moment); + } +} + pub trait Trait: consensus::Trait + system::Trait { /// The position of the required timestamp-set extrinsic. const TIMESTAMP_SET_POSITION: u32; /// Type used for expressing timestamp. type Moment: Parameter + Default + SimpleArithmetic + Mul + Div; + /// Something which can be notified when the timestamp is set. Set this to `()` if not needed. + type OnTimestampSet: OnTimestampSet; } decl_module! { @@ -88,8 +108,10 @@ decl_module! { Self::now().is_zero() || now >= Self::now() + Self::block_period(), "Timestamp must increment by at least between sequential blocks" ); - ::Now::put(now); + ::Now::put(now.clone()); ::DidUpdate::put(true); + + >::on_timestamp_set(now); } fn on_finalise() { @@ -192,11 +214,12 @@ mod tests { const NOTE_OFFLINE_POSITION: u32 = 1; type Log = DigestItem; type SessionKey = u64; - type OnOfflineValidator = (); + type InherentOfflineReport = (); } impl Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; type Moment = u64; + type OnTimestampSet = (); } type Timestamp = Module;