diff --git a/Cargo.lock b/Cargo.lock index 7214eed86976e..8042fa41c2b3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,6 +454,7 @@ dependencies = [ "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", + "substrate-runtime-session 0.1.0", "substrate-runtime-staking 0.1.0", "substrate-runtime-support 0.1.0", "substrate-runtime-system 0.1.0", @@ -487,6 +488,7 @@ dependencies = [ "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", @@ -2747,6 +2749,7 @@ dependencies = [ "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", "substrate-runtime-io 0.1.0", @@ -2814,6 +2817,7 @@ dependencies = [ "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", @@ -2881,6 +2885,7 @@ dependencies = [ "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", diff --git a/README.adoc b/README.adoc index 3e11d84eb6fa6..b452f173d1c2b 100644 --- a/README.adoc +++ b/README.adoc @@ -4,9 +4,87 @@ Next-generation framework for blockchain innovation. ## Description -At its heart, Substrate is a combination of three technologies: WebAssembly, Libp2p and AfG Consensus. It is both a library for building new blockchains with and a "skeleton key" of a blockchain client, able to synchronise to any Substrate-based chain. +At its heart, Substrate is a combination of three technologies: WebAssembly, Libp2p and AfG Consensus. It is both a library for building new blockchains and a "skeleton key" of a blockchain client, able to synchronise to any Substrate-based chain. -Substrate chains have two distinct features that make them "next-generation": a dynamic, self-defining state-transition function and a progressive consensus algorithm with fast block production and adaptive, definite finality. The STF, encoded in WebAssembly, is known as the "runtime". This defines the `execute_block` function, and can specify everything from the staking algorithm, transaction semantics, logging mechanisms and governance procedures. Because the runtime is entirely dynamic all of these can be switched out or upgraded at any time. A Substrate chain is very much a "living organism". +Substrate chains have three distinct features that make them "next-generation": a dynamic, self-defining state-transition function, light-client functionality from day one and a progressive consensus algorithm with fast block production and adaptive, definite finality. The STF, encoded in WebAssembly, is known as the "runtime". This defines the `execute_block` function, and can specify everything from the staking algorithm, transaction semantics, logging mechanisms and procedures for replacing any aspect of itself or of the blockchain's state ("governance"). Because the runtime is entirely dynamic all of these can be switched out or upgraded at any time. A Substrate chain is very much a "living organism". + +## Usage + +Substrate is still an early stage project, and while it has already been used as the basis of major projects like Polkadot, using it is still a significant undertaking. In particular, you should have a good knowledge of blockchain concepts and basic cryptography. Terminology like header, block, client, hash, transaction and signature should be familiar. At present you will need a working knowledge of Rust to be able to do anything interesting (though eventually, we aim for this not to be the case). + +Substrate is designed to be used in one of three ways: + +1. Trivial: By running the Substrate binary `substrate` and configuring it with a genesis block that includes the current demonstration runtime. In this case, you just build Substrate, configure a JSON file and launch your own blockchain. This affords you the least amount of customisability, primarily allowing you to change the genesis parameters of the various included runtime modules such as balances, staking, block-period, fees and governance. + +2. Modular: By hacking together modules from the Substrate Runtime Module Library into a new runtime and possibly altering or reconfiguring the Substrate client's block authoring logic. This affords you a very large amount of freedom over your own blockchain's logic, letting you change datatypes, add or remove modules and, crucially, add your own modules. Much can be changed without touching the block-authoring logic (since it is generic). If this is the case, then the existing Substrate binary can be used for block authoring and syncing. If the block authoring logic needs to be tweaked, then a new altered block-authoring binary must be built as a separate project and used by validators. This is how the Polkadot relay chain is built and should suffice for almost all circumstances in the near to mid-term. + +3. Generic: The entire Substrate Runtime Module Library can be ignored and the entire runtime designed and implemented from scratch. If desired, this can be done in a language other than Rust, providing it can target WebAssembly. If the runtime can be made to be compatible with the existing client's block authoring logic, then you can simply construct a new genesis block from your Wasm blob and launch your chain with the existing Rust-based Substrate client. If not, then you'll need to alter the client's block authoring logic accordingly. This is probably a useless option for most projects right now, but provides complete flexibility allowing for a long-term far-reaching upgrade path for the Substrate paradigm. + +### The Basics of Substrate + +Substrate is a blockchain platform with a completely generic state transition function. That said, it does come with both standards and conventions (particularly regarding the Runtime Module Library) regarding underlying datastructures. Roughly speaking, these core datatypes correspond to as `trait`s in terms of the actual non-negotiable standard and generic `struct`s in terms of the convention. + +``` +Header := Parent + ExtrinsicsRoot + StorageRoot + Digest +Block := Header + Extrinsics + Justifications +``` + +### Extrinsics + +Extrinsics in Substrate are pieces of information from "the outside world" that are contained in the blocks of the chain. You might think "ahh, that means *transactions*": in fact, no. Extrinsics fall into two broad categories of which only one is *transactions*. The other is known as *inherents*. The difference between these two is that transactions are signed and gossipped on the network and can be deemed useful *per se*. This fits the mould of what you would call transactions in Bitcoin or Ethereum. + +Inherents, meanwhile, are not passed on the network and are not signed. They represent data which describes the environment but which cannot call upon anything to prove it per se such as a signature. Rather they are assumed to be "true" simply because a sufficiently large number of validators have agreed on them being reasonable. + +To give an example, there is the timestamp inherent which sets the current timestamp of the block. This is not a fixed part of Substrate, but does come as part of the Substrate Runtime Module Library to be used as desired. No signature could fundamentally prove that a block were authored at a given time in quite the same way that a signature can "prove" the desire to spend some particular funds. Rather, it is the business of each validator to ensure that they believe the timestamp is set to something reasonable before they agree that the block candidate is valid. + +Other examples include the parachain-heads extrinsic in Polkadot and the "note-missed-proposal" extrinsic used in the Substrate Runtime Module Library to determine and punish or deactivate offline validators. + +### Runtime and API + +Substrate chains all have a runtime. The runtime is a WebAssembly "blob" that includes a number of entry-points. Some entry-points are required as part of the underlying Substrate specification. Others are merely convention and required for the default implemnentation of the Substrate client to be able to author blocks. In short these two sets are: + +The runtime is API entry points are expected to be in the runtime's `api` module. There is a specific ABI based upon the Substrate Simple Codec (`codec`) which is used to encode and decode the arguments for these functions and specify where and how they should passed. A special macro is provided called `impl_stubs` which prepares all functionality for marshalling arguments and basicall yjust allows you to write the functions as you would normally (except for the fact that there must be example one argument - tuples are allowed to workaround). + +Here's the Polkadot API implementation as of PoC-2: + +```rust +pub mod api { + impl_stubs!( + + // Standard: Required. + version => |()| super::Version::version(), + authorities => |()| super::Consensus::authorities(), + execute_block => |block| super::Executive::execute_block(block), + + // Conventional: Needed for Substrate client's reference block authoring logic to work. + initialise_block => |header| super::Executive::initialise_block(&header), + apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic), + finalise_block => |()| super::Executive::finalise_block(), + inherent_extrinsics => |inherent| super::inherent_extrinsics(inherent), + + // Non-standard (Polkadot-specific). This just exposes some stuff that Polkadot client + // finds useful. + validator_count => |()| super::Session::validator_count(), + validators => |()| super::Session::validators() + ); +} +``` + +As you can see, at the minimum there are only three API calls to implement. If you want to reuse as much of Substrate's reference block authoring client code, then you'll want to provide the next four entrypoints (though three of them you probably already implemented as part of `execute_block`). + +Of the first three, there is `execute_block`, which contains the actions to be taken to execute a block and pretty much defines the blockchain. Then there is `authorities` which tells the AfG consensus algorithm sitting in the Substrate client who the given authorities (known as "validators" in some contexts) are that can finalise the next block. Finally, there is `version`, which is a fairly sophisticated version identifier. This includes a key distinction between *specification version* and *authoring version*, with the former essentially versioning the logic of `execute_block` and the latter versioning only the logic of `inherent_extrinsics` and core aspects of extrinsic validity. + +### Inherent Extrinsics + +The Substrate Runtime Module Library includes functionality for timestamps and slashing. If used, these rely on "trusted" external information being passed in via inherent extrinsics. The Substrate reference block authoring client software will expect to be able to call into the runtime API with collated data (in the case of the reference Substrate authoring client, this is merely the current timestamp and which nodes were offline) in order to return the appropriate extrinsics ready for inclusion. If new inherent extrinsic types and data are to be used in a modified runtime, then it is this function (and its argument type) that would change. + +### Block-authoring Logic + +In Substrate, there is a major distinction between blockchain *syncing* and block *authoring* ("authoring" is a more general term for what is called "mining" in Bitcoin). The first case might be refered to as a "full node" (or "light node" - Substrate supports both): authoring necessarily requires a synced node and therefore all authoring clients must necessarily be able to synchronise. However, the reverse is not true. The primary functionality that authoring nodes have which is not in "sync nodes" is threefold: transaction queue logic, inherent transaction knowledge and BFT consensus logic. BFT consensus logic is provided as a core element of Substrate and can be ignored since it is only exposed in the SDK under the `authorities()` API entry. + +Transaction queue logic in Substrate is designed to be as generic as possible, allowing a runtime to express which transactions are fit for inclusion in a block through the `initialize_block` and `apply_extrinsic` calls. However, more subtle aspects like priotisation and replacement policy must currently be expressed "hard coded" as part of the blockchain's authoring code. That said, Substrate's reference implementation for a transaction queue which should be sufficient for an initial chain implementation. + +Inherent extrinsic knowledge is again somewhat generic, and the actual construction of the extrinsics is, by convention, delegated to the "soft code" in the runtime. If ever there needs to be additional extrinsic information in the chain, then both the block authoring logic will need to be altered to provide it into the runtime and the runtime's `inherent_extrinsics` call will need to use this extra information in order to construct any additional extrinsic transactions for inclusion in the block. ## Roadmap @@ -28,3 +106,72 @@ Substrate chains have two distinct features that make them "next-generation": a - Introduce basic but extensible transaction queue and block-builder and place them in the executable. - DAO runtime module - Audit + + +## Building + +== Hacking on Substrate + +If you'd actually like hack on Substrate, you can just grab the source code and +build it. Ensure you have Rust and the support software installed: + +[source, shell] +---- +curl https://sh.rustup.rs -sSf | sh +rustup update nightly +rustup target add wasm32-unknown-unknown --toolchain nightly +rustup update stable +cargo install --git https://github.com/alexcrichton/wasm-gc +sudo apt install cmake pkg-config libssl-dev git +---- + +Then, grab the Substrate source code: + +[source, shell] +---- +git clone https://github.com/paritytech/substrate.git +cd substrate +---- + +Then build the code: + +[source, shell] +---- +./scripts/build.sh # Builds the WebAssembly binaries +./scripts/build-demos.sh # Builds the WebAssembly binaries +cargo build # Builds all native code +---- + +You can run the tests if you like: + +[source, shell] +cargo test --all + +You can start a development chain with: + +[source, shell] +cargo run -- --dev + +=== Development + +You can run a simple single-node development "network" on your machine by +running in a terminal: + +[source, shell] +substrate --dev + +== Local Two-node Testnet + +If you want to see the multi-node consensus algorithm in action locally, then +you can create a local testnet. You'll need two terminals open. In one, run: + +[source, shell] +substrate --chain=local --validator --key Alice -d /tmp/alice + +and in the other, run: + +[source, shell] +substrate --chain=local --validator --key Bob -d /tmp/bob --port 30334 --bootnodes '/ip4/127.0.0.1/tcp/30333/p2p/ALICE_BOOTNODE_ID_HERE' + +Ensure you replace `ALICE_BOOTNODE_ID_HERE` with the node ID from the output of +the first terminal. diff --git a/demo/cli/src/lib.rs b/demo/cli/src/lib.rs index 70d97204f0a9f..6d2f936a58c7e 100644 --- a/demo/cli/src/lib.rs +++ b/demo/cli/src/lib.rs @@ -167,7 +167,6 @@ pub fn run(args: I) -> error::Result<()> where session: Some(SessionConfig { validators: vec![god_key.clone().into()], session_length: 720, // that's 1 hour per session. - broken_percent_late: 30, }), staking: Some(StakingConfig { current_era: 0, diff --git a/demo/executor/Cargo.toml b/demo/executor/Cargo.toml index c154a7c16501d..96b547af13f9b 100644 --- a/demo/executor/Cargo.toml +++ b/demo/executor/Cargo.toml @@ -20,6 +20,7 @@ demo-runtime = { path = "../runtime" } [dev-dependencies] substrate-keyring = { path = "../../substrate/keyring" } substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" } +substrate-runtime-session = { path = "../../substrate/runtime/session" } substrate-runtime-staking = { path = "../../substrate/runtime/staking" } substrate-runtime-system = { path = "../../substrate/runtime/system" } substrate-runtime-consensus = { path = "../../substrate/runtime/consensus" } diff --git a/demo/executor/src/lib.rs b/demo/executor/src/lib.rs index a75885bd911f2..cef697589e702 100644 --- a/demo/executor/src/lib.rs +++ b/demo/executor/src/lib.rs @@ -30,6 +30,7 @@ extern crate triehash; #[cfg(test)] extern crate substrate_keyring as keyring; #[cfg(test)] extern crate substrate_runtime_primitives as runtime_primitives; #[cfg(test)] extern crate substrate_runtime_support as runtime_support; +#[cfg(test)] extern crate substrate_runtime_session as session; #[cfg(test)] extern crate substrate_runtime_staking as staking; #[cfg(test)] extern crate substrate_runtime_system as system; #[cfg(test)] extern crate substrate_runtime_consensus as consensus; @@ -51,9 +52,10 @@ mod tests { use demo_primitives::{Hash, BlockNumber, AccountId}; use runtime_primitives::traits::Header as HeaderT; use runtime_primitives::{ApplyOutcome, ApplyError, ApplyResult, MaybeUnsigned}; - use {staking, system, consensus}; + use {staking, session, system, consensus}; + use system::{EventRecord, Phase}; use demo_runtime::{Header, Block, UncheckedExtrinsic, Extrinsic, Call, Concrete, Staking, - BuildStorage, GenesisConfig, SessionConfig, StakingConfig, BareExtrinsic}; + BuildStorage, GenesisConfig, SessionConfig, StakingConfig, BareExtrinsic, System, Event}; use ed25519::{Public, Pair}; const BLOATY_CODE: &[u8] = include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm"); @@ -195,7 +197,6 @@ mod tests { session: Some(SessionConfig { session_length: 2, validators: vec![One.to_raw_public().into(), Two.to_raw_public().into(), three], - broken_percent_late: 100, }), staking: Some(StakingConfig { sessions_per_era: 2, @@ -256,7 +257,7 @@ mod tests { // Blake // hex!("3437bf4b182ab17bb322af5c67e55f6be487a77084ad2b4e27ddac7242e4ad21").into(), // Keccak - hex!("856f39cc430b2ecc2b94f55f0df44b28a25ab3ed341a60bdf0b8f382616f675f").into(), + hex!("56c9a542e48ccf4e0821de301ea4384e87425604278b12a9db31c6d4e89ca51e").into(), vec![BareExtrinsic { signed: alice(), index: 0, @@ -272,7 +273,7 @@ mod tests { // Blake // hex!("741fcb660e6fa9f625fbcd993b49f6c1cc4040f5e0cc8727afdedf11fd3c464b").into(), // Keccak - hex!("32cb12103306811f4febf3a93c893ebd896f0df5bcf285912d406b43d9f041aa").into(), + hex!("166a2593d35f2d1bc87eca8cf2e320ed06759000a02aa88e51fa85b12c6f1267").into(), vec![ BareExtrinsic { signed: bob(), @@ -295,7 +296,7 @@ mod tests { // Blake // hex!("2c7231a9c210a7aa4bea169d944bc4aaacd517862b244b8021236ffa7f697991").into(), // Keccak - hex!("f7bdc5a3409738285c04585ec436c5c9c3887448f7cf1b5086664517681eb7c1").into(), + hex!("be186810570e437f0d803493fced9879207b064a0701fd8d68662b9563b4d33e").into(), vec![BareExtrinsic { signed: alice(), index: 0, @@ -313,6 +314,12 @@ mod tests { runtime_io::with_externalities(&mut t, || { assert_eq!(Staking::voting_balance(&alice()), 41); assert_eq!(Staking::voting_balance(&bob()), 69); + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: Event::staking(staking::RawEvent::NewAccount(bob(), 1, staking::NewAccountOutcome::NoHint)) + } + ]); }); executor().call(&mut t, COMPACT_CODE, "execute_block", &block2().0, true).0.unwrap(); @@ -320,6 +327,16 @@ mod tests { runtime_io::with_externalities(&mut t, || { assert_eq!(Staking::voting_balance(&alice()), 30); assert_eq!(Staking::voting_balance(&bob()), 78); + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::Finalization, + event: Event::session(session::RawEvent::NewSession(1)) + }, + EventRecord { + phase: Phase::Finalization, + event: Event::staking(staking::RawEvent::Reward(0)) + } + ]); }); } diff --git a/demo/runtime/Cargo.toml b/demo/runtime/Cargo.toml index 1e3252285d586..a13a87469cb61 100644 --- a/demo/runtime/Cargo.toml +++ b/demo/runtime/Cargo.toml @@ -11,6 +11,7 @@ serde = { version = "1.0", default_features = false } serde_derive = { version = "1.0", optional = true } safe-mix = { version = "1.0", default_features = false} substrate-codec = { path = "../../substrate/codec" } +substrate-codec-derive = { path = "../../substrate/codec/derive" } substrate-runtime-std = { path = "../../substrate/runtime-std" } substrate-runtime-io = { path = "../../substrate/runtime-io" } substrate-runtime-support = { path = "../../substrate/runtime-support" } diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 6ee577c8170eb..46101f8463595 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -34,6 +34,11 @@ extern crate serde_derive; #[cfg(feature = "std")] extern crate serde; +extern crate substrate_codec as codec; + +#[macro_use] +extern crate substrate_codec_derive; + extern crate substrate_runtime_std as rstd; extern crate substrate_runtime_consensus as consensus; extern crate substrate_runtime_council as council; @@ -90,6 +95,7 @@ impl system::Trait for Concrete { type Digest = generic::Digest>; type AccountId = AccountId; type Header = generic::Header>; + type Event = Event; } /// System module for this concrete runtime. @@ -121,18 +127,20 @@ impl Convert for SessionKeyConversion { } impl session::Trait for Concrete { - const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = SessionKeyConversion; type OnSessionChange = Staking; + type Event = Event; } /// Session module for this concrete runtime. pub type Session = session::Module; impl staking::Trait for Concrete { + const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type Balance = Balance; type AccountIndex = AccountIndex; - type OnAccountKill = (); + type OnFreeBalanceZero = (); + type Event = Event; } /// Staking module for this concrete runtime. @@ -152,6 +160,12 @@ pub type Council = council::Module; /// Council voting module for this concrete runtime. pub type CouncilVoting = council::voting::Module; +impl_outer_event! { + pub enum Event for Concrete { + session, staking + } +} + impl_outer_dispatch! { #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] @@ -211,6 +225,7 @@ pub mod api { impl_stubs!( version => |()| super::Version::version(), authorities => |()| super::Consensus::authorities(), + events => |()| super::System::events(), initialise_block => |header| super::Executive::initialise_block(&header), apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic), execute_block => |block| super::Executive::execute_block(block), diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock index 9d51df3715411..8f831386d6db7 100644 --- a/demo/runtime/wasm/Cargo.lock +++ b/demo/runtime/wasm/Cargo.lock @@ -101,6 +101,7 @@ dependencies = [ "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", "substrate-runtime-council 0.1.0", @@ -706,6 +707,7 @@ dependencies = [ "serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-std 0.1.0", @@ -765,6 +767,7 @@ dependencies = [ "serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", @@ -785,6 +788,7 @@ dependencies = [ "serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", @@ -830,6 +834,7 @@ dependencies = [ "serde 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-codec-derive 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", diff --git a/demo/runtime/wasm/Cargo.toml b/demo/runtime/wasm/Cargo.toml index 430f880cd2bc5..965c818788870 100644 --- a/demo/runtime/wasm/Cargo.toml +++ b/demo/runtime/wasm/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib"] [dependencies] integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" } safe-mix = { version = "1.0", default_features = false} +substrate-codec-derive = { path = "../../../substrate/codec/derive" } substrate-codec = { path = "../../../substrate/codec", default-features = false } substrate-primitives = { path = "../../../substrate/primitives", default-features = false } substrate-runtime-std = { path = "../../../substrate/runtime-std", default-features = false } diff --git a/substrate/runtime-support/src/lib.rs b/substrate/runtime-support/src/lib.rs index 75b4d4979a069..d5d663fbe05fb 100644 --- a/substrate/runtime-support/src/lib.rs +++ b/substrate/runtime-support/src/lib.rs @@ -83,3 +83,26 @@ macro_rules! assert_ok { assert_eq!($x, Ok($y)); } } + +#[macro_export] +macro_rules! impl_outer_event { + ($(#[$attr:meta])* pub enum $name:ident for $trait:ident { $( $module:ident ),* }) => { + // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. + #[derive(Clone, PartialEq, Eq, Encode, Decode)] + #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] + $(#[$attr])* + #[allow(non_camel_case_types)] + pub enum $name { + $( + $module($module::Event<$trait>), + )* + } + $( + impl From<$module::Event<$trait>> for $name { + fn from(x: $module::Event<$trait>) -> Self { + $name::$module(x) + } + } + )* + } +} diff --git a/substrate/runtime/contract/src/account_db.rs b/substrate/runtime/contract/src/account_db.rs index efa5c150c1ddd..e5ad0e2b88391 100644 --- a/substrate/runtime/contract/src/account_db.rs +++ b/substrate/runtime/contract/src/account_db.rs @@ -69,7 +69,7 @@ impl AccountDb for DirectAccountDb { if let staking::UpdateBalanceOutcome::AccountKilled = staking::Module::::set_free_balance_creating(&address, balance) { - // Account killed. This will ultimately lead to calling `OnAccountKill` callback + // Account killed. This will ultimately lead to calling `OnFreeBalanceZero` callback // which will make removal of CodeOf and StorageOf for this account. // In order to avoid writing over the deleted properties we `continue` here. continue; diff --git a/substrate/runtime/contract/src/lib.rs b/substrate/runtime/contract/src/lib.rs index 6761c03442bc6..bdeebcf7b29fd 100644 --- a/substrate/runtime/contract/src/lib.rs +++ b/substrate/runtime/contract/src/lib.rs @@ -247,8 +247,8 @@ impl Module { } } -impl staking::OnAccountKill for Module { - fn on_account_kill(who: &T::AccountId) { +impl staking::OnFreeBalanceZero for Module { + fn on_free_balance_zero(who: &T::AccountId) { >::remove(who); >::remove_prefix(who.clone()); } diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs index c248df4537f77..1f3d221ffe2c2 100644 --- a/substrate/runtime/contract/src/tests.rs +++ b/substrate/runtime/contract/src/tests.rs @@ -44,20 +44,23 @@ impl system::Trait for Test { type Digest = Digest; type AccountId = u64; type Header = Header; + type Event = (); } impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; type Moment = u64; } impl staking::Trait for Test { + const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type Balance = u64; type AccountIndex = u64; - type OnAccountKill = Contract; + type OnFreeBalanceZero = Contract; + type Event = (); } impl session::Trait for Test { - const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = Staking; + type Event = (); } impl Trait for Test { type Gas = u64; @@ -89,7 +92,6 @@ fn new_test_ext(existential_deposit: u64, gas_price: u64) -> runtime_io::TestExt session::GenesisConfig:: { session_length: 1, validators: vec![10, 20], - broken_percent_late: 100, }.build_storage() .unwrap(), ); diff --git a/substrate/runtime/council/src/lib.rs b/substrate/runtime/council/src/lib.rs index 0214d97dab570..8118075fd3f1b 100644 --- a/substrate/runtime/council/src/lib.rs +++ b/substrate/runtime/council/src/lib.rs @@ -651,16 +651,19 @@ mod tests { type Digest = Digest; type AccountId = u64; type Header = Header; + type Event = (); } impl session::Trait for Test { - const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = staking::Module; + type Event = (); } impl staking::Trait for Test { + const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type Balance = u64; type AccountIndex = u64; - type OnAccountKill = (); + type OnFreeBalanceZero = (); + type Event = (); } impl democracy::Trait for Test { type Proposal = Proposal; @@ -680,7 +683,6 @@ mod tests { t.extend(session::GenesisConfig::{ session_length: 1, //??? or 2? validators: vec![10, 20], - broken_percent_late: 100, }.build_storage().unwrap()); t.extend(staking::GenesisConfig::{ sessions_per_era: 1, diff --git a/substrate/runtime/democracy/src/lib.rs b/substrate/runtime/democracy/src/lib.rs index 20ad062247495..dfbd2180bbc74 100644 --- a/substrate/runtime/democracy/src/lib.rs +++ b/substrate/runtime/democracy/src/lib.rs @@ -393,16 +393,19 @@ mod tests { type Digest = Digest; type AccountId = u64; type Header = Header; + type Event = (); } impl session::Trait for Test { - const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = staking::Module; + type Event = (); } impl staking::Trait for Test { + const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type Balance = u64; type AccountIndex = u64; - type OnAccountKill = (); + type OnFreeBalanceZero = (); + type Event = (); } impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; @@ -421,7 +424,6 @@ mod tests { t.extend(session::GenesisConfig::{ session_length: 1, //??? or 2? validators: vec![10, 20], - broken_percent_late: 100, }.build_storage().unwrap()); t.extend(staking::GenesisConfig::{ sessions_per_era: 1, diff --git a/substrate/runtime/executive/Cargo.toml b/substrate/runtime/executive/Cargo.toml index 4b8307170ab94..20d913e92553a 100644 --- a/substrate/runtime/executive/Cargo.toml +++ b/substrate/runtime/executive/Cargo.toml @@ -8,6 +8,7 @@ hex-literal = "0.1.0" serde = { version = "1.0", default_features = false } serde_derive = { version = "1.0", optional = true } substrate-codec = { path = "../../codec", default_features = false } +substrate-codec-derive = { path = "../../codec/derive", default_features = false } substrate-runtime-std = { path = "../../runtime-std", default_features = false } substrate-runtime-io = { path = "../../runtime-io", default_features = false } substrate-runtime-support = { path = "../../runtime-support", default_features = false } @@ -29,6 +30,7 @@ std = [ "serde/std", "serde_derive", "substrate-codec/std", + "substrate-codec-derive/std", "substrate-runtime-primitives/std", "substrate-runtime-io/std", "substrate-runtime-system/std", diff --git a/substrate/runtime/executive/src/lib.rs b/substrate/runtime/executive/src/lib.rs index 57b9a993c4df0..476a5cf1ac217 100644 --- a/substrate/runtime/executive/src/lib.rs +++ b/substrate/runtime/executive/src/lib.rs @@ -24,12 +24,19 @@ extern crate serde; #[macro_use] extern crate serde_derive; -extern crate substrate_runtime_std as rstd; +#[cfg(test)] +#[macro_use] +extern crate substrate_codec_derive; + +#[cfg_attr(test, macro_use)] extern crate substrate_runtime_support as runtime_support; + +extern crate substrate_runtime_std as rstd; extern crate substrate_runtime_io as runtime_io; extern crate substrate_codec as codec; extern crate substrate_runtime_primitives as primitives; extern crate substrate_runtime_system as system; + #[cfg(test)] extern crate substrate_runtime_timestamp as timestamp; @@ -52,7 +59,6 @@ extern crate substrate_runtime_staking as staking; use rstd::prelude::*; use rstd::marker::PhantomData; use rstd::result; -use runtime_support::StorageValue; use primitives::traits::{self, Header, Zero, One, Checkable, Applyable, CheckEqual, Executable, MakePayment, Hash, AuxLookup}; use codec::{Codec, Encode}; @@ -125,6 +131,7 @@ impl< extrinsics.into_iter().for_each(Self::apply_extrinsic_no_note); // post-transactional book-keeping. + >::note_finished_extrinsics(); Finalisation::execute(); // any final checks @@ -134,6 +141,7 @@ impl< /// Finalise the block - it is up the caller to ensure that all header fields are valid /// except state-root. pub fn finalise_block() -> System::Header { + >::note_finished_extrinsics(); Finalisation::execute(); // setup extrinsics @@ -194,7 +202,7 @@ impl< // decode parameters and dispatch let r = xt.apply(); - >::put(>::get() + 1u32); + >::note_applied_extrinsic(); r.map(|_| internal::ApplyOutcome::Success).or_else(|e| Ok(internal::ApplyOutcome::Fail(e))) } @@ -232,6 +240,12 @@ mod tests { } } + impl_outer_event!{ + pub enum MetaEvent for Test { + session, staking + } + } + // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. #[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] pub struct Test; @@ -250,16 +264,19 @@ mod tests { type Digest = Digest; type AccountId = u64; type Header = Header; + type Event = MetaEvent; } impl session::Trait for Test { - const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = staking::Module; + type Event = MetaEvent; } impl staking::Trait for Test { + const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type Balance = u64; type AccountIndex = u64; - type OnAccountKill = (); + type OnFreeBalanceZero = (); + type Event = MetaEvent; } impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; @@ -319,7 +336,7 @@ mod tests { // Blake // state_root: hex!("02532989c613369596025dfcfc821339fc9861987003924913a5a1382f87034a").into(), // Keccak - state_root: hex!("ed456461b82664990b6ebd1caf1360056f6e8a062e73fada331e1c92cd81cad4").into(), + state_root: hex!("246ea6d86eefe3fc32f746fdcb1749a5f245570c70a04b43d08b5defac44505a").into(), extrinsics_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), digest: Digest { logs: vec![], }, }, @@ -353,7 +370,7 @@ mod tests { header: Header { parent_hash: [69u8; 32].into(), number: 1, - state_root: hex!("ed456461b82664990b6ebd1caf1360056f6e8a062e73fada331e1c92cd81cad4").into(), + state_root: hex!("246ea6d86eefe3fc32f746fdcb1749a5f245570c70a04b43d08b5defac44505a").into(), extrinsics_root: [0u8; 32].into(), digest: Digest { logs: vec![], }, }, @@ -369,7 +386,7 @@ mod tests { with_externalities(&mut t, || { Executive::initialise_block(&Header::new(1, H256::default(), H256::default(), [69u8; 32].into(), Digest::default())); assert!(Executive::apply_extrinsic(xt).is_err()); - assert_eq!(>::extrinsic_index(), 0); + assert_eq!(>::extrinsic_index(), Some(0)); }); } } diff --git a/substrate/runtime/primitives/src/traits.rs b/substrate/runtime/primitives/src/traits.rs index 7b9fe8568e3d4..2dc369b1f0baf 100644 --- a/substrate/runtime/primitives/src/traits.rs +++ b/substrate/runtime/primitives/src/traits.rs @@ -104,6 +104,14 @@ pub struct Identity; impl Convert for Identity { fn convert(a: T) -> T { a } } +pub struct Empty; +impl Convert for Empty { + fn convert(_: T) -> () { () } +} +pub struct UseInto; +impl> Convert for UseInto { + fn convert(t: T) -> S { t.into() } +} pub trait MaybeEmpty { fn is_empty(&self) -> bool; diff --git a/substrate/runtime/session/Cargo.toml b/substrate/runtime/session/Cargo.toml index 481653125b941..c2acfc1de2638 100644 --- a/substrate/runtime/session/Cargo.toml +++ b/substrate/runtime/session/Cargo.toml @@ -10,6 +10,7 @@ serde_derive = { version = "1.0", optional = true } safe-mix = { version = "1.0", default_features = false} substrate-keyring = { path = "../../keyring", optional = true } substrate-codec = { path = "../../codec", default_features = false } +substrate-codec-derive = { path = "../../codec/derive", default_features = false } substrate-primitives = { path = "../../primitives", default_features = false } substrate-runtime-std = { path = "../../runtime-std", default_features = false } substrate-runtime-io = { path = "../../runtime-io", default_features = false } @@ -27,6 +28,7 @@ std = [ "safe-mix/std", "substrate-keyring", "substrate-codec/std", + "substrate-codec-derive/std", "substrate-primitives/std", "substrate-runtime-std/std", "substrate-runtime-io/std", diff --git a/substrate/runtime/session/src/lib.rs b/substrate/runtime/session/src/lib.rs index 5583843fd960d..e5fa1a678a739 100644 --- a/substrate/runtime/session/src/lib.rs +++ b/substrate/runtime/session/src/lib.rs @@ -38,6 +38,9 @@ extern crate substrate_runtime_std as rstd; #[macro_use] extern crate substrate_runtime_support as runtime_support; +#[macro_use] +extern crate substrate_codec_derive; + extern crate substrate_runtime_io as runtime_io; extern crate substrate_codec as codec; extern crate substrate_runtime_primitives as primitives; @@ -46,7 +49,7 @@ extern crate substrate_runtime_system as system; extern crate substrate_runtime_timestamp as timestamp; use rstd::prelude::*; -use primitives::traits::{Zero, One, RefInto, MaybeEmpty, Executable, Convert, As}; +use primitives::traits::{Zero, One, RefInto, Executable, Convert, As}; use runtime_support::{StorageValue, StorageMap}; use runtime_support::dispatch::Result; @@ -64,20 +67,19 @@ impl OnSessionChange for () { } pub trait Trait: timestamp::Trait { - // the position of the required note_missed_proposal extrinsic. - const NOTE_MISSED_PROPOSAL_POSITION: u32; - type ConvertAccountIdToSessionKey: Convert; type OnSessionChange: OnSessionChange; + type Event: From> + Into<::Event>; } +pub type Event = RawEvent<::BlockNumber>; + decl_module! { pub struct Module; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum Call where aux: T::PublicAux { fn set_key(aux, key: T::SessionKey) -> Result = 0; - fn note_offline(aux, offline_val_indices: Vec) -> Result = 1; } #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -87,6 +89,19 @@ decl_module! { } } +/// An event in this module. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[derive(Encode, Decode, PartialEq, Eq, Clone)] +pub enum RawEvent { + /// New session has happened. Note that the argument is the session index, not the block number + /// as the type might suggest. + NewSession(BlockNumber), +} + +impl From> for () { + fn from(_: RawEvent) -> () { () } +} + decl_storage! { trait Store for Module; @@ -98,8 +113,6 @@ decl_storage! { pub CurrentIndex get(current_index): b"ses:ind" => required T::BlockNumber; // Timestamp when current session started. pub CurrentStart get(current_start): b"ses:current_start" => required T::Moment; - // Percent by which the session must necessarily finish late before we early-exit the session. - pub BrokenPercentLate get(broken_percent_late): b"ses:broken_percent_late" => required T::Moment; // Opinions of the current validator set about the activeness of their peers. // Gets cleared when the validator set changes. @@ -147,20 +160,6 @@ impl Module { Ok(()) } - /// Notes which of the validators appear to be online from the point of the view of the block author. - pub fn note_offline(aux: &T::PublicAux, offline_val_indices: Vec) -> Result { - assert!(aux.is_empty()); - assert!( - >::extrinsic_index() == T::NOTE_MISSED_PROPOSAL_POSITION, - "note_missed_proposal extrinsic must be at position {} in the block", - T::NOTE_MISSED_PROPOSAL_POSITION - ); - - let vs = Self::validators(); - >::put(offline_val_indices.into_iter().map(|i| vs[i as usize].clone()).collect::>()); - Ok(()) - } - // INTERNAL API (available to other runtime modules) /// Set the current set of validators. @@ -188,13 +187,21 @@ impl Module { } } + /// Deposit one of this module's events. + fn deposit_event(event: Event) { + >::deposit_event(::Event::from(event).into()); + } + /// Move onto next session: register the new authority set. pub fn rotate_session(is_final_block: bool, apply_rewards: bool) { let now = >::get(); let time_elapsed = now.clone() - Self::current_start(); + let session_index = >::get() + One::one(); + + Self::deposit_event(RawEvent::NewSession(session_index)); // Increment current session index. - >::put(>::get() + One::one()); + >::put(session_index); >::put(now); // Enact era length change. @@ -250,7 +257,6 @@ impl Executable for Module { pub struct GenesisConfig { pub session_length: T::BlockNumber, pub validators: Vec, - pub broken_percent_late: T::Moment, } #[cfg(any(feature = "std", test))] @@ -260,7 +266,6 @@ impl Default for GenesisConfig { GenesisConfig { session_length: T::BlockNumber::sa(1000), validators: vec![], - broken_percent_late: T::Moment::sa(30), } } } @@ -276,8 +281,7 @@ impl primitives::BuildStorage for GenesisConfig Self::hash(>::key()).to_vec() => self.session_length.encode(), Self::hash(>::key()).to_vec() => T::BlockNumber::sa(0).encode(), Self::hash(>::key()).to_vec() => T::Moment::zero().encode(), - Self::hash(>::key()).to_vec() => self.validators.encode(), - Self::hash(>::key()).to_vec() => self.broken_percent_late.encode() + Self::hash(>::key()).to_vec() => self.validators.encode() ]) } } @@ -308,15 +312,16 @@ mod tests { type Digest = Digest; type AccountId = u64; type Header = Header; + type Event = (); } impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; type Moment = u64; } impl Trait for Test { - const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = (); + type Event = (); } type System = system::Module; @@ -336,7 +341,6 @@ mod tests { t.extend(GenesisConfig::{ session_length: 2, validators: vec![1, 2, 3], - broken_percent_late: 30, }.build_storage().unwrap()); t.into() } diff --git a/substrate/runtime/staking/src/lib.rs b/substrate/runtime/staking/src/lib.rs index dd1e6fcc2d5ca..c0b4e8614bda1 100644 --- a/substrate/runtime/staking/src/lib.rs +++ b/substrate/runtime/staking/src/lib.rs @@ -77,6 +77,12 @@ const RECLAIM_INDEX_MAGIC: usize = 0x69; pub type Address = RawAddress<::AccountId, ::AccountIndex>; +pub type Event = RawEvent< + ::Balance, + ::AccountId, + ::AccountIndex +>; + #[cfg(test)] #[derive(Debug, PartialEq, Clone)] pub enum LockStatus { @@ -94,13 +100,13 @@ pub enum LockStatus { } /// The account was the given id was killed. -pub trait OnAccountKill { +pub trait OnFreeBalanceZero { /// The account was the given id was killed. - fn on_account_kill(who: &AccountId); + fn on_free_balance_zero(who: &AccountId); } -impl OnAccountKill for () { - fn on_account_kill(_who: &AccountId) {} +impl OnFreeBalanceZero for () { + fn on_free_balance_zero(_who: &AccountId) {} } /// Preference of what happens on a slash event. @@ -121,17 +127,21 @@ impl Default for SlashPreference { pub trait Trait: system::Trait + session::Trait { /// The allowed extrinsic position for `missed_proposal` inherent. -// const NOTE_MISSED_PROPOSAL_POSITION: u32; // TODO: uncomment when removed from session::Trait + const NOTE_MISSED_PROPOSAL_POSITION: u32; /// The balance of an account. type Balance: Parameter + SimpleArithmetic + Codec + Default + Copy + As + As + As; /// Type used for storing an account's index; implies the maximum number of accounts the system /// can hold. type AccountIndex: Parameter + Member + Codec + SimpleArithmetic + As + As + As + As + As + Copy; - /// A function which is invoked when the given account is dead. + /// A function which is invoked when the free-balance has fallen below the existential deposit and + /// has been reduced to zero. /// /// Gives a chance to clean up resources associated with the given account. - type OnAccountKill: OnAccountKill; + type OnFreeBalanceZero: OnFreeBalanceZero; + + /// The overarching event type. + type Event: From> + Into<::Event>; } decl_module! { @@ -159,6 +169,26 @@ decl_module! { } } +/// An event in this module. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[derive(Encode, Decode, PartialEq, Eq, Clone)] +pub enum RawEvent { + /// All validators have been rewarded by the given balance. + Reward(Balance), + /// One validator (and their nominators) has been given a offline-warning (they're still within + /// their grace). The accrued number of slashes is recorded, too. + OfflineWarning(AccountId, u32), + /// One validator (and their nominators) has been slashed by the given amount. + OfflineSlash(AccountId, Balance), + /// A new account was created. + NewAccount(AccountId, AccountIndex, NewAccountOutcome), + /// An account was reaped. + ReapedAccount(AccountId), +} +impl From> for () { + fn from(_: RawEvent) -> () { () } +} + decl_storage! { trait Store for Module; @@ -227,7 +257,7 @@ decl_storage! { // This is the only balance that matters in terms of most operations on tokens. It is // alone used to determine the balance when in the contract execution environment. When this // balance falls below the value of `ExistentialDeposit`, then the "current account" is - // deleted: specifically, `Bondage` and `FreeBalance`. Furthermore, `OnAccountKill` callback + // deleted: specifically, `Bondage` and `FreeBalance`. Furthermore, `OnFreeBalanceZero` callback // is invoked, giving a chance to external modules to cleanup data associated with // the deleted account. // @@ -253,7 +283,10 @@ decl_storage! { pub Bondage get(bondage): b"sta:bon:" => default map [ T::AccountId => T::BlockNumber ]; } -enum NewAccountOutcome { +/// Whatever happened about the hint given when creating the new account. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] +#[derive(Encode, Decode, PartialEq, Eq, Clone, Copy)] +pub enum NewAccountOutcome { NoHint, GoodHint, BadHint, @@ -485,7 +518,7 @@ impl Module { fn note_missed_proposal(aux: &T::PublicAux, offline_val_indices: Vec) -> Result { assert!(aux.is_empty()); assert!( - >::extrinsic_index() == T::NOTE_MISSED_PROPOSAL_POSITION, + >::extrinsic_index() == Some(T::NOTE_MISSED_PROPOSAL_POSITION), "note_missed_proposal extrinsic must be at position {} in the block", T::NOTE_MISSED_PROPOSAL_POSITION ); @@ -496,7 +529,7 @@ impl Module { >::insert(v.clone(), slash_count + 1); let grace = Self::offline_slash_grace(); - if slash_count >= grace { + let event = if slash_count >= grace { let instances = slash_count - grace; let slash = Self::early_era_slash() << instances; let next_slash = slash << 1u32; @@ -512,7 +545,11 @@ impl Module { } let _ = Self::force_new_era(false); } - } + RawEvent::OfflineSlash(v, slash) + } else { + RawEvent::OfflineWarning(v, slash_count) + }; + Self::deposit_event(event); } Ok(()) @@ -520,6 +557,11 @@ impl Module { // PRIV DISPATCH + /// Deposit one of this module's events. + fn deposit_event(event: Event) { + >::deposit_event(::Event::from(event).into()); + } + /// Set the number of sessions in an era. fn set_sessions_per_era(new: T::BlockNumber) -> Result { >::put(&new); @@ -806,6 +848,7 @@ impl Module { for v in >::validators().iter() { Self::reward_validator(v, reward); } + Self::deposit_event(RawEvent::Reward(reward)); } let session_index = >::current_index(); @@ -911,7 +954,9 @@ impl Module { try_set[item_index] = who.clone(); >::insert(set_index, try_set); - return NewAccountOutcome::GoodHint; + Self::deposit_event(RawEvent::NewAccount(who.clone(), try_index, NewAccountOutcome::GoodHint)); + + return NewAccountOutcome::GoodHint } } NewAccountOutcome::BadHint @@ -931,6 +976,8 @@ impl Module { set_index += One::one(); }; + let index = T::AccountIndex::sa(set_index.as_() * ENUM_SET_SIZE + set.len()); + // update set. set.push(who.clone()); @@ -942,18 +989,28 @@ impl Module { // write set. >::insert(set_index, set); + Self::deposit_event(RawEvent::NewAccount(who.clone(), index, ret)); + ret } + fn reap_account(who: &T::AccountId) { + >::remove(who); + Self::deposit_event(RawEvent::ReapedAccount(who.clone())); + } + /// Kill an account's free portion. fn on_free_too_low(who: &T::AccountId) { Self::decrease_total_stake_by(Self::free_balance(who)); >::remove(who); >::remove(who); - T::OnAccountKill::on_account_kill(who); + + // TODO: rename this to on_free_account_kill or some such, since it only pertains to removing the + // free-balance part of the account, not the whole thing. + T::OnFreeBalanceZero::on_free_balance_zero(who); if Self::reserved_balance(who).is_zero() { - >::remove(who); + Self::reap_account(who); } } @@ -961,8 +1018,9 @@ impl Module { fn on_reserved_too_low(who: &T::AccountId) { Self::decrease_total_stake_by(Self::reserved_balance(who)); >::remove(who); + if Self::free_balance(who).is_zero() { - >::remove(who); + Self::reap_account(who); } } diff --git a/substrate/runtime/staking/src/mock.rs b/substrate/runtime/staking/src/mock.rs index 2efeab4d30743..7b3097010d7eb 100644 --- a/substrate/runtime/staking/src/mock.rs +++ b/substrate/runtime/staking/src/mock.rs @@ -43,20 +43,23 @@ impl system::Trait for Test { type Digest = Digest; type AccountId = u64; type Header = Header; + type Event = (); } impl session::Trait for Test { - const NOTE_MISSED_PROPOSAL_POSITION: u32 = 0; type ConvertAccountIdToSessionKey = Identity; type OnSessionChange = Staking; + type Event = (); } impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; type Moment = u64; } impl Trait for Test { + const NOTE_MISSED_PROPOSAL_POSITION: u32 = 1; type Balance = u64; type AccountIndex = u64; - type OnAccountKill = (); + type OnFreeBalanceZero = (); + type Event = (); } pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool, reward: u64) -> runtime_io::TestExternalities { @@ -73,7 +76,6 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64 t.extend(session::GenesisConfig::{ session_length, validators: vec![10, 20], - broken_percent_late: 30, }.build_storage().unwrap()); t.extend(GenesisConfig::{ sessions_per_era, diff --git a/substrate/runtime/staking/src/tests.rs b/substrate/runtime/staking/src/tests.rs index 122b9202dde6b..809838379ae12 100644 --- a/substrate/runtime/staking/src/tests.rs +++ b/substrate/runtime/staking/src/tests.rs @@ -28,6 +28,7 @@ fn note_null_missed_proposal_should_work() { assert_eq!(Staking::offline_slash_grace(), 0); assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Staking::free_balance(&10), 1); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![])); assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Staking::free_balance(&10), 1); @@ -42,6 +43,7 @@ fn note_missed_proposal_should_work() { assert_eq!(Staking::offline_slash_grace(), 0); assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Staking::free_balance(&10), 70); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0])); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Staking::free_balance(&10), 50); @@ -56,9 +58,11 @@ fn note_missed_proposal_exponent_should_work() { assert_eq!(Staking::offline_slash_grace(), 0); assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Staking::free_balance(&10), 150); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0])); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Staking::free_balance(&10), 130); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0])); assert_eq!(Staking::slash_count(&10), 2); assert_eq!(Staking::free_balance(&10), 90); @@ -77,12 +81,14 @@ fn note_missed_proposal_grace_should_work() { assert_eq!(Staking::slash_count(&10), 0); assert_eq!(Staking::free_balance(&10), 70); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0])); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Staking::free_balance(&10), 70); assert_eq!(Staking::slash_count(&20), 0); assert_eq!(Staking::free_balance(&20), 70); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1])); assert_eq!(Staking::slash_count(&10), 2); assert_eq!(Staking::free_balance(&10), 50); @@ -104,11 +110,13 @@ fn note_missed_proposal_force_unstake_session_change_should_work() { assert_eq!(Staking::intentions(), vec![10, 20, 1]); assert_eq!(Session::validators(), vec![10, 20]); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0])); assert_eq!(Staking::free_balance(&10), 50); assert_eq!(Staking::slash_count(&10), 1); assert_eq!(Staking::intentions(), vec![10, 20, 1]); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0])); assert_eq!(Staking::intentions(), vec![1, 20]); assert_eq!(Staking::free_balance(&10), 10); @@ -125,23 +133,27 @@ fn note_missed_proposal_auto_unstake_session_change_should_work() { assert_eq!(Staking::intentions(), vec![10, 20]); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1])); assert_eq!(Staking::free_balance(&10), 6980); assert_eq!(Staking::free_balance(&20), 6980); assert_eq!(Staking::intentions(), vec![10, 20]); assert!(Staking::forcing_new_era().is_none()); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1])); assert_eq!(Staking::free_balance(&10), 6940); assert_eq!(Staking::free_balance(&20), 6940); assert_eq!(Staking::intentions(), vec![20]); assert!(Staking::forcing_new_era().is_some()); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![1])); assert_eq!(Staking::free_balance(&10), 6940); assert_eq!(Staking::free_balance(&20), 6860); assert_eq!(Staking::intentions(), vec![20]); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![1])); assert_eq!(Staking::free_balance(&10), 6940); assert_eq!(Staking::free_balance(&20), 6700); @@ -213,6 +225,7 @@ fn slashing_should_work() { assert_eq!(Staking::voting_balance(&10), 21); System::set_block_number(7); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1])); assert_eq!(Staking::voting_balance(&10), 1); }); @@ -433,6 +446,7 @@ fn nominating_slashes_should_work() { assert_eq!(Staking::voting_balance(&4), 40); System::set_block_number(5); + ::system::ExtrinsicIndex::::put(1); assert_ok!(Staking::note_missed_proposal(&Default::default(), vec![0, 1])); assert_eq!(Staking::voting_balance(&1), 0); assert_eq!(Staking::voting_balance(&2), 20); diff --git a/substrate/runtime/system/Cargo.toml b/substrate/runtime/system/Cargo.toml index 11d470ffd203c..28388bd5390f9 100644 --- a/substrate/runtime/system/Cargo.toml +++ b/substrate/runtime/system/Cargo.toml @@ -9,6 +9,7 @@ serde = { version = "1.0", default_features = false } serde_derive = { version = "1.0", optional = true } safe-mix = { version = "1.0", default_features = false} substrate-codec = { path = "../../codec", default_features = false } +substrate-codec-derive = { path = "../../codec/derive", default_features = false } substrate-primitives = { path = "../../primitives", default_features = false } substrate-runtime-std = { path = "../../runtime-std", default_features = false } substrate-runtime-io = { path = "../../runtime-io", default_features = false } @@ -22,6 +23,7 @@ std = [ "serde_derive", "safe-mix/std", "substrate-codec/std", + "substrate-codec-derive/std", "substrate-primitives/std", "substrate-runtime-std/std", "substrate-runtime-io/std", diff --git a/substrate/runtime/system/src/lib.rs b/substrate/runtime/system/src/lib.rs index 60dd6059b4070..1c4d39da01ed5 100644 --- a/substrate/runtime/system/src/lib.rs +++ b/substrate/runtime/system/src/lib.rs @@ -19,6 +19,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(any(feature = "std", test))] +extern crate substrate_primitives; + #[cfg_attr(any(feature = "std", test), macro_use)] extern crate substrate_runtime_std as rstd; @@ -32,8 +35,11 @@ extern crate serde; #[macro_use] extern crate serde_derive; -extern crate substrate_runtime_io as runtime_io; +#[macro_use] +extern crate substrate_codec_derive; + extern crate substrate_codec as codec; +extern crate substrate_runtime_io as runtime_io; extern crate substrate_runtime_primitives as primitives; extern crate safe_mix; @@ -74,26 +80,50 @@ pub trait Trait: Eq + Clone { Hash = Self::Hash, Digest = Self::Digest >; + type Event: Parameter + Member; } decl_module! { pub struct Module; } +/// A phase of a block's execution. +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone, Debug))] +pub enum Phase { + /// Applying an extrinsic. + ApplyExtrinsic(u32), + /// The end. + Finalization, +} + +/// Record of an event happening. +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone, Debug))] +pub struct EventRecord { + /// The phase of the block it happened in. + pub phase: Phase, + /// The event itself. + pub event: E, +} + decl_storage! { trait Store for Module; pub AccountNonce get(account_nonce): b"sys:non" => default map [ T::AccountId => T::Index ]; pub BlockHash get(block_hash): b"sys:old" => required map [ T::BlockNumber => T::Hash ]; - pub ExtrinsicIndex get(extrinsic_index): b"sys:xti" => required u32; - pub ExtrinsicData get(extrinsic_data): b"sys:xtd" => required map [ u32 => Vec ]; + ExtrinsicCount: b"sys:extrinsic_count" => u32; + pub ExtrinsicIndex get(extrinsic_index): b"sys:xti" => u32; + ExtrinsicData get(extrinsic_data): b"sys:xtd" => required map [ u32 => Vec ]; RandomSeed get(random_seed): b"sys:rnd" => required T::Hash; // The current block number being processed. Set by `execute_block`. Number get(block_number): b"sys:num" => required T::BlockNumber; ParentHash get(parent_hash): b"sys:pha" => required T::Hash; ExtrinsicsRoot get(extrinsics_root): b"sys:txr" => required T::Hash; Digest get(digest): b"sys:dig" => default T::Digest; + + Events get(events): b"sys:events" => default Vec>; } impl Module { @@ -105,19 +135,23 @@ impl Module { >::insert(*number - One::one(), parent_hash); >::put(txs_root); >::put(Self::calculate_random()); - >::put(0); + >::put(0u32); + >::kill(); } /// Remove temporary "environment" entries in storage. pub fn finalise() -> T::Header { >::kill(); - >::kill(); + >::kill(); let number = >::take(); let parent_hash = >::take(); let digest = >::take(); let extrinsics_root = >::take(); let storage_root = T::Hashing::storage_root(); + + // > stays to be inspected by the client. + ::new(number, extrinsics_root, storage_root, parent_hash, digest) } @@ -128,6 +162,14 @@ impl Module { >::put(l); } + /// Deposits an event onto this block's event record. + pub fn deposit_event(event: T::Event) { + let phase = >::get().map_or(Phase::Finalization, |c| Phase::ApplyExtrinsic(c)); + let mut events = Self::events(); + events.push(EventRecord { phase, event }); + >::put(events); + } + /// Calculate the current block's random seed. fn calculate_random() -> T::Hash { assert!(Self::block_number() > Zero::zero(), "Block number may never be zero"); @@ -180,12 +222,24 @@ impl Module { /// Note what the extrinsic data of the current extrinsic index is. If this is called, then /// ensure `derive_extrinsics` is also called before block-building is completed. pub fn note_extrinsic(encoded_xt: Vec) { - >::insert(Self::extrinsic_index(), encoded_xt); + >::insert(>::get().unwrap_or_default(), encoded_xt); + } + + /// To be called immediately after an extrinsic has been applied. + pub fn note_applied_extrinsic() { + >::put(>::get().unwrap_or_default() + 1u32); + } + + /// To be called immediately after `note_applied_extrinsic` of the last extrinsic of the block + /// has been called. + pub fn note_finished_extrinsics() { + >::put(>::get().unwrap_or_default()); + >::kill(); } /// Remove all extrinsics data and save the extrinsics trie root. pub fn derive_extrinsics() { - let extrinsics = (0..Self::extrinsic_index()).map(>::take).collect(); + let extrinsics = (0..>::get().unwrap_or_default()).map(>::take).collect(); let xts_root = extrinsics_data_root::(extrinsics); >::put(xts_root); } @@ -219,3 +273,57 @@ impl primitives::BuildStorage for GenesisConfig ]) } } + +#[cfg(test)] +mod tests { + use super::*; + use runtime_io::with_externalities; + use substrate_primitives::H256; + use primitives::BuildStorage; + use primitives::traits::BlakeTwo256; + use primitives::testing::{Digest, Header}; + + #[derive(Clone, Eq, PartialEq)] + pub struct Test; + impl Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + type Event = u16; + } + + type System = Module; + + fn new_test_ext() -> runtime_io::TestExternalities { + GenesisConfig::::default().build_storage().unwrap().into() + } + + #[test] + fn deposit_event_should_work() { + with_externalities(&mut new_test_ext(), || { + System::initialise(&1, &[0u8; 32].into(), &[0u8; 32].into()); + System::note_finished_extrinsics(); + System::deposit_event(1u16); + System::finalise(); + assert_eq!(System::events(), vec![EventRecord { phase: Phase::Finalization, event: 1u16 }]); + + System::initialise(&2, &[0u8; 32].into(), &[0u8; 32].into()); + System::deposit_event(42u16); + System::note_applied_extrinsic(); + System::deposit_event(69u16); + System::note_applied_extrinsic(); + System::note_finished_extrinsics(); + System::deposit_event(3u16); + System::finalise(); + assert_eq!(System::events(), vec![ + EventRecord { phase: Phase::ApplyExtrinsic(0), event: 42u16 }, + EventRecord { phase: Phase::ApplyExtrinsic(1), event: 69u16 }, + EventRecord { phase: Phase::Finalization, event: 3u16 } + ]); + }); + } +} diff --git a/substrate/runtime/timestamp/src/lib.rs b/substrate/runtime/timestamp/src/lib.rs index 21a1e8ee84513..3591f9489e2ed 100644 --- a/substrate/runtime/timestamp/src/lib.rs +++ b/substrate/runtime/timestamp/src/lib.rs @@ -80,7 +80,7 @@ impl Module { assert!(aux.is_empty()); assert!(!::DidUpdate::exists(), "Timestamp must be updated only once in the block"); assert!( - >::extrinsic_index() == T::TIMESTAMP_SET_POSITION, + >::extrinsic_index() == Some(T::TIMESTAMP_SET_POSITION), "Timestamp extrinsic must be at position {} in the block", T::TIMESTAMP_SET_POSITION ); @@ -158,6 +158,7 @@ mod tests { type Digest = Digest; type AccountId = u64; type Header = Header; + type Event = (); } impl consensus::Trait for Test { type PublicAux = u64;