Skip to content
Snippets Groups Projects
Commit 68b2a09d authored by Dan Forbes's avatar Dan Forbes Committed by GitHub
Browse files

Augmented node template docs (#6721)

parent 9acb321f
Branches
No related merge requests found
......@@ -8,8 +8,8 @@ Follow these steps to prepare a local Substrate development environment :hammer_
### Simple Setup
Install all the required dependencies with a single command (be patient, this can take up
to 30 minutes).
Install all the required dependencies with a single command (be patient, this can take up to 30
minutes).
```bash
curl https://getsubstrate.io -sSf | bash -s -- --fast
......@@ -17,7 +17,8 @@ curl https://getsubstrate.io -sSf | bash -s -- --fast
### Manual Setup
Find manual setup instructions at the [Substrate Developer Hub](https://substrate.dev/docs/en/knowledgebase/getting-started/#manual-installation).
Find manual setup instructions at the
[Substrate Developer Hub](https://substrate.dev/docs/en/knowledgebase/getting-started/#manual-installation).
### Build
......@@ -54,8 +55,8 @@ RUST_LOG=debug RUST_BACKTRACE=1 ./target/release/node-template -lruntime=debug -
### Multi-Node Local Testnet
To see the multi-node consensus algorithm in action, run a local testnet with two validator nodes,
Alice and Bob, that have been [configured](/bin/node-template/node/src/chain_spec.rs) as the
initial authorities of the `local` testnet chain and endowed with testnet units.
Alice and Bob, that have been [configured](/bin/node-template/node/src/chain_spec.rs) as the initial
authorities of the `local` testnet chain and endowed with testnet units.
Note: this will require two terminal sessions (one for each node).
......@@ -92,6 +93,91 @@ cargo run -- \
Execute `cargo run -- --help` to learn more about the template node's CLI options.
## Template Structure
A Substrate project such as this consists of a number of components that are spread across a few
directories.
### Node
A blockchain node is an application that allows users to participate in a blockchain network.
Substrate-based blockchain nodes expose a number of capabilities:
- Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the
nodes in the network to communicate with one another.
- Consensus: Blockchains must have a way to come to
[consensus](https://substrate.dev/docs/en/knowledgebase/advanced/consensus) on the state of the
network. Substrate makes it possible to supply custom consensus engines and also ships with
several consensus mechanisms that have been built on top of Web3 Foundation research.
- RPC Server: A remote procedure call (RPC) server is used to interact with Substrate nodes.
There are several files in the `node` directory - take special note of the following:
- [`chain_spec.rs`](./node/src/chain_spec.rs): A
[chain specification](https://substrate.dev/docs/en/knowledgebase/integrate/chain-spec) is a
source code file that defines a Substrate chain's initial (genesis) state. Chain specifications
are useful for development and testing, and critical when architecting the launch of a
production chain. Take note of the `development_config` and `testnet_genesis` functions, which
are used to define the genesis state for the local development chain configuration. These
functions identify some
[well-known accounts](https://substrate.dev/docs/en/knowledgebase/integrate/subkey#well-known-keys)
and use them to configure the blockchain's initial state.
- [`service.rs`](./node/src/service.rs): This file defines the node implementation. Take note of
the libraries that this file imports and the names of the functions it invokes. In particular,
there are references to consensus-related topics, such as the
[longest chain rule](https://substrate.dev/docs/en/knowledgebase/advanced/consensus#longest-chain-rule),
the [Aura](https://substrate.dev/docs/en/knowledgebase/advanced/consensus#aura) block authoring
mechanism and the
[GRANDPA](https://substrate.dev/docs/en/knowledgebase/advanced/consensus#grandpa) finality
gadget.
After the node has been [built](#build), refer to the embedded documentation to learn more about the
capabilities and configuration parameters that it exposes:
```shell
./target/release/node-template --help
```
### Runtime
The Substrate project in this repository uses the
[FRAME](https://substrate.dev/docs/en/knowledgebase/runtime/frame) framework to construct a
blockchain runtime. FRAME allows runtime developers to declare domain-specific logic in modules
called "pallets". At the heart of FRAME is a helpful
[macro language](https://substrate.dev/docs/en/knowledgebase/runtime/macros) that makes it easy to
create pallets and flexibly compose them to create blockchains that can address a variety of needs.
Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note
the following:
- This file configures several pallets to include in the runtime. Each pallet configuration is
defined by a code block that begins with `impl $PALLET_NAME::Trait for Runtime`.
- The pallets are composed into a single runtime by way of the
[`construct_runtime!`](https://crates.parity.io/frame_support/macro.construct_runtime.html)
macro, which is part of the core
[FRAME Support](https://substrate.dev/docs/en/knowledgebase/runtime/frame#support-library)
library.
### Pallets
The runtime in this project is constructed using many FRAME pallets that ship with the
[core Substrate repository](https://github.com/paritytech/substrate/tree/master/frame) and a
template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs) directory.
A FRAME pallet is compromised of a number of blockchain primitives:
- Storage: FRAME defines a rich set of powerful
[storage abstractions](https://substrate.dev/docs/en/knowledgebase/runtime/storage) that makes
it easy to use Substrate's efficient key-value database to manage the evolving state of a
blockchain.
- Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched)
from outside of the runtime in order to update its state.
- Events: Substrate uses [events](https://substrate.dev/docs/en/knowledgebase/runtime/events) to
notify users of important changes in the runtime.
- Errors: When a dispatchable fails, it returns an error.
- Trait: The `Trait` configuration interface is used to define the types and parameters upon which
a FRAME pallet depends.
## Generate a Custom Node Template
Generate a Substrate node template based on a particular commit by running the following commands:
......
......@@ -8,13 +8,13 @@ use sp_finality_grandpa::AuthorityId as GrandpaId;
use sp_runtime::traits::{Verify, IdentifyAccount};
use sc_service::ChainType;
// Note this is the URL for the telemetry server
//const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
// The URL for the telemetry server.
// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
/// Helper function to generate a crypto pair from seed
/// Generate a crypto pair from seed.
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
TPublic::Pair::from_string(&format!("//{}", seed), None)
.expect("static values are valid; qed")
......@@ -23,14 +23,14 @@ pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Pu
type AccountPublic = <Signature as Verify>::Signer;
/// Helper function to generate an account ID from seed
/// Generate an account ID from seed.
pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId where
AccountPublic: From<<TPublic::Pair as Pair>::Public>
{
AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
}
/// Helper function to generate an authority key for Aura
/// Generate an Aura authority key.
pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) {
(
get_from_seed::<AuraId>(s),
......@@ -42,15 +42,20 @@ pub fn development_config() -> Result<ChainSpec, String> {
let wasm_binary = WASM_BINARY.ok_or("Development wasm binary not available".to_string())?;
Ok(ChainSpec::from_genesis(
// Name
"Development",
// ID
"dev",
ChainType::Development,
move || testnet_genesis(
wasm_binary,
// Initial PoA authorities
vec![
authority_keys_from_seed("Alice"),
],
// Sudo account
get_account_id_from_seed::<sr25519::Public>("Alice"),
// Pre-funded accounts
vec![
get_account_id_from_seed::<sr25519::Public>("Alice"),
get_account_id_from_seed::<sr25519::Public>("Bob"),
......@@ -59,10 +64,15 @@ pub fn development_config() -> Result<ChainSpec, String> {
],
true,
),
// Bootnodes
vec![],
// Telemetry
None,
// Protocol ID
None,
// Properties
None,
// Extensions
None,
))
}
......@@ -71,16 +81,21 @@ pub fn local_testnet_config() -> Result<ChainSpec, String> {
let wasm_binary = WASM_BINARY.ok_or("Development wasm binary not available".to_string())?;
Ok(ChainSpec::from_genesis(
// Name
"Local Testnet",
// ID
"local_testnet",
ChainType::Local,
move || testnet_genesis(
wasm_binary,
// Initial PoA authorities
vec![
authority_keys_from_seed("Alice"),
authority_keys_from_seed("Bob"),
],
// Sudo account
get_account_id_from_seed::<sr25519::Public>("Alice"),
// Pre-funded accounts
vec![
get_account_id_from_seed::<sr25519::Public>("Alice"),
get_account_id_from_seed::<sr25519::Public>("Bob"),
......@@ -97,14 +112,20 @@ pub fn local_testnet_config() -> Result<ChainSpec, String> {
],
true,
),
// Bootnodes
vec![],
// Telemetry
None,
// Protocol ID
None,
// Properties
None,
// Extensions
None,
))
}
/// Configure initial storage state for FRAME modules.
fn testnet_genesis(
wasm_binary: &[u8],
initial_authorities: Vec<(AuraId, GrandpaId)>,
......@@ -114,10 +135,12 @@ fn testnet_genesis(
) -> GenesisConfig {
GenesisConfig {
system: Some(SystemConfig {
// Add Wasm runtime to storage.
code: wasm_binary.to_vec(),
changes_trie_config: Default::default(),
}),
balances: Some(BalancesConfig {
// Configure endowed accounts with initial balance of 1 << 60.
balances: endowed_accounts.iter().cloned().map(|k|(k, 1 << 60)).collect(),
}),
aura: Some(AuraConfig {
......@@ -127,6 +150,7 @@ fn testnet_genesis(
authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(),
}),
sudo: Some(SudoConfig {
// Assign network admin rights.
key: root_key,
}),
}
......
......@@ -6,7 +6,7 @@ version = "2.0.0-rc5"
license = "Unlicense"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME pallet template"
description = "FRAME pallet template for defining custom runtime logic."
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
......
#![cfg_attr(not(feature = "std"), no_std)]
/// A FRAME pallet template with necessary imports
/// Feel free to remove or edit this file as needed.
/// If you change the name of this file, make sure to update its references in runtime/src/lib.rs
/// If you remove this file, you can remove those references
/// For more guidance on Substrate FRAME, see the example pallet
/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs
/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// https://substrate.dev/docs/en/knowledgebase/runtime/frame
use frame_support::{decl_module, decl_storage, decl_event, decl_error, dispatch, traits::Get};
use frame_system::ensure_signed;
......@@ -18,89 +13,87 @@ mod mock;
#[cfg(test)]
mod tests;
/// The pallet's configuration trait.
/// Configure the pallet by specifying the parameters and types on which it depends.
pub trait Trait: frame_system::Trait {
// Add other types and constants required to configure this pallet.
/// The overarching event type.
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
}
// This pallet's storage items.
// The pallet's runtime storage items.
// https://substrate.dev/docs/en/knowledgebase/runtime/storage
decl_storage! {
// It is important to update your storage name so that your pallet's
// storage items are isolated from other pallets.
// A unique name is used to ensure that the pallet's storage items are isolated.
// This name may be updated, but each pallet in the runtime must use a unique name.
// ---------------------------------vvvvvvvvvvvvvv
trait Store for Module<T: Trait> as TemplateModule {
// Just a dummy storage item.
// Here we are declaring a StorageValue, `Something` as a Option<u32>
// `get(fn something)` is the default getter which returns either the stored `u32` or `None` if nothing stored
// Learn more about declaring storage items:
// https://substrate.dev/docs/en/knowledgebase/runtime/storage#declaring-storage-items
Something get(fn something): Option<u32>;
}
}
// The pallet's events
// Pallets use events to inform users when important changes are made.
// https://substrate.dev/docs/en/knowledgebase/runtime/events
decl_event!(
pub enum Event<T> where AccountId = <T as frame_system::Trait>::AccountId {
/// Just a dummy event.
/// Event `Something` is declared with a parameter of the type `u32` and `AccountId`
/// To emit this event, we call the deposit function, from our runtime functions
/// [something, who]
/// Event documentation should end with an array that provides descriptive names for event
/// parameters. [something, who]
SomethingStored(u32, AccountId),
}
);
// The pallet's errors
// Errors inform users that something went wrong.
decl_error! {
pub enum Error for Module<T: Trait> {
/// Value was None
/// Error names should be descriptive.
NoneValue,
/// Value reached maximum and cannot be incremented further
/// Errors should have helpful documentation associated with them.
StorageOverflow,
}
}
// The pallet's dispatchable functions.
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
decl_module! {
/// The module declaration.
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
// Initializing errors
// this includes information about your errors in the node's metadata.
// it is needed only if you are using errors in your pallet
// Errors must be initialized if they are used by the pallet.
type Error = Error<T>;
// Initializing events
// this is needed only if you are using events in your pallet
// Events must be initialized if they are used by the pallet.
fn deposit_event() = default;
/// Just a dummy entry point.
/// function that can be called by the external world as an extrinsics call
/// takes a parameter of the type `AccountId`, stores it, and emits an event
/// An example dispatchable that takes a singles value as a parameter, writes the value to
/// storage and emits an event. This function must be dispatched by a signed extrinsic.
#[weight = 10_000 + T::DbWeight::get().writes(1)]
pub fn do_something(origin, something: u32) -> dispatch::DispatchResult {
// Check it was signed and get the signer. See also: ensure_root and ensure_none
// Check that the extrinsic was signed and get the signer.
// This function will return an error if the extrinsic is not signed.
// https://substrate.dev/docs/en/knowledgebase/runtime/origin
let who = ensure_signed(origin)?;
// Code to execute when something calls this.
// For example: the following line stores the passed in u32 in the storage
// Update storage.
Something::put(something);
// Here we are raising the Something event
// Emit an event.
Self::deposit_event(RawEvent::SomethingStored(something, who));
// Return a successful DispatchResult
Ok(())
}
/// Another dummy entry point.
/// takes no parameters, attempts to increment storage value, and possibly throws an error
/// An example dispatchable that may throw a custom error.
#[weight = 10_000 + T::DbWeight::get().reads_writes(1,1)]
pub fn cause_error(origin) -> dispatch::DispatchResult {
// Check it was signed and get the signer. See also: ensure_root and ensure_none
let _who = ensure_signed(origin)?;
// Read a value from storage.
match Something::get() {
// Return an error if the value has not been set.
None => Err(Error::<T>::NoneValue)?,
Some(old) => {
// Increment the value read from storage; will error in the event of overflow.
let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;
// Update the value in storage with the incremented result.
Something::put(new);
Ok(())
},
......
// Creating mock runtime here
use crate::{Module, Trait};
use sp_core::H256;
use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
......@@ -12,9 +10,8 @@ impl_outer_origin! {
pub enum Origin for Test {}
}
// For testing the pallet, we construct most of a mock runtime. This means
// first constructing a configuration type (`Test`) which `impl`s each of the
// configuration traits of pallets we want to use.
// Configure a mock runtime to test the pallet.
#[derive(Clone, Eq, PartialEq)]
pub struct Test;
parameter_types! {
......@@ -23,6 +20,7 @@ parameter_types! {
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
}
impl system::Trait for Test {
type BaseCallFilter = ();
type Origin = Origin;
......@@ -50,13 +48,14 @@ impl system::Trait for Test {
type OnKilledAccount = ();
type SystemWeightInfo = ();
}
impl Trait for Test {
type Event = ();
}
pub type TemplateModule = Module<Test>;
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
// Build genesis storage according to the mock runtime.
pub fn new_test_ext() -> sp_io::TestExternalities {
system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
}
// Tests to be written here
use crate::{Error, mock::*};
use frame_support::{assert_ok, assert_noop};
#[test]
fn it_works_for_default_value() {
new_test_ext().execute_with(|| {
// Just a dummy test for the dummy function `do_something`
// calling the `do_something` function with a value 42
// Dispatch a signed extrinsic.
assert_ok!(TemplateModule::do_something(Origin::signed(1), 42));
// asserting that the stored value is equal to what we stored
// Read pallet storage and assert an expected result.
assert_eq!(TemplateModule::something(), Some(42));
});
}
......@@ -17,7 +14,7 @@ fn it_works_for_default_value() {
#[test]
fn correct_error_for_none_value() {
new_test_ext().execute_with(|| {
// Ensure the correct error is thrown on None value
// Ensure the expected error is thrown when no value is present.
assert_noop!(
TemplateModule::cause_error(Origin::signed(1)),
Error::<Test>::NoneValue
......
//! The Substrate Node Template runtime. This can be compiled with `#[no_std]`, ready for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
#![recursion_limit="256"]
......@@ -40,7 +38,7 @@ pub use frame_support::{
},
};
/// Importing a template pallet
/// Import the template pallet.
pub use template;
/// An index to a block.
......@@ -93,7 +91,6 @@ pub mod opaque {
}
}
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node-template"),
impl_name: create_runtime_str!("node-template"),
......@@ -108,7 +105,7 @@ pub const MILLISECS_PER_BLOCK: u64 = 6000;
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
// These time units are defined in number of blocks.
// Time is measured by number of blocks.
pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
pub const HOURS: BlockNumber = MINUTES * 60;
pub const DAYS: BlockNumber = HOURS * 24;
......@@ -134,6 +131,8 @@ parameter_types! {
pub const Version: RuntimeVersion = VERSION;
}
// Configure FRAME pallets to include in runtime.
impl system::Trait for Runtime {
/// The basic call filter to use in dispatchable.
type BaseCallFilter = ();
......@@ -258,11 +257,12 @@ impl sudo::Trait for Runtime {
type Call = Call;
}
/// Used for the module template in `./template.rs`
/// Configure the pallet template in pallets/template.
impl template::Trait for Runtime {
type Event = Event;
}
// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
pub enum Runtime where
Block = Block,
......@@ -277,7 +277,7 @@ construct_runtime!(
Balances: balances::{Module, Call, Storage, Config<T>, Event<T>},
TransactionPayment: transaction_payment::{Module, Storage},
Sudo: sudo::{Module, Call, Config<T>, Storage, Event<T>},
// Used for the module template in `./template.rs`
// Include the custom logic from the template pallet in the runtime.
TemplateModule: template::{Module, Call, Storage, Event<T>},
}
);
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment