diff --git a/.gitignore b/.gitignore index 28c28cc8ab0e..afa9ed33f4a0 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ runtime/wasm/target/ substrate.code-workspace target/ *.scale +justfile diff --git a/Cargo.lock b/Cargo.lock index ce19fb039cb9..602891892a2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15633,6 +15633,7 @@ dependencies = [ name = "polkadot-sdk-docs" version = "0.0.1" dependencies = [ + "assert_cmd", "chain-spec-guide-runtime", "cumulus-client-service", "cumulus-pallet-aura-ext", @@ -15640,6 +15641,7 @@ dependencies = [ "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "docify", + "frame-benchmarking", "frame-executive", "frame-metadata-hash-extension", "frame-support", @@ -15663,6 +15665,7 @@ dependencies = [ "pallet-example-offchain-worker", "pallet-example-single-block-migrations", "pallet-examples", + "pallet-grandpa", "pallet-multisig", "pallet-nfts", "pallet-preimage", @@ -15677,8 +15680,12 @@ dependencies = [ "pallet-xcm", "parachain-template-runtime", "parity-scale-codec", + "polkadot-omni-node-lib", "polkadot-sdk", + "polkadot-sdk-docs-first-pallet", + "polkadot-sdk-docs-first-runtime", "polkadot-sdk-frame", + "rand", "sc-chain-spec", "sc-cli", "sc-client-db", @@ -15694,6 +15701,7 @@ dependencies = [ "sc-rpc-api", "sc-service", "scale-info", + "serde_json", "simple-mermaid 0.1.1", "solochain-template-runtime", "sp-api 26.0.0", @@ -15708,6 +15716,7 @@ dependencies = [ "sp-std 14.0.0", "sp-tracing 16.0.0", "sp-version 29.0.0", + "sp-weights 27.0.0", "staging-chain-spec-builder", "staging-node-cli", "staging-parachain-info", @@ -15720,6 +15729,35 @@ dependencies = [ "xcm-simulator", ] +[[package]] +name = "polkadot-sdk-docs-first-pallet" +version = "0.0.0" +dependencies = [ + "docify", + "parity-scale-codec", + "polkadot-sdk-frame", + "scale-info", +] + +[[package]] +name = "polkadot-sdk-docs-first-runtime" +version = "0.0.0" +dependencies = [ + "docify", + "pallet-balances", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "polkadot-sdk-docs-first-pallet", + "polkadot-sdk-frame", + "scale-info", + "serde_json", + "sp-keyring", + "substrate-wasm-builder", +] + [[package]] name = "polkadot-sdk-frame" version = "0.1.0" @@ -15742,8 +15780,10 @@ dependencies = [ "sp-consensus-aura", "sp-consensus-grandpa", "sp-core 28.0.0", + "sp-genesis-builder", "sp-inherents", "sp-io 30.0.0", + "sp-keyring", "sp-offchain", "sp-runtime 31.0.1", "sp-session", @@ -16836,8 +16876,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", - "heck 0.5.0", - "itertools 0.12.1", + "heck 0.4.1", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -16870,7 +16910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.82", @@ -16883,7 +16923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.82", diff --git a/Cargo.toml b/Cargo.toml index 049de32b54cf..6ba91de3c09b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,6 +149,8 @@ members = [ "cumulus/test/service", "cumulus/xcm/xcm-emulator", "docs/sdk", + "docs/sdk/packages/guides/first-pallet", + "docs/sdk/packages/guides/first-runtime", "docs/sdk/src/reference_docs/chain_spec_runtime", "polkadot", "polkadot/cli", @@ -806,6 +808,8 @@ hyper = { version = "1.3.1", default-features = false } hyper-rustls = { version = "0.24.2" } hyper-util = { version = "0.1.5", default-features = false } # TODO: remove hyper v0.14 https://github.com/paritytech/polkadot-sdk/issues/4896 +first-pallet = { package = "polkadot-sdk-docs-first-pallet", path = "docs/sdk/packages/guides/first-pallet", default-features = false } +first-runtime = { package = "polkadot-sdk-docs-first-runtime", path = "docs/sdk/packages/guides/first-runtime", default-features = false } hyperv14 = { package = "hyper", version = "0.14.29", default-features = false } impl-serde = { version = "0.5.0", default-features = false } impl-trait-for-tuples = { version = "0.2.2" } diff --git a/cumulus/polkadot-omni-node/lib/src/cli.rs b/cumulus/polkadot-omni-node/lib/src/cli.rs index 6ca328912bba..dc59c185d909 100644 --- a/cumulus/polkadot-omni-node/lib/src/cli.rs +++ b/cumulus/polkadot-omni-node/lib/src/cli.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +//! CLI options of the omni-node. See [`Command`]. + use crate::{ chain_spec::DiskChainSpecLoader, common::{ @@ -103,6 +105,7 @@ pub enum Subcommand { Benchmark(frame_benchmarking_cli::BenchmarkCmd), } +/// CLI Options shipped with `polkadot-omni-node`. #[derive(clap::Parser)] #[command( propagate_version = true, @@ -113,9 +116,11 @@ pub struct Cli { #[arg(skip)] pub(crate) chain_spec_loader: Option>, + /// Possible subcommands. See [`Subcommand`]. #[command(subcommand)] pub subcommand: Option, + /// The shared parameters with all cumulus-based parachain nodes. #[command(flatten)] pub run: cumulus_client_cli::RunCmd, @@ -200,6 +205,7 @@ impl SubstrateCli for Cli { } } +/// The relay chain CLI flags. These are passed in after a `--` at the end. #[derive(Debug)] pub struct RelayChainCli { /// The actual relay chain cli object. diff --git a/cumulus/polkadot-omni-node/lib/src/lib.rs b/cumulus/polkadot-omni-node/lib/src/lib.rs index a293ab225c6f..3f01f4211144 100644 --- a/cumulus/polkadot-omni-node/lib/src/lib.rs +++ b/cumulus/polkadot-omni-node/lib/src/lib.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +//! # Polkadot Omni Node Library +//! //! Helper library that can be used to run a parachain node. //! //! ## Overview @@ -37,11 +39,18 @@ //! //! ## Examples //! -//! For an example, see the `polkadot-parachain-bin` crate. +//! For an example, see the [`polkadot-parachain-bin`](https://crates.io/crates/polkadot-parachain-bin) crate. +//! +//! ## Binary +//! +//! It can be used to start a parachain node from a provided chain spec file. +//! It is only compatible with runtimes that use block number `u32` and `Aura` consensus. +//! +//! Example: `polkadot-omni-node --chain ` #![deny(missing_docs)] -mod cli; +pub mod cli; mod command; mod common; mod fake_runtime_api; diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs index d00d7adf27e1..e8043bd7b2aa 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs @@ -28,7 +28,6 @@ use sc_network::NetworkBackend; use sc_service::{build_polkadot_syncing_strategy, Configuration, PartialComponents, TaskManager}; use sc_telemetry::TelemetryHandle; use sp_runtime::traits::Header; -use sp_timestamp::Timestamp; use std::{marker::PhantomData, sync::Arc}; pub struct ManualSealNode(PhantomData); @@ -182,7 +181,10 @@ impl ManualSealNode { additional_key_values: None, }; Ok(( - sp_timestamp::InherentDataProvider::new(Timestamp::new(0)), + // This is intentional, as the runtime that we expect to run against this + // will never receive the aura-related inherents/digests, and providing + // real timestamps would cause aura <> timestamp checking to fail. + sp_timestamp::InherentDataProvider::new(sp_timestamp::Timestamp::new(0)), mocked_parachain, )) } diff --git a/cumulus/polkadot-omni-node/src/main.rs b/cumulus/polkadot-omni-node/src/main.rs index a86ec6f6fde6..5bca81e2e78b 100644 --- a/cumulus/polkadot-omni-node/src/main.rs +++ b/cumulus/polkadot-omni-node/src/main.rs @@ -14,12 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -//! Basic polkadot omni-node. +//! White labeled polkadot omni-node. //! -//! It can be used to start a parachain node from a provided chain spec file. -//! It is only compatible with runtimes that use block number `u32` and `Aura` consensus. -//! -//! Example: `polkadot-omni-node --chain [chain_spec.json]` +//! For documentation, see [`polkadot_omni_node_lib`]. #![warn(missing_docs)] #![warn(unused_extern_crates)] diff --git a/docs/mermaid/IA.mmd b/docs/mermaid/IA.mmd index 0f14e200df9c..dcf9806dcb62 100644 --- a/docs/mermaid/IA.mmd +++ b/docs/mermaid/IA.mmd @@ -8,6 +8,5 @@ flowchart polkadot_sdk --> substrate polkadot_sdk --> frame - polkadot_sdk --> polkadot[polkadot node] polkadot_sdk --> xcm polkadot_sdk --> templates diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index b86ce9868208..0c39367eeed3 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -29,6 +29,7 @@ pallet-example-offchain-worker = { workspace = true, default-features = true } # How we build docs in rust-docs simple-mermaid = "0.1.1" docify = { workspace = true } +serde_json = { workspace = true } # Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. polkadot-sdk = { features = ["runtime-full"], workspace = true, default-features = true } @@ -39,6 +40,7 @@ subkey = { workspace = true, default-features = true } frame-system = { workspace = true } frame-support = { workspace = true } frame-executive = { workspace = true } +frame-benchmarking = { workspace = true } pallet-example-authorization-tx-extension = { workspace = true, default-features = true } pallet-example-single-block-migrations = { workspace = true, default-features = true } frame-metadata-hash-extension = { workspace = true, default-features = true } @@ -70,6 +72,9 @@ cumulus-primitives-proof-size-hostfunction = { workspace = true, default-feature cumulus-client-service = { workspace = true, default-features = true } cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } +# Omni Node +polkadot-omni-node-lib = { workspace = true, default-features = true } + # Pallets and FRAME internals pallet-aura = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } @@ -92,6 +97,7 @@ pallet-scheduler = { workspace = true, default-features = true } pallet-referenda = { workspace = true, default-features = true } pallet-broker = { workspace = true, default-features = true } pallet-babe = { workspace = true, default-features = true } +pallet-grandpa = { workspace = true, default-features = true } # Primitives sp-io = { workspace = true, default-features = true } @@ -106,6 +112,7 @@ sp-arithmetic = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } +sp-weights = { workspace = true, default-features = true } # XCM @@ -117,9 +124,18 @@ xcm-simulator = { workspace = true } pallet-xcm = { workspace = true } # runtime guides -chain-spec-guide-runtime = { workspace = true } + +chain-spec-guide-runtime = { workspace = true, default-features = true } # Templates -minimal-template-runtime = { workspace = true } -solochain-template-runtime = { workspace = true } -parachain-template-runtime = { workspace = true } +minimal-template-runtime = { workspace = true, default-features = true } +solochain-template-runtime = { workspace = true, default-features = true } +parachain-template-runtime = { workspace = true, default-features = true } + +# local packages +first-runtime = { workspace = true, default-features = true } +first-pallet = { workspace = true, default-features = true } + +[dev-dependencies] +assert_cmd = "2.0.14" +rand = "0.8" diff --git a/docs/sdk/assets/theme.css b/docs/sdk/assets/theme.css index 1f47a8ef5b0c..f9aa4760275e 100644 --- a/docs/sdk/assets/theme.css +++ b/docs/sdk/assets/theme.css @@ -6,6 +6,27 @@ --polkadot-purple: #552BBF; } +/* Light theme */ +html[data-theme="light"] { + --quote-background: #f9f9f9; + --quote-border: #ccc; + --quote-text: #333; +} + +/* Dark theme */ +html[data-theme="dark"] { + --quote-background: #333; + --quote-border: #555; + --quote-text: #f9f9f9; +} + +/* Ayu theme */ +html[data-theme="ayu"] { + --quote-background: #272822; + --quote-border: #383830; + --quote-text: #f8f8f2; +} + body.sdk-docs { nav.sidebar>div.sidebar-crate>a>img { width: 190px; @@ -20,3 +41,17 @@ body.sdk-docs { html[data-theme="light"] .sidebar-crate > .logo-container > img { content: url("https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/images/Polkadot_Logo_Horizontal_Pink_Black.png"); } + +/* Custom styles for blockquotes */ +blockquote { + background-color: var(--quote-background); + border-left: 5px solid var(--quote-border); + color: var(--quote-text); + margin: 1em 0; + padding: 1em 1.5em; + /* font-style: italic; */ +} + +blockquote p { + margin: 0; +} diff --git a/docs/sdk/packages/guides/first-pallet/Cargo.toml b/docs/sdk/packages/guides/first-pallet/Cargo.toml new file mode 100644 index 000000000000..dad5b8863494 --- /dev/null +++ b/docs/sdk/packages/guides/first-pallet/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "polkadot-sdk-docs-first-pallet" +description = "A simple pallet created for the polkadot-sdk-docs guides" +version = "0.0.0" +license = "MIT-0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +frame = { workspace = true, features = ["experimental", "runtime"] } +docify = { workspace = true } + +[features] +default = ["std"] +std = ["codec/std", "frame/std", "scale-info/std"] diff --git a/docs/sdk/packages/guides/first-pallet/src/lib.rs b/docs/sdk/packages/guides/first-pallet/src/lib.rs new file mode 100644 index 000000000000..168b7ca44aba --- /dev/null +++ b/docs/sdk/packages/guides/first-pallet/src/lib.rs @@ -0,0 +1,480 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Pallets used in the `your_first_pallet` guide. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[docify::export] +#[frame::pallet(dev_mode)] +pub mod shell_pallet { + use frame::prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); +} + +#[frame::pallet(dev_mode)] +pub mod pallet { + use frame::prelude::*; + + #[docify::export] + pub type Balance = u128; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export] + /// Single storage item, of type `Balance`. + #[pallet::storage] + pub type TotalIssuance = StorageValue<_, Balance>; + + #[docify::export] + /// A mapping from `T::AccountId` to `Balance` + #[pallet::storage] + pub type Balances = StorageMap<_, _, T::AccountId, Balance>; + + #[docify::export(impl_pallet)] + #[pallet::call] + impl Pallet { + /// An unsafe mint that can be called by anyone. Not a great idea. + pub fn mint_unsafe( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + // ensure that this is a signed account, but we don't really check `_anyone`. + let _anyone = ensure_signed(origin)?; + + // update the balances map. Notice how all `` remains as ``. + Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); + // update total issuance. + TotalIssuance::::mutate(|t| *t = Some(t.unwrap_or(0) + amount)); + + Ok(()) + } + + /// Transfer `amount` from `origin` to `dest`. + pub fn transfer( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // ensure sender has enough balance, and if so, calculate what is left after `amount`. + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + if sender_balance < amount { + return Err("InsufficientBalance".into()) + } + let remainder = sender_balance - amount; + + // update sender and dest balances. + Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); + Balances::::insert(&sender, remainder); + + Ok(()) + } + } + + #[allow(unused)] + impl Pallet { + #[docify::export] + pub fn transfer_better( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + ensure!(sender_balance >= amount, "InsufficientBalance"); + let remainder = sender_balance - amount; + + // .. snip + Ok(()) + } + + #[docify::export] + /// Transfer `amount` from `origin` to `dest`. + pub fn transfer_better_checked( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + let remainder = sender_balance.checked_sub(amount).ok_or("InsufficientBalance")?; + + // .. snip + Ok(()) + } + } + + #[cfg(any(test, doc))] + pub(crate) mod tests { + use crate::pallet::*; + + #[docify::export(testing_prelude)] + use frame::testing_prelude::*; + + pub(crate) const ALICE: u64 = 1; + pub(crate) const BOB: u64 = 2; + pub(crate) const CHARLIE: u64 = 3; + + #[docify::export] + // This runtime is only used for testing, so it should be somewhere like `#[cfg(test)] mod + // tests { .. }` + mod runtime { + use super::*; + // we need to reference our `mod pallet` as an identifier to pass to + // `construct_runtime`. + // YOU HAVE TO CHANGE THIS LINE BASED ON YOUR TEMPLATE + use crate::pallet as pallet_currency; + + construct_runtime!( + pub enum Runtime { + // ---^^^^^^ This is where `enum Runtime` is defined. + System: frame_system, + Currency: pallet_currency, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + // within pallet we just said `::AccountId`, now we + // finally specified it. + type AccountId = u64; + } + + // our simple pallet has nothing to be configured. + impl pallet_currency::Config for Runtime {} + } + + pub(crate) use runtime::*; + + #[allow(unused)] + #[docify::export] + fn new_test_state_basic() -> TestState { + let mut state = TestState::new_empty(); + let accounts = vec![(ALICE, 100), (BOB, 100)]; + state.execute_with(|| { + for (who, amount) in &accounts { + Balances::::insert(who, amount); + TotalIssuance::::mutate(|b| *b = Some(b.unwrap_or(0) + amount)); + } + }); + + state + } + + #[docify::export] + pub(crate) struct StateBuilder { + balances: Vec<(::AccountId, Balance)>, + } + + #[docify::export(default_state_builder)] + impl Default for StateBuilder { + fn default() -> Self { + Self { balances: vec![(ALICE, 100), (BOB, 100)] } + } + } + + #[docify::export(impl_state_builder_add)] + impl StateBuilder { + fn add_balance( + mut self, + who: ::AccountId, + amount: Balance, + ) -> Self { + self.balances.push((who, amount)); + self + } + } + + #[docify::export(impl_state_builder_build)] + impl StateBuilder { + pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut ext = TestState::new_empty(); + ext.execute_with(|| { + for (who, amount) in &self.balances { + Balances::::insert(who, amount); + TotalIssuance::::mutate(|b| *b = Some(b.unwrap_or(0) + amount)); + } + }); + + ext.execute_with(test); + + // assertions that must always hold + ext.execute_with(|| { + assert_eq!( + Balances::::iter().map(|(_, x)| x).sum::(), + TotalIssuance::::get().unwrap_or_default() + ); + }) + } + } + + #[docify::export] + #[test] + fn first_test() { + TestState::new_empty().execute_with(|| { + // We expect Alice's account to have no funds. + assert_eq!(Balances::::get(&ALICE), None); + assert_eq!(TotalIssuance::::get(), None); + + // mint some funds into Alice's account. + assert_ok!(Pallet::::mint_unsafe( + RuntimeOrigin::signed(ALICE), + ALICE, + 100 + )); + + // re-check the above + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(TotalIssuance::::get(), Some(100)); + }) + } + + #[docify::export] + #[test] + fn state_builder_works() { + StateBuilder::default().build_and_execute(|| { + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(Balances::::get(&BOB), Some(100)); + assert_eq!(Balances::::get(&CHARLIE), None); + assert_eq!(TotalIssuance::::get(), Some(200)); + }); + } + + #[docify::export] + #[test] + fn state_builder_add_balance() { + StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| { + assert_eq!(Balances::::get(&CHARLIE), Some(42)); + assert_eq!(TotalIssuance::::get(), Some(242)); + }) + } + + #[test] + #[should_panic] + fn state_builder_duplicate_genesis_fails() { + StateBuilder::default() + .add_balance(CHARLIE, 42) + .add_balance(CHARLIE, 43) + .build_and_execute(|| { + assert_eq!(Balances::::get(&CHARLIE), None); + assert_eq!(TotalIssuance::::get(), Some(242)); + }) + } + + #[docify::export] + #[test] + fn mint_works() { + StateBuilder::default().build_and_execute(|| { + // given the initial state, when: + assert_ok!(Pallet::::mint_unsafe(RuntimeOrigin::signed(ALICE), BOB, 100)); + + // then: + assert_eq!(Balances::::get(&BOB), Some(200)); + assert_eq!(TotalIssuance::::get(), Some(300)); + + // given: + assert_ok!(Pallet::::mint_unsafe( + RuntimeOrigin::signed(ALICE), + CHARLIE, + 100 + )); + + // then: + assert_eq!(Balances::::get(&CHARLIE), Some(100)); + assert_eq!(TotalIssuance::::get(), Some(400)); + }); + } + + #[docify::export] + #[test] + fn transfer_works() { + StateBuilder::default().build_and_execute(|| { + // given the initial state, when: + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); + + // then: + assert_eq!(Balances::::get(&ALICE), Some(50)); + assert_eq!(Balances::::get(&BOB), Some(150)); + assert_eq!(TotalIssuance::::get(), Some(200)); + + // when: + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(BOB), ALICE, 50)); + + // then: + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(Balances::::get(&BOB), Some(100)); + assert_eq!(TotalIssuance::::get(), Some(200)); + }); + } + + #[docify::export] + #[test] + fn transfer_from_non_existent_fails() { + StateBuilder::default().build_and_execute(|| { + // given the initial state, when: + assert_err!( + Pallet::::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10), + "NonExistentAccount" + ); + + // then nothing has changed. + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(Balances::::get(&BOB), Some(100)); + assert_eq!(Balances::::get(&CHARLIE), None); + assert_eq!(TotalIssuance::::get(), Some(200)); + }); + } + } +} + +#[frame::pallet(dev_mode)] +pub mod pallet_v2 { + use super::pallet::Balance; + use frame::prelude::*; + + #[docify::export(config_v2)] + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type of the runtime. + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + + TryInto>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type Balances = StorageMap<_, _, T::AccountId, Balance>; + + #[pallet::storage] + pub type TotalIssuance = StorageValue<_, Balance>; + + #[docify::export] + #[pallet::error] + pub enum Error { + /// Account does not exist. + NonExistentAccount, + /// Account does not have enough balance. + InsufficientBalance, + } + + #[docify::export] + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A transfer succeeded. + Transferred { from: T::AccountId, to: T::AccountId, amount: Balance }, + } + + #[pallet::call] + impl Pallet { + #[docify::export(transfer_v2)] + pub fn transfer( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // ensure sender has enough balance, and if so, calculate what is left after `amount`. + let sender_balance = + Balances::::get(&sender).ok_or(Error::::NonExistentAccount)?; + let remainder = + sender_balance.checked_sub(amount).ok_or(Error::::InsufficientBalance)?; + + Balances::::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount)); + Balances::::insert(&sender, remainder); + + Self::deposit_event(Event::::Transferred { from: sender, to: dest, amount }); + + Ok(()) + } + } + + #[cfg(any(test, doc))] + pub mod tests { + use super::{super::pallet::tests::StateBuilder, *}; + use frame::testing_prelude::*; + const ALICE: u64 = 1; + const BOB: u64 = 2; + + #[docify::export] + pub mod runtime_v2 { + use super::*; + use crate::pallet_v2 as pallet_currency; + + construct_runtime!( + pub enum Runtime { + System: frame_system, + Currency: pallet_currency, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + type AccountId = u64; + } + + impl pallet_currency::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + } + } + + pub(crate) use runtime_v2::*; + + #[docify::export(transfer_works_v2)] + #[test] + fn transfer_works() { + StateBuilder::default().build_and_execute(|| { + // skip the genesis block, as events are not deposited there and we need them for + // the final assertion. + System::set_block_number(ALICE); + + // given the initial state, when: + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); + + // then: + assert_eq!(Balances::::get(&ALICE), Some(50)); + assert_eq!(Balances::::get(&BOB), Some(150)); + assert_eq!(TotalIssuance::::get(), Some(200)); + + // now we can also check that an event has been deposited: + assert_eq!( + System::read_events_for_pallet::>(), + vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }] + ); + }); + } + } +} diff --git a/docs/sdk/packages/guides/first-runtime/Cargo.toml b/docs/sdk/packages/guides/first-runtime/Cargo.toml new file mode 100644 index 000000000000..303d5c5e7f5f --- /dev/null +++ b/docs/sdk/packages/guides/first-runtime/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "polkadot-sdk-docs-first-runtime" +description = "A simple runtime created for the polkadot-sdk-docs guides" +version = "0.0.0" +license = "MIT-0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +serde_json = { workspace = true } + +# this is a frame-based runtime, thus importing `frame` with runtime feature enabled. +frame = { workspace = true, features = ["experimental", "runtime"] } + +# pallets that we want to use +pallet-balances = { workspace = true } +pallet-sudo = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-transaction-payment = { workspace = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true } + +# other polkadot-sdk-deps +sp-keyring = { workspace = true } + +# local pallet templates +first-pallet = { workspace = true } + +docify = { workspace = true } + +[build-dependencies] +substrate-wasm-builder = { workspace = true, optional = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "serde_json/std", + + "frame/std", + + "pallet-balances/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + + "first-pallet/std", + "sp-keyring/std", + + "substrate-wasm-builder", +] diff --git a/docs/sdk/packages/guides/first-runtime/build.rs b/docs/sdk/packages/guides/first-runtime/build.rs new file mode 100644 index 000000000000..b7676a70dfe8 --- /dev/null +++ b/docs/sdk/packages/guides/first-runtime/build.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +fn main() { + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } +} diff --git a/docs/sdk/packages/guides/first-runtime/src/lib.rs b/docs/sdk/packages/guides/first-runtime/src/lib.rs new file mode 100644 index 000000000000..92d51962b6fe --- /dev/null +++ b/docs/sdk/packages/guides/first-runtime/src/lib.rs @@ -0,0 +1,301 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime used in `your_first_runtime`. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +use alloc::{vec, vec::Vec}; +use first_pallet::pallet_v2 as our_first_pallet; +use frame::{ + prelude::*, + runtime::{apis, prelude::*}, +}; +use pallet_transaction_payment_rpc_runtime_api::{FeeDetails, RuntimeDispatchInfo}; + +#[docify::export] +#[runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("first-runtime"), + impl_name: create_runtime_str!("first-runtime"), + authoring_version: 1, + spec_version: 0, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + system_version: 1, +}; + +#[docify::export(cr)] +construct_runtime!( + pub struct Runtime { + // Mandatory for all runtimes + System: frame_system, + + // A number of other pallets from FRAME. + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Sudo: pallet_sudo, + TransactionPayment: pallet_transaction_payment, + + // Our local pallet + FirstPallet: our_first_pallet, + } +); + +#[docify::export_content] +mod runtime_types { + use super::*; + pub(super) type SignedExtra = ( + // `frame` already provides all the signed extensions from `frame-system`. We just add the + // one related to tx-payment here. + frame::runtime::types_common::SystemTransactionExtensionsOf, + pallet_transaction_payment::ChargeTransactionPayment, + ); + + pub(super) type Block = frame::runtime::types_common::BlockOf; + pub(super) type Header = HeaderFor; + + pub(super) type RuntimeExecutive = Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + >; +} +use runtime_types::*; + +#[docify::export_content] +mod config_impls { + use super::*; + + parameter_types! { + pub const Version: RuntimeVersion = VERSION; + } + + #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = Block; + type Version = Version; + type AccountData = + pallet_balances::AccountData<::Balance>; + } + + #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] + impl pallet_balances::Config for Runtime { + type AccountStore = System; + } + + #[derive_impl(pallet_sudo::config_preludes::TestDefaultConfig)] + impl pallet_sudo::Config for Runtime {} + + #[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] + impl pallet_timestamp::Config for Runtime {} + + #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] + impl pallet_transaction_payment::Config for Runtime { + type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; + // We specify a fixed length to fee here, which essentially means all transactions charge + // exactly 1 unit of fee. + type LengthToFee = FixedFee<1, ::Balance>; + type WeightToFee = NoFee<::Balance>; + } +} + +#[docify::export(our_config_impl)] +impl our_first_pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +/// Provides getters for genesis configuration presets. +pub mod genesis_config_presets { + use super::*; + use crate::{ + interface::{Balance, MinimumBalance}, + BalancesConfig, RuntimeGenesisConfig, SudoConfig, + }; + use serde_json::Value; + + /// Returns a development genesis config preset. + #[docify::export] + pub fn development_config_genesis() -> Value { + let endowment = >::get().max(1) * 1000; + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: AccountKeyring::iter() + .map(|a| (a.to_account_id(), endowment)) + .collect::>(), + }, + sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") + } + + /// Get the set of the available genesis config presets. + #[docify::export] + pub fn get_preset(id: &PresetId) -> Option> { + let patch = match id.try_into() { + Ok(DEV_RUNTIME_PRESET) => development_config_genesis(), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) + } + + /// List of supported presets. + #[docify::export] + pub fn preset_names() -> Vec { + vec![PresetId::from(DEV_RUNTIME_PRESET)] + } +} + +impl_runtime_apis! { + impl apis::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + RuntimeExecutive::execute_block(block) + } + + fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { + RuntimeExecutive::initialize_block(header) + } + } + + impl apis::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> Vec { + Runtime::metadata_versions() + } + } + + impl apis::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { + RuntimeExecutive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> HeaderFor { + RuntimeExecutive::finalize_block() + } + + fn inherent_extrinsics(data: InherentData) -> Vec> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: InherentData, + ) -> CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl apis::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ExtrinsicFor, + block_hash: ::Hash, + ) -> TransactionValidity { + RuntimeExecutive::validate_transaction(source, tx, block_hash) + } + } + + impl apis::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &HeaderFor) { + RuntimeExecutive::offchain_worker(header) + } + } + + impl apis::SessionKeys for Runtime { + fn generate_session_keys(_seed: Option>) -> Vec { + Default::default() + } + + fn decode_session_keys( + _encoded: Vec, + ) -> Option, apis::KeyTypeId)>> { + Default::default() + } + } + + impl apis::AccountNonceApi for Runtime { + fn account_nonce(account: interface::AccountId) -> interface::Nonce { + System::account_nonce(account) + } + } + + impl apis::GenesisBuilder for Runtime { + fn build_state(config: Vec) -> GenesisBuilderResult { + build_state::(config) + } + + fn get_preset(id: &Option) -> Option> { + get_preset::(id, self::genesis_config_presets::get_preset) + } + + fn preset_names() -> Vec { + crate::genesis_config_presets::preset_names() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< + Block, + interface::Balance, + > for Runtime { + fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> interface::Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> interface::Balance { + TransactionPayment::length_to_fee(length) + } + } +} + +/// Just a handy re-definition of some types based on what is already provided to the pallet +/// configs. +pub mod interface { + use super::Runtime; + use frame::prelude::frame_system; + + pub type AccountId = ::AccountId; + pub type Nonce = ::Nonce; + pub type Hash = ::Hash; + pub type Balance = ::Balance; + pub type MinimumBalance = ::ExistentialDeposit; +} diff --git a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs index 812e674d163b..2339088abed4 100644 --- a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs +++ b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs @@ -70,10 +70,11 @@ //! - Ensure enough coretime is assigned to the parachain. For maximum throughput the upper bound is //! 3 cores. //! -//!
Phase 1 is not needed if using the polkadot-parachain binary -//! built from the latest polkadot-sdk release! Simply pass the -//! --experimental-use-slot-based parameter to the command line and jump to Phase -//! 2.
+//!
Phase 1 is NOT needed if using the polkadot-parachain or +//! polkadot-omni-node binary, or polkadot-omni-node-lib built from the +//! latest polkadot-sdk release! Simply pass the --experimental-use-slot-based +//! ([`polkadot_omni_node_lib::cli::Cli::experimental_use_slot_based`]) parameter to the command +//! line and jump to Phase 2.
//! //! The following steps assume using the cumulus parachain template. //! diff --git a/docs/sdk/src/guides/mod.rs b/docs/sdk/src/guides/mod.rs index a7fd146ccdf3..747128a728d0 100644 --- a/docs/sdk/src/guides/mod.rs +++ b/docs/sdk/src/guides/mod.rs @@ -1,14 +1,22 @@ //! # Polkadot SDK Docs Guides //! -//! This crate contains a collection of guides that are foundational to the developers of Polkadot -//! SDK. They are common user-journeys that are traversed in the Polkadot ecosystem. +//! This crate contains a collection of guides that are foundational to the developers of +//! Polkadot SDK. They are common user-journeys that are traversed in the Polkadot ecosystem. //! -//! 1. [`crate::guides::your_first_pallet`] is your starting point with Polkadot SDK. It contains -//! the basics of -//! building a simple crypto currency with FRAME. -//! 2. [`crate::guides::your_first_runtime`] is the next step in your journey. It contains the -//! basics of building a runtime that contains this pallet, plus a few common pallets from FRAME. +//! The main user-journey covered by these guides is: //! +//! * [`your_first_pallet`], where you learn what a FRAME pallet is, and write your first +//! application logic. +//! * [`your_first_runtime`], where you learn how to compile your pallets into a WASM runtime. +//! * [`your_first_node`], where you learn how to run the said runtime in a node. +//! +//! > By this step, you have already launched a full Polkadot-SDK-based blockchain! +//! +//! Once done, feel free to step up into one of our templates: [`crate::polkadot_sdk::templates`]. +//! +//! [`your_first_pallet`]: crate::guides::your_first_pallet +//! [`your_first_runtime`]: crate::guides::your_first_runtime +//! [`your_first_node`]: crate::guides::your_first_node //! //! Other guides are related to other miscellaneous topics and are listed as modules below. @@ -19,19 +27,12 @@ pub mod your_first_pallet; /// compiling it to [WASM](crate::polkadot_sdk::substrate#wasm-build). pub mod your_first_runtime; -// /// Running the given runtime with a node. No specific consensus mechanism is used at this stage. -// TODO -// pub mod your_first_node; - -// /// How to enhance a given runtime and node to be cumulus-enabled, run it as a parachain -// /// and connect it to a relay-chain. -// TODO -// pub mod cumulus_enabled_parachain; +/// Running the given runtime with a node. No specific consensus mechanism is used at this stage. +pub mod your_first_node; -// /// How to make a given runtime XCM-enabled, capable of sending messages (`Transact`) between -// /// itself and the relay chain to which it is connected. -// TODO -// pub mod xcm_enabled_parachain; +/// How to enhance a given runtime and node to be cumulus-enabled, run it as a parachain +/// and connect it to a relay-chain. +// pub mod your_first_parachain; /// How to enable storage weight reclaiming in a parachain node and runtime. pub mod enable_pov_reclaim; diff --git a/docs/sdk/src/guides/your_first_node.rs b/docs/sdk/src/guides/your_first_node.rs index d12349c99063..da37c11c206f 100644 --- a/docs/sdk/src/guides/your_first_node.rs +++ b/docs/sdk/src/guides/your_first_node.rs @@ -1 +1,314 @@ //! # Your first Node +//! +//! In this guide, you will learn how to run a runtime, such as the one created in +//! [`your_first_runtime`], in a node. Within the context of this guide, we will focus on running +//! the runtime with an [`omni-node`]. Please first read this page to learn about the OmniNode, and +//! other options when it comes to running a node. +//! +//! [`your_first_runtime`] is a runtime with no consensus related code, and therefore can only be +//! executed with a node that also expects no consensus ([`sc_consensus_manual_seal`]). +//! `polkadot-omni-node`'s [`--dev-block-time`] precisely does this. +//! +//! > All of the following steps are coded as unit tests of this module. Please see `Source` of the +//! > page for more information. +//! +//! ## Running The Omni Node +//! +//! ### Installs +//! +//! The `polkadot-omni-node` can either be downloaded from the latest [Release](https://github.com/paritytech/polkadot-sdk/releases/) of `polkadot-sdk`, +//! or installed using `cargo`: +//! +//! ```text +//! cargo install polkadot-omni-node +//! ``` +//! +//! Next, we need to install the [`chain-spec-builder`]. This is the tool that allows us to build +//! chain-specifications, through interacting with the genesis related APIs of the runtime, as +//! described in [`crate::guides::your_first_runtime#genesis-configuration`]. +//! +//! ```text +//! cargo install staging-chain-spec-builder +//! ``` +//! +//! > The name of the crate is prefixed with `staging` as the crate name `chain-spec-builder` on +//! > crates.io is already taken and is not controlled by `polkadot-sdk` developers. +//! +//! ### Building Runtime +//! +//! Next, we need to build the corresponding runtime that we wish to interact with. +//! +//! ```text +//! cargo build --release -p path-to-runtime +//! ``` +//! Equivalent code in tests: +#![doc = docify::embed!("./src/guides/your_first_node.rs", build_runtime)] +//! +//! This creates the wasm file under `./target/{release}/wbuild/release` directory. +//! +//! ### Building Chain Spec +//! +//! Next, we can generate the corresponding chain-spec file. For this example, we will use the +//! `development` (`sp_genesis_config::DEVELOPMENT`) preset. +//! +//! Note that we intend to run this chain-spec with `polkadot-omni-node`, which is tailored for +//! running parachains. This requires the chain-spec to always contain the `para_id` and a +//! `relay_chain` fields, which are provided below as CLI arguments. +//! +//! ```text +//! chain-spec-builder \ +//! -c \ +//! create \ +//! --para-id 42 \ +//! --relay-chain dontcare \ +//! --runtime polkadot_sdk_docs_first_runtime.wasm \ +//! named-preset development +//! ``` +//! +//! Equivalent code in tests: +#![doc = docify::embed!("./src/guides/your_first_node.rs", csb)] +//! +//! +//! ### Running `polkadot-omni-node` +//! +//! Finally, we can run the node with the generated chain-spec file. We can also specify the block +//! time using the `--dev-block-time` flag. +//! +//! ```text +//! polkadot-omni-node \ +//! --tmp \ +//! --dev-block-time 1000 \ +//! --chain .json +//! ``` +//! +//! > Note that we always prefer to use `--tmp` for testing, as it will save the chain state to a +//! > temporary folder, allowing the chain-to be easily restarted without `purge-chain`. See +//! > [`sc_cli::commands::PurgeChainCmd`] and [`sc_cli::commands::RunCmd::tmp`] for more info. +//! +//! This will start the node and import the blocks. Note while using `--dev-block-time`, the node +//! will use the testing-specific manual-seal consensus. This is an efficient way to test the +//! application logic of your runtime, without needing to yet care about consensus, block +//! production, relay-chain and so on. +//! +//! ### Next Steps +//! +//! * See the rest of the steps in [`crate::reference_docs::omni_node#user-journey`]. +//! +//! [`runtime`]: crate::reference_docs::glossary#runtime +//! [`node`]: crate::reference_docs::glossary#node +//! [`build_config`]: first_runtime::Runtime#method.build_config +//! [`omni-node`]: crate::reference_docs::omni_node +//! [`--dev-block-time`]: (polkadot_omni_node_lib::cli::Cli::dev_block_time) + +#[cfg(test)] +mod tests { + use assert_cmd::Command; + use rand::Rng; + use sc_chain_spec::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET}; + use sp_genesis_builder::PresetId; + use std::path::PathBuf; + + const PARA_RUNTIME: &'static str = "parachain-template-runtime"; + const FIRST_RUNTIME: &'static str = "polkadot-sdk-docs-first-runtime"; + const MINIMAL_RUNTIME: &'static str = "minimal-template-runtime"; + + const CHAIN_SPEC_BUILDER: &'static str = "chain-spec-builder"; + const OMNI_NODE: &'static str = "polkadot-omni-node"; + + fn cargo() -> Command { + Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())) + } + + fn get_target_directory() -> Option { + let output = cargo().arg("metadata").arg("--format-version=1").output().ok()?; + + if !output.status.success() { + return None; + } + + let metadata: serde_json::Value = serde_json::from_slice(&output.stdout).ok()?; + let target_directory = metadata["target_directory"].as_str()?; + + Some(PathBuf::from(target_directory)) + } + + fn find_release_binary(name: &str) -> Option { + let target_dir = get_target_directory()?; + let release_path = target_dir.join("release").join(name); + + if release_path.exists() { + Some(release_path) + } else { + None + } + } + + fn find_wasm(runtime_name: &str) -> Option { + let target_dir = get_target_directory()?; + let wasm_path = target_dir + .join("release") + .join("wbuild") + .join(runtime_name) + .join(format!("{}.wasm", runtime_name.replace('-', "_"))); + + if wasm_path.exists() { + Some(wasm_path) + } else { + None + } + } + + fn maybe_build_runtimes() { + if find_wasm(&PARA_RUNTIME).is_none() { + println!("Building parachain-template-runtime..."); + Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg(PARA_RUNTIME) + .assert() + .success(); + } + if find_wasm(&FIRST_RUNTIME).is_none() { + println!("Building polkadot-sdk-docs-first-runtime..."); + #[docify::export_content] + fn build_runtime() { + Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg(FIRST_RUNTIME) + .assert() + .success(); + } + build_runtime() + } + + assert!(find_wasm(PARA_RUNTIME).is_some()); + assert!(find_wasm(FIRST_RUNTIME).is_some()); + } + + fn maybe_build_chain_spec_builder() { + if find_release_binary(CHAIN_SPEC_BUILDER).is_none() { + println!("Building chain-spec-builder..."); + Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg("staging-chain-spec-builder") + .assert() + .success(); + } + assert!(find_release_binary(CHAIN_SPEC_BUILDER).is_some()); + } + + fn maybe_build_omni_node() { + if find_release_binary(OMNI_NODE).is_none() { + println!("Building polkadot-omni-node..."); + Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg("polkadot-omni-node") + .assert() + .success(); + } + } + + fn test_runtime_preset(runtime: &'static str, block_time: u64, maybe_preset: Option) { + sp_tracing::try_init_simple(); + maybe_build_runtimes(); + maybe_build_chain_spec_builder(); + maybe_build_omni_node(); + + let chain_spec_builder = + find_release_binary(&CHAIN_SPEC_BUILDER).expect("we built it above; qed"); + let omni_node = find_release_binary(OMNI_NODE).expect("we built it above; qed"); + let runtime_path = find_wasm(runtime).expect("we built it above; qed"); + + let random_seed: u32 = rand::thread_rng().gen(); + let chain_spec_file = std::env::current_dir() + .unwrap() + .join(format!("{}_{}_{}.json", runtime, block_time, random_seed)); + + Command::new(chain_spec_builder) + .args(["-c", chain_spec_file.to_str().unwrap()]) + .arg("create") + .args(["--para-id", "1000", "--relay-chain", "dontcare"]) + .args(["-r", runtime_path.to_str().unwrap()]) + .args(match maybe_preset { + Some(preset) => vec!["named-preset".to_string(), preset.to_string()], + None => vec!["default".to_string()], + }) + .assert() + .success(); + + let output = Command::new(omni_node) + .arg("--tmp") + .args(["--chain", chain_spec_file.to_str().unwrap()]) + .args(["--dev-block-time", block_time.to_string().as_str()]) + .timeout(std::time::Duration::from_secs(10)) + .output() + .unwrap(); + + std::fs::remove_file(chain_spec_file).unwrap(); + + // uncomment for debugging. + // println!("output: {:?}", output); + + let expected_blocks = (10_000 / block_time).saturating_div(2); + assert!(expected_blocks > 0, "test configuration is bad, should give it more time"); + assert!(String::from_utf8(output.stderr) + .unwrap() + .contains(format!("Imported #{}", expected_blocks).to_string().as_str())); + } + + #[test] + fn works_with_different_block_times() { + test_runtime_preset(PARA_RUNTIME, 100, Some(DEV_RUNTIME_PRESET.into())); + test_runtime_preset(PARA_RUNTIME, 3000, Some(DEV_RUNTIME_PRESET.into())); + + // we need this snippet just for docs + #[docify::export_content(csb)] + fn build_para_chain_spec_works() { + let chain_spec_builder = find_release_binary(&CHAIN_SPEC_BUILDER).unwrap(); + let runtime_path = find_wasm(PARA_RUNTIME).unwrap(); + let output = "/tmp/demo-chain-spec.json"; + Command::new(chain_spec_builder) + .args(["-c", output]) + .arg("create") + .args(["--para-id", "1000", "--relay-chain", "dontcare"]) + .args(["-r", runtime_path.to_str().unwrap()]) + .args(["named-preset", "development"]) + .assert() + .success(); + std::fs::remove_file(output).unwrap(); + } + build_para_chain_spec_works(); + } + + #[test] + fn parachain_runtime_works() { + // TODO: None doesn't work. But maybe it should? it would be misleading as many users might + // use it. + [Some(DEV_RUNTIME_PRESET.into()), Some(LOCAL_TESTNET_RUNTIME_PRESET.into())] + .into_iter() + .for_each(|preset| { + test_runtime_preset(PARA_RUNTIME, 1000, preset); + }); + } + + #[test] + fn minimal_runtime_works() { + [None, Some(DEV_RUNTIME_PRESET.into())].into_iter().for_each(|preset| { + test_runtime_preset(MINIMAL_RUNTIME, 1000, preset); + }); + } + + #[test] + fn guide_first_runtime_works() { + [Some(DEV_RUNTIME_PRESET.into())].into_iter().for_each(|preset| { + test_runtime_preset(FIRST_RUNTIME, 1000, preset); + }); + } +} diff --git a/docs/sdk/src/guides/your_first_pallet/mod.rs b/docs/sdk/src/guides/your_first_pallet/mod.rs index fcfaab00e552..aef8981b4d4a 100644 --- a/docs/sdk/src/guides/your_first_pallet/mod.rs +++ b/docs/sdk/src/guides/your_first_pallet/mod.rs @@ -47,7 +47,7 @@ //! //! [`pallet::config`] and [`pallet::pallet`] are both mandatory parts of any //! pallet. Refer to the documentation of each to get an overview of what they do. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", shell_pallet)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", shell_pallet)] //! //! All of the code that follows in this guide should live inside of the `mod pallet`. //! @@ -61,17 +61,17 @@ //! > For the rest of this guide, we will opt for a balance type of `u128`. For the sake of //! > simplicity, we are hardcoding this type. In a real pallet is best practice to define it as a //! > generic bounded type in the `Config` trait, and then specify it in the implementation. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Balance)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Balance)] //! //! The definition of these two storage items, based on [`pallet::storage`] details, is as follows: -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", TotalIssuance)] -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Balances)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", TotalIssuance)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Balances)] //! //! ### Dispatchables //! //! Next, we will define the dispatchable functions. As per [`pallet::call`], these will be defined //! as normal `fn`s attached to `struct Pallet`. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", impl_pallet)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", impl_pallet)] //! //! The logic of these functions is self-explanatory. Instead, we will focus on the FRAME-related //! details: @@ -108,14 +108,14 @@ //! How we handle error in the above snippets is fairly rudimentary. Let's look at how this can be //! improved. First, we can use [`frame::prelude::ensure`] to express the error slightly better. //! This macro will call `.into()` under the hood. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_better)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_better)] //! //! Moreover, you will learn in the [Defensive Programming //! section](crate::reference_docs::defensive_programming) that it is always recommended to use //! safe arithmetic operations in your runtime. By using [`frame::traits::CheckedSub`], we can not //! only take a step in that direction, but also improve the error handing and make it slightly more //! ergonomic. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_better_checked)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_better_checked)] //! //! This is more or less all the logic that there is in this basic currency pallet! //! @@ -145,7 +145,7 @@ //! through [`frame::runtime::prelude::construct_runtime`]. All runtimes also have to include //! [`frame::prelude::frame_system`]. So we expect to see a runtime with two pallet, `frame_system` //! and the one we just wrote. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", runtime)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", runtime)] //! //! > [`frame::pallet_macros::derive_impl`] is a FRAME feature that enables developers to have //! > defaults for associated types. @@ -182,7 +182,7 @@ //! to learn is that all of your pallet testing code should be wrapped in //! [`frame::testing_prelude::TestState`]. This is a type that provides access to an in-memory state //! to be used in our tests. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", first_test)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", first_test)] //! //! In the first test, we simply assert that there is no total issuance, and no balance associated //! with Alice's account. Then, we mint some balance into Alice's, and re-check. @@ -206,16 +206,16 @@ //! //! Let's see how we can implement a better test setup using this pattern. First, we define a //! `struct StateBuilder`. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", StateBuilder)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", StateBuilder)] //! //! This struct is meant to contain the same list of accounts and balances that we want to have at //! the beginning of each block. We hardcoded this to `let accounts = vec![(ALICE, 100), (2, 100)];` //! so far. Then, if desired, we attach a default value for this struct. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", default_state_builder)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", default_state_builder)] //! //! Like any other builder pattern, we attach functions to the type to mutate its internal //! properties. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", impl_state_builder_add)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", impl_state_builder_add)] //! //! Finally --the useful part-- we write our own custom `build_and_execute` function on //! this type. This function will do multiple things: @@ -227,23 +227,23 @@ //! after each test. For example, in this test, we do some additional checking about the //! correctness of the `TotalIssuance`. We leave it up to you as an exercise to learn why the //! assertion should always hold, and how it is checked. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", impl_state_builder_build)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", impl_state_builder_build)] //! //! We can write tests that specifically check the initial state, and making sure our `StateBuilder` //! is working exactly as intended. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", state_builder_works)] -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", state_builder_add_balance)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", state_builder_works)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", state_builder_add_balance)] //! //! ### More Tests //! //! Now that we have a more ergonomic test setup, let's see how a well written test for transfer and //! mint would look like. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_works)] -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", mint_works)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_works)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", mint_works)] //! //! It is always a good idea to build a mental model where you write *at least* one test for each //! "success path" of a dispatchable, and one test for each "failure path", such as: -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_from_non_existent_fails)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_from_non_existent_fails)] //! //! We leave it up to you to write a test that triggers the `InsufficientBalance` error. //! @@ -272,8 +272,8 @@ //! With the explanation out of the way, let's see how these components can be added. Both follow a //! fairly familiar syntax: normal Rust enums, with extra [`pallet::event`] and [`pallet::error`] //! attributes attached. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Event)] -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Error)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Event)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Error)] //! //! One slightly custom part of this is the [`pallet::generate_deposit`] part. Without going into //! too much detail, in order for a pallet to emit events to the rest of the system, it needs to do @@ -288,17 +288,17 @@ //! 2. But, doing this conversion and storing is too much to expect each pallet to define. FRAME //! provides a default way of storing events, and this is what [`pallet::generate_deposit`] is //! doing. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", config_v2)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", config_v2)] //! //! > These `Runtime*` types are better explained in //! > [`crate::reference_docs::frame_runtime_types`]. //! //! Then, we can rewrite the `transfer` dispatchable as such: -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_v2)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_v2)] //! //! Then, notice how now we would need to provide this `type RuntimeEvent` in our test runtime //! setup. -#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", runtime_v2)] +#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", runtime_v2)] //! //! In this snippet, the actual `RuntimeEvent` type (right hand side of `type RuntimeEvent = //! RuntimeEvent`) is generated by diff --git a/docs/sdk/src/guides/your_first_pallet/with_event.rs b/docs/sdk/src/guides/your_first_pallet/with_event.rs deleted file mode 100644 index a5af29c9c319..000000000000 --- a/docs/sdk/src/guides/your_first_pallet/with_event.rs +++ /dev/null @@ -1,101 +0,0 @@ -#[frame::pallet(dev_mode)] -pub mod pallet { - use frame::prelude::*; - - #[docify::export] - pub type Balance = u128; - - #[pallet::config] - pub trait Config: frame_system::Config {} - - #[pallet::pallet] - pub struct Pallet(_); - - #[docify::export] - /// Single storage item, of type `Balance`. - #[pallet::storage] - pub type TotalIssuance = StorageValue<_, Balance>; - - #[docify::export] - /// A mapping from `T::AccountId` to `Balance` - #[pallet::storage] - pub type Balances = StorageMap<_, _, T::AccountId, Balance>; - - #[docify::export(impl_pallet)] - #[pallet::call] - impl Pallet { - /// An unsafe mint that can be called by anyone. Not a great idea. - pub fn mint_unsafe( - origin: T::RuntimeOrigin, - dest: T::AccountId, - amount: Balance, - ) -> DispatchResult { - // ensure that this is a signed account, but we don't really check `_anyone`. - let _anyone = ensure_signed(origin)?; - - // update the balances map. Notice how all `` remains as ``. - Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); - // update total issuance. - TotalIssuance::::mutate(|t| *t = Some(t.unwrap_or(0) + amount)); - - Ok(()) - } - - /// Transfer `amount` from `origin` to `dest`. - pub fn transfer( - origin: T::RuntimeOrigin, - dest: T::AccountId, - amount: Balance, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - - // ensure sender has enough balance, and if so, calculate what is left after `amount`. - let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; - if sender_balance < amount { - return Err("NotEnoughBalance".into()) - } - let remainder = sender_balance - amount; - - // update sender and dest balances. - Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); - Balances::::insert(&sender, remainder); - - Ok(()) - } - } - - #[allow(unused)] - impl Pallet { - #[docify::export] - pub fn transfer_better( - origin: T::RuntimeOrigin, - dest: T::AccountId, - amount: Balance, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - - let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; - ensure!(sender_balance >= amount, "NotEnoughBalance"); - let remainder = sender_balance - amount; - - // .. snip - Ok(()) - } - - #[docify::export] - /// Transfer `amount` from `origin` to `dest`. - pub fn transfer_better_checked( - origin: T::RuntimeOrigin, - dest: T::AccountId, - amount: Balance, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - - let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; - let remainder = sender_balance.checked_sub(amount).ok_or("NotEnoughBalance")?; - - // .. snip - Ok(()) - } - } -} diff --git a/docs/sdk/src/guides/your_first_runtime.rs b/docs/sdk/src/guides/your_first_runtime.rs index c58abc1120c1..79f01e66979a 100644 --- a/docs/sdk/src/guides/your_first_runtime.rs +++ b/docs/sdk/src/guides/your_first_runtime.rs @@ -1,3 +1,170 @@ //! # Your first Runtime //! -//! 🚧 +//! This guide will walk you through the steps to add your pallet to a runtime. +//! +//! The good news is, in [`crate::guides::your_first_pallet`], we have already created a _test_ +//! runtime that was used for testing, and a real runtime is not that much different! +//! +//! ## Setup +//! +//! A runtime shares a few similar setup requirements as with a pallet: +//! +//! * importing [`frame`], [`codec`], and [`scale_info`] crates. +//! * following the [`std` feature-gating](crate::polkadot_sdk::substrate#wasm-build) pattern. +//! +//! But, more specifically, it also contains: +//! +//! * a `build.rs` that uses [`substrate_wasm_builder`]. This entails declaring +//! `[build-dependencies]` in the Cargo manifest file: +//! +//! ```ignore +//! [build-dependencies] +//! substrate-wasm-builder = { ... } +//! ``` +//! +//! >Note that a runtime must always be one-runtime-per-crate. You cannot define multiple runtimes +//! per rust crate. +//! +//! You can find the full code of this guide in [`first_runtime`]. +//! +//! ## Your First Runtime +//! +//! The first new property of a real runtime that it must define its +//! [`frame::runtime::prelude::RuntimeVersion`]: +#![doc = docify::embed!("./packages/guides/first-runtime/src/lib.rs", VERSION)] +//! +//! The version contains a number of very important fields, such as `spec_version` and `spec_name` +//! that play an important role in identifying your runtime and its version, more importantly in +//! runtime upgrades. More about runtime upgrades in +//! [`crate::reference_docs::frame_runtime_upgrades_and_migrations`]. +//! +//! Then, a real runtime also contains the `impl` of all individual pallets' `trait Config` for +//! `struct Runtime`, and a [`frame::runtime::prelude::construct_runtime`] macro that amalgamates +//! them all. +//! +//! In the case of our example: +#![doc = docify::embed!("./packages/guides/first-runtime/src/lib.rs", our_config_impl)] +//! +//! In this example, we bring in a number of other pallets from [`frame`] into the runtime, each of +//! their `Config` need to be implemented for `struct Runtime`: +#![doc = docify::embed!("./packages/guides/first-runtime/src/lib.rs", config_impls)] +//! +//! Notice how we use [`frame::pallet_macros::derive_impl`] to provide "default" configuration items +//! for each pallet. Feel free to dive into the definition of each default prelude (eg. +//! [`frame::prelude::frame_system::pallet::config_preludes`]) to learn more which types are exactly +//! used. +//! +//! Recall that in test runtime in [`crate::guides::your_first_pallet`], we provided `type AccountId +//! = u64` to `frame_system`, while in this case we rely on whatever is provided by +//! [`SolochainDefaultConfig`], which is indeed a "real" 32 byte account id. +//! +//! Then, a familiar instance of `construct_runtime` amalgamates all of the pallets: +#![doc = docify::embed!("./packages/guides/first-runtime/src/lib.rs", cr)] +//! +//! Recall from [`crate::reference_docs::wasm_meta_protocol`] that every (real) runtime needs to +//! implement a set of runtime APIs that will then let the node to communicate with it. The final +//! steps of crafting a runtime are related to achieving exactly this. +//! +//! First, we define a number of types that eventually lead to the creation of an instance of +//! [`frame::runtime::prelude::Executive`]. The executive is a handy FRAME utility that, through +//! amalgamating all pallets and further types, implements some of the very very core pieces of the +//! runtime logic, such as how blocks are executed and other runtime-api implementations. +#![doc = docify::embed!("./packages/guides/first-runtime/src/lib.rs", runtime_types)] +//! +//! Finally, we use [`frame::runtime::prelude::impl_runtime_apis`] to implement all of the runtime +//! APIs that the runtime wishes to expose. As you will see in the code, most of these runtime API +//! implementations are merely forwarding calls to `RuntimeExecutive` which handles the actual +//! logic. Given that the implementation block is somewhat large, we won't repeat it here. You can +//! look for `impl_runtime_apis!` in [`first_runtime`]. +//! +//! ```ignore +//! impl_runtime_apis! { +//! impl apis::Core for Runtime { +//! fn version() -> RuntimeVersion { +//! VERSION +//! } +//! +//! fn execute_block(block: Block) { +//! RuntimeExecutive::execute_block(block) +//! } +//! +//! fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { +//! RuntimeExecutive::initialize_block(header) +//! } +//! } +//! +//! // many more trait impls... +//! } +//! ``` +//! +//! And that more or less covers the details of how you would write a real runtime! +//! +//! Once you compile a crate that contains a runtime as above, simply running `cargo build` will +//! generate the wasm blobs and place them under `./target/release/wbuild`, as explained +//! [here](crate::polkadot_sdk::substrate#wasm-build). +//! +//! ## Genesis Configuration +//! +//! Every runtime specifies a number of runtime APIs that help the outer world (most notably, a +//! `node`) know what is the genesis state of this runtime. These APIs are then used to generate +//! what is known as a **Chain Specification, or chain spec for short**. A chain spec is the +//! primary way to run a new chain. +//! +//! These APIs are defined in [`sp_genesis_builder`], and are re-exposed as a part of +//! [`frame::runtime::apis`]. Therefore, the implementation blocks can be found inside of +//! `impl_runtime_apis!` similar to: +//! +//! ```ignore +//! impl_runtime_apis! { +//! impl apis::GenesisBuilder for Runtime { +//! fn build_state(config: Vec) -> GenesisBuilderResult { +//! build_state::(config) +//! } +//! +//! fn get_preset(id: &Option) -> Option> { +//! get_preset::(id, self::genesis_config_presets::get_preset) +//! } +//! +//! fn preset_names() -> Vec { +//! crate::genesis_config_presets::preset_names() +//! } +//! } +//! +//! } +//! ``` +//! +//! The implementation of these function can naturally vary from one runtime to the other, but the +//! overall pattern is common. For the case of this runtime, we do the following: +//! +//! 1. Expose one non-default preset, namely [`sp_genesis_builder::DEV_RUNTIME_PRESET`]. This means +//! our runtime has two "presets" of genesis state in total: `DEV_RUNTIME_PRESET` and `None`. +#![doc = docify::embed!("./packages/guides/first-runtime/src/lib.rs", preset_names)] +//! +//! For `build_state` and `get_preset`, we use the helper functions provide by frame: +//! +//! * [`frame::runtime::prelude::build_state`] and [`frame::runtime::prelude::get_preset`]. +//! +//! Indeed, our runtime needs to specify what its `DEV_RUNTIME_PRESET` genesis state should be like: +#![doc = docify::embed!("./packages/guides/first-runtime/src/lib.rs", development_config_genesis)] +//! +//! For more in-depth information about `GenesisConfig`, `ChainSpec`, the `GenesisBuilder` API and +//! `chain-spec-builder`, see [`crate::reference_docs::chain_spec_genesis`]. +//! +//! ## Next Step +//! +//! See [`crate::guides::your_first_node`]. +//! +//! ## Further Reading +//! +//! 1. To learn more about signed extensions, see [`crate::reference_docs::signed_extensions`]. +//! 2. `AllPalletsWithSystem` is also generated by `construct_runtime`, as explained in +//! [`crate::reference_docs::frame_runtime_types`]. +//! 3. `Executive` supports more generics, most notably allowing the runtime to configure more +//! runtime migrations, as explained in +//! [`crate::reference_docs::frame_runtime_upgrades_and_migrations`]. +//! 4. Learn more about adding and implementing runtime apis in +//! [`crate::reference_docs::custom_runtime_api_rpc`]. +//! 5. To see a complete example of a runtime+pallet that is similar to this guide, please see +//! [`crate::polkadot_sdk::templates`]. +//! +//! [`SolochainDefaultConfig`]: struct@frame_system::pallet::config_preludes::SolochainDefaultConfig diff --git a/docs/sdk/src/lib.rs b/docs/sdk/src/lib.rs index 86ca677d7cef..e2c5fc93479c 100644 --- a/docs/sdk/src/lib.rs +++ b/docs/sdk/src/lib.rs @@ -12,7 +12,8 @@ //! - Start by learning about the the [`polkadot_sdk`], its structure and context. //! - Then, head over to the [`guides`]. This modules contains in-depth guides about the most //! important user-journeys of the Polkadot SDK. -//! - Whilst reading the guides, you might find back-links to [`reference_docs`]. +//! - Whilst reading the guides, you might find back-links to [`reference_docs`]. +//! - [`external_resources`] for a list of 3rd party guides and tutorials. //! - Finally, is the parent website of this crate that contains the //! list of further tools related to the Polkadot SDK. //! diff --git a/docs/sdk/src/meta_contributing.rs b/docs/sdk/src/meta_contributing.rs index e1297151b231..d68d9bca18b1 100644 --- a/docs/sdk/src/meta_contributing.rs +++ b/docs/sdk/src/meta_contributing.rs @@ -69,7 +69,8 @@ //! > what topics are already covered in this crate, and how you can build on top of the information //! > that they already pose, rather than repeating yourself**. //! -//! For more details see the [latest documenting guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/DOCUMENTATION_GUIDELINES.md). +//! For more details see the [latest documenting +//! guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/DOCUMENTATION_GUIDELINES.md). //! //! #### Example: Explaining `#[pallet::call]` //! @@ -132,6 +133,13 @@ //! compromise, but in the long term, we should work towards finding a way to maintain different //! revisions of this crate. //! +//! ## Versioning +//! +//! So long as not deployed in `crates.io`, please notice that all of the information in this crate, +//! namely in [`crate::guides`] and such are compatible with the master branch of `polkadot-sdk`. A +//! few solutions have been proposed to improve this, please see +//! [here](https://github.com/paritytech/polkadot-sdk/issues/6191). +//! //! ## How to Develop Locally //! //! To view the docs specific [`crate`] locally for development, including the correct HTML headers diff --git a/docs/sdk/src/polkadot_sdk/mod.rs b/docs/sdk/src/polkadot_sdk/mod.rs index c089b6729ce5..bf7346b871a1 100644 --- a/docs/sdk/src/polkadot_sdk/mod.rs +++ b/docs/sdk/src/polkadot_sdk/mod.rs @@ -75,6 +75,26 @@ //! runtimes are located under the //! [`polkadot-fellows/runtimes`](https://github.com/polkadot-fellows/runtimes) repository. //! +//! ### Binaries +//! +//! The main binaries that are part of the Polkadot SDK are: + +//! * [`polkadot`]: The Polkadot relay chain node binary, as noted above. +//! * [`polkadot-omni-node`]: A white-labeled parachain collator node. See more in +//! [`crate::reference_docs::omni_node`]. +//! * [`polkadot-parachain-bin`]: The collator node used to run collators for all Polkadot system +//! parachains. +//! * [`frame-omni-bencher`]: a benchmarking tool for FRAME-based runtimes. Nodes typically contain +//! a +//! `benchmark` subcommand that does the same. +//! * [`chain_spec_builder`]: Utility to build chain-specs Nodes typically contain a `build-spec` +//! subcommand that does the same. +//! * [`subkey`]: Substrate's key management utility. +//! * [`substrate-node`](node_cli) is an extensive substrate node that contains the superset of all +//! runtime and node side features. The corresponding runtime, called [`kitchensink_runtime`] +//! contains all of the modules that are provided with `FRAME`. This node and runtime is only used +//! for testing and demonstration. +//! //! ### Summary //! //! The following diagram summarizes how some of the components of Polkadot SDK work together: @@ -116,6 +136,9 @@ //! [`cumulus`]: crate::polkadot_sdk::cumulus //! [`polkadot`]: crate::polkadot_sdk::polkadot //! [`xcm`]: crate::polkadot_sdk::xcm +//! [`frame-omni-bencher`]: https://crates.io/crates/frame-omni-bencher +//! [`polkadot-parachain-bin`]: https://crates.io/crates/polkadot-parachain-bin +//! [`polkadot-omni-node`]: https://crates.io/crates/polkadot-omni-node /// Learn about Cumulus, the framework that transforms [`substrate`]-based chains into /// [`polkadot`]-enabled parachains. diff --git a/docs/sdk/src/polkadot_sdk/substrate.rs b/docs/sdk/src/polkadot_sdk/substrate.rs index 56b89f8c9c2a..ed654c2842c4 100644 --- a/docs/sdk/src/polkadot_sdk/substrate.rs +++ b/docs/sdk/src/polkadot_sdk/substrate.rs @@ -90,22 +90,6 @@ //! //! In order to ensure that the WASM build is **deterministic**, the [Substrate Runtime Toolbox (srtool)](https://github.com/paritytech/srtool) can be used. //! -//! ### Binaries -//! -//! Multiple binaries are shipped with substrate, the most important of which are located in the -//! [`./bin`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/bin) folder. -//! -//! * [`node_cli`] is an extensive substrate node that contains the superset of all runtime and node -//! side features. The corresponding runtime, called [`kitchensink_runtime`] contains all of the -//! modules that are provided with `FRAME`. This node and runtime is only used for testing and -//! demonstration. -//! * [`chain_spec_builder`]: Utility to build more detailed chain-specs for the aforementioned -//! node. Other projects typically contain a `build-spec` subcommand that does the same. -//! * [`node_template`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/bin/node): -//! a template node that contains a minimal set of features and can act as a starting point of a -//! project. -//! * [`subkey`]: Substrate's key management utility. -//! //! ### Anatomy of a Binary Crate //! //! From the above, [`node_cli`]/[`kitchensink_runtime`] and `node-template` are essentially diff --git a/docs/sdk/src/reference_docs/chain_spec_genesis.rs b/docs/sdk/src/reference_docs/chain_spec_genesis.rs index a2e22d1ed1eb..3326f433f288 100644 --- a/docs/sdk/src/reference_docs/chain_spec_genesis.rs +++ b/docs/sdk/src/reference_docs/chain_spec_genesis.rs @@ -152,10 +152,13 @@ //! presets and build the chain specification file. It is possible to use the tool with the //! [_demonstration runtime_][`chain_spec_guide_runtime`]. To build the required packages, just run //! the following command: +//! //! ```ignore //! cargo build -p staging-chain-spec-builder -p chain-spec-guide-runtime --release //! ``` +//! //! The `chain-spec-builder` util can also be installed with `cargo install`: +//! //! ```ignore //! cargo install staging-chain-spec-builder //! cargo build -p chain-spec-guide-runtime --release diff --git a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs index cf9e58791492..68d7d31f67f3 100644 --- a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs +++ b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs @@ -1,23 +1,212 @@ //! # FRAME Benchmarking and Weights. //! -//! Notes: +//! This reference doc explores the concept of weights within Polkadot-SDK runtimes, and more +//! specifically how FRAME-based runtimes handle it. //! -//! On Weight as a concept. +//! ## Metering //! -//! - Why we need it. Super important. People hate this. We need to argue why it is worth it. -//! - Axis of weight: PoV + Time. -//! - pre dispatch weight vs. metering and post dispatch correction. -//! - mention that we will do this for PoV -//! - you can manually refund using `DispatchResultWithPostInfo`. -//! - Technically you can have weights with any benchmarking framework. You just need one number to -//! be computed pre-dispatch. But FRAME gives you a framework for this. -//! - improve documentation of `#[weight = ..]` and `#[pallet::weight(..)]`. All syntax variation -//! should be covered. +//! The existence of "weight" as a concept in Polkadot-SDK is a direct consequence of the usage of +//! WASM as a virtual machine. Unlike a metered virtual machine like EVM, where every instruction +//! can have a (fairly) deterministic "cost" (also known as "gas price") associated with it, WASM is +//! a stack machine with more complex instruction set, and more unpredictable execution times. This +//! means that unlike EVM, it is not possible to implement a "metering" system in WASM. A metering +//! system is one in which instructions are executed one by one, and the cost/gas is stored in an +//! accumulator. The execution may then halt once a gas limit is reached. //! -//! On FRAME benchmarking machinery: +//! In Polkadot-SDK, the WASM runtime is not assumed to be metered. //! -//! - Component analysis, why everything must be linear. -//! - How to write benchmarks, how you must think of worst case. -//! - How to run benchmarks. +//! ## Trusted Code //! -//! - +//! Another important difference is that EVM is mostly used to express smart contracts, which are +//! foreign and untrusted codes from the perspective of the blockchain executing them. In such +//! cases, metering is crucial, in order to ensure a malicious code cannot consume more gas than +//! expected. +//! +//! This assumption does not hold about the runtime of Polkadot-SDK-based blockchains. The runtime +//! is trusted code, and it is assumed to be written by the same team/developers who are running the +//! blockchain itself. Therefore, this assumption of "untrusted foreign code" does not hold. +//! +//! This is why the runtime can opt for a more performant, more flexible virtual machine like WASM, +//! and get away without having metering. +//! +//! ## Benchmarking +//! +//! With the matter of untrusted code execution out of the way, the need for strict metering goes +//! out of the way. Yet, it would still be very beneficial for block producers to be able to know an +//! upper bound on how much resources a operation is going to consume before actually executing that +//! operation. This is why FRAME has a toolkit for benchmarking pallets: So that this upper bound +//! can be empirically determined. +//! +//! > Note: Benchmarking is a static analysis: It is all about knowing the upper bound of how much +//! > resources an operation takes statically, without actually executing it. In the context of +//! > FRAME extrinsics, this static-ness is expressed by the keyword "pre-dispatch". +//! +//! To understand why this upper bound is needed, consider the following: A block producer knows +//! they have 20ms left to finish producing their block, and wishes to include more transactions in +//! the block. Yet, in a metered environment, it would not know which transaction is likely to fit +//! the 20ms. In a benchmarked environment, it can examine the transactions for their upper bound, +//! and include the ones that are known to fit based on the worst case. +//! +//! The benchmarking code can be written as a part of FRAME pallet, using the macros provided in +//! [`frame_benchmarking`]. See any of the existing pallets in `polkadot-sdk`, or the pallets in our +//! [`crate::polkadot_sdk::templates`] for examples. +//! +//! ## Weight +//! +//! Finally, [`sp_weights::Weight`] is the output of the benchmarking process. It is a +//! two-dimensional data structure that demonstrates the resources consumed by a given block of +//! code (for example, a transaction). The two dimensions are: +//! +//! * reference time: The time consumed in pico-seconds, on a reference hardware. +//! * proof size: The amount of storage proof necessary to re-execute the block of code. This is +//! mainly needed for parachain <> relay-chain verification. +//! +//! ## How To Write Benchmarks: Worst Case +//! +//! The most important detail about writing benchmarking code is that it must be written such that +//! it captures the worst case execution of any block of code. +//! +//! Consider: +#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer)] +//! +//! If this block of code is to be benchmarked, then the benchmarking code must be written such that +//! it captures the worst case. +//! +//! ## Gluing Pallet Benchmarking with Runtime +//! +//! FRAME pallets are mandated to provide their own benchmarking code. Runtimes contain the +//! boilerplate needed to run these benchmarking (see [Running Benchmarks +//! below](#running-benchmarks)). The outcome of running these benchmarks are meant to be fed back +//! into the pallet via a conventional `trait WeightInfo` on `Config`: +#![doc = docify::embed!("src/reference_docs/frame_benchmarking_weight.rs", WeightInfo)] +//! +//! Then, individual functions of this trait are the final values that we assigned to the +//! [`frame::pallet_macros::weight`] attribute: +#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer_2)] +//! +//! ## Manual Refund +//! +//! Back to the assumption of writing benchmarks for worst case: Sometimes, the pre-dispatch weight +//! significantly differ from the post-dispatch actual weight consumed. This can be expressed with +//! the following FRAME syntax: +#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer_3)] +//! +//! ## Running Benchmarks +//! +//! Two ways exist to run the benchmarks of a runtime. +//! +//! 1. The old school way: Most Polkadot-SDK based nodes (such as the ones integrated in +//! [`templates`]) have an a `benchmark` subcommand integrated into themselves. +//! 2. The more [`crate::reference_docs::omni_node`] compatible way of running the benchmarks would +//! be using [`frame-omni-bencher`] CLI, which only relies on a runtime. +//! +//! Note that by convention, the runtime and pallets always have their benchmarking code feature +//! gated as behind `runtime-benchmarks`. So, the runtime should be compiled with `--features +//! runtime-benchmarks`. +//! +//! ## Automatic Refund of `proof_size`. +//! +//! A new feature in FRAME allows the runtime to be configured for "automatic refund" of the proof +//! size weight. This is very useful for maximizing the throughput of parachains. Please see: +//! [`crate::guides::enable_pov_reclaim`]. +//! +//! ## Summary +//! +//! Polkadot-SDK runtimes use a more performant VM, namely WASM, which does not have metering. In +//! return they have to be benchmarked to provide an upper bound on the resources they consume. This +//! upper bound is represented as [`sp_weights::Weight`]. +//! +//! ## Future: PolkaVM +//! +//! With the transition of Polkadot relay chain to [JAM], a set of new features are being +//! introduced, one of which being a new virtual machine named [PolkaVM] that is as flexible as +//! WASM, but also capable of metering. This might alter the future of benchmarking in FRAME and +//! Polkadot-SDK, rendering them not needed anymore once PolkaVM is fully integrated into +//! Polkadot-sdk. For a basic explanation of JAM and PolkaVM, see [here](https://blog.kianenigma.com/posts/tech/demystifying-jam/#pvm). +//! +//! +//! [`frame-omni-bencher`]: https://crates.io/crates/frame-omni-bencher +//! [`templates`]: crate::polkadot_sdk::templates +//! [PolkaVM]: https://github.com/koute/polkavm +//! [JAM]: https://graypaper.com + +#[frame::pallet(dev_mode)] +#[allow(unused_variables, unreachable_code, unused, clippy::diverging_sub_expression)] +pub mod pallet { + use frame::prelude::*; + + #[docify::export] + pub trait WeightInfo { + fn simple_transfer() -> Weight; + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + #[docify::export] + #[pallet::weight(10_000)] + pub fn simple_transfer( + origin: OriginFor, + destination: T::AccountId, + amount: u32, + ) -> DispatchResult { + let destination_exists = todo!(); + if destination_exists { + // simpler code path + } else { + // more complex code path + } + Ok(()) + } + + #[docify::export] + #[pallet::weight(T::WeightInfo::simple_transfer())] + pub fn simple_transfer_2( + origin: OriginFor, + destination: T::AccountId, + amount: u32, + ) -> DispatchResult { + let destination_exists = todo!(); + if destination_exists { + // simpler code path + } else { + // more complex code path + } + Ok(()) + } + + #[docify::export] + // This is the worst-case, pre-dispatch weight. + #[pallet::weight(T::WeightInfo::simple_transfer())] + pub fn simple_transfer_3( + origin: OriginFor, + destination: T::AccountId, + amount: u32, + ) -> DispatchResultWithPostInfo { + // ^^ Notice the new return type + let destination_exists = todo!(); + if destination_exists { + // simpler code path + // Note that need for .into(), to convert `()` to `PostDispatchInfo` + // See: https://paritytech.github.io/polkadot-sdk/master/frame_support/dispatch/struct.PostDispatchInfo.html#impl-From%3C()%3E-for-PostDispatchInfo + Ok(().into()) + } else { + // more complex code path + let actual_weight = + todo!("this can likely come from another benchmark that is NOT the worst case"); + let pays_fee = todo!("You can set this to `Pays::Yes` or `Pays::No` to change if this transaction should pay fees"); + Ok(frame::deps::frame_support::dispatch::PostDispatchInfo { + actual_weight: Some(actual_weight), + pays_fee, + }) + } + } + } +} diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs index 9cf5605a88ba..e47eece784c4 100644 --- a/docs/sdk/src/reference_docs/mod.rs +++ b/docs/sdk/src/reference_docs/mod.rs @@ -78,7 +78,6 @@ pub mod frame_system_accounts; pub mod development_environment_advice; /// Learn about benchmarking and weight. -// TODO: @shawntabrizi @ggwpez https://github.com/paritytech/polkadot-sdk-docs/issues/50 pub mod frame_benchmarking_weight; /// Learn about the token-related logic in FRAME and how to apply it to your use case. @@ -109,3 +108,6 @@ pub mod umbrella_crate; /// Learn about how to create custom RPC endpoints and runtime APIs. pub mod custom_runtime_api_rpc; + +/// The [`polkadot-omni-node`](https://crates.io/crates/polkadot-omni-node) and its related binaries. +pub mod omni_node; diff --git a/docs/sdk/src/reference_docs/omni_node.rs b/docs/sdk/src/reference_docs/omni_node.rs new file mode 100644 index 000000000000..44d63704a458 --- /dev/null +++ b/docs/sdk/src/reference_docs/omni_node.rs @@ -0,0 +1,185 @@ +//! # (Omni) Node +//! +//! This reference doc elaborates on what a Polkadot-SDK/Substrate node software is, and what +//! various ways exist to run one. +//! +//! The node software, as denoted in [`crate::reference_docs::wasm_meta_protocol`], is everything in +//! a blockchain other than the WASM runtime. It contains common components such as the database, +//! networking, RPC server and consensus. Substrate-based nodes are native binaries that are +//! compiled down from the Rust source code. The `node` folder in any of the [`templates`] are +//! examples of this source. +//! +//! > Note: A typical node also contains a lot of other tools (exposed as subcommands) that are +//! > useful for operating a blockchain, such as the ones noted in +//! > [`polkadot_omni_node_lib::cli::Cli::subcommand`]. +//! +//! ## Node <> Runtime Interdependence +//! +//! While in principle the node can be mostly independent of the runtime, for various reasons, such +//! as the [native runtime](crate::reference_docs::wasm_meta_protocol#native-runtime), the node and +//! runtime were historically tightly linked together. Another reason is that the node and the +//! runtime need to be in agreement about which consensus algorithm they use, as described +//! [below](#consensus-engine). +//! +//! Specifically, the node relied on the existence of a linked runtime, and *could only reliably run +//! that runtime*. This is why if you look at any of the [`templates`], they are all composed of a +//! node, and a runtime. +//! +//! Moreover, the code and API of each of these nodes was historically very advanced, and tailored +//! towards those who wish to customize many of the node components at depth. +//! +//! > The notorious `service.rs` in any node template is a good example of this. +//! +//! A [trend](https://github.com/paritytech/polkadot-sdk/issues/62) has already been undergoing in +//! order to de-couple the node and the runtime for a long time. The north star of this effort is +//! twofold : +//! +//! 1. develop what can be described as an "omni-node": A node that can run most runtimes. +//! 2. provide a cleaner abstraction for creating a custom node. +//! +//! While a single omni-node running *all possible runtimes* is not feasible, the +//! [`polkadot-omni-node`] is an attempt at creating the former, and the [`polkadot_omni_node_lib`] +//! is the latter. +//! +//! > Note: The OmniNodes are mainly focused on the development needs of **Polkadot +//! > parachains ONLY**, not (Substrate) solo-chains. For the time being, solo-chains are not +//! > supported by the OmniNodes. This might change in the future. +//! +//! ## Types of Nodes +//! +//! With the emergence of the OmniNodes, let's look at the various Node options available to a +//! builder. +//! +//! ### [`polkadot-omni-node`] +//! +//! [`polkadot-omni-node`] is a white-labeled binary, released as a part of Polkadot SDK that is +//! capable of meeting the needs of most Polkadot parachains. +//! +//! It can act as the collator of a parachain in production, with all the related auxillary +//! functionalities that a normal collator node has: RPC server, archiving state, etc. Moreover, it +//! can also run the wasm blob of the parachain locally for testing and development. +//! +//! ### [`polkadot_omni_node_lib`] +//! +//! [`polkadot_omni_node_lib`] is the library version of the above, which can be used to create a +//! fresh parachain node, with a some limited configuration options using a lean API. +//! +//! ### Old School Nodes +//! +//! The existing node architecture, as seen in the [`templates`], is still available for those who +//! want to have full control over the node software. +//! +//! ### Summary +//! +//! We can summarize the choices for the node software of any given user of Polkadot-SDK, wishing to +//! deploy a parachain into 3 categories: +//! +//! 1. **Use the [`polkadot-omni-node`]**: This is the easiest way to get started, and is the most +//! likely to be the best choice for most users. +//! * can run almost any runtime with [`--dev-block-time`] +//! 2. **Use the [`polkadot_omni_node_lib`]**: This is the best choice for those who want to have +//! slightly more control over the node software, such as embedding a custom chain-spec. +//! 3. **Use the old school nodes**: This is the best choice for those who want to have full control +//! over the node software, such as changing the consensus engine, altering the transaction pool, +//! and so on. +//! +//! ## _OmniTools_: User Journey +//! +//! All in all, the user journey of a team/builder, in the OmniNode world is as follows: +//! +//! * The [`templates`], most notably the [`parachain-template`] is the canonical starting point. +//! That being said, the node code of the templates (which may be eventually +//! removed/feature-gated) is no longer of relevance. The only focus is in the runtime, and +//! obtaining a `.wasm` file. References: +//! * [`crate::guides::your_first_pallet`] +//! * [`crate::guides::your_first_runtime`] +//! * If need be, the weights of the runtime need to be updated using `frame-omni-bencher`. +//! References: +//! * [`crate::reference_docs::frame_benchmarking_weight`] +//! * Next, [`chain-spec-builder`] is used to generate a `chain_spec.json`, either for development, +//! or for production. References: +//! * [`crate::reference_docs::chain_spec_genesis`] +//! * For local development, the following options are available: +//! * `polkadot-omni-node` (notably, with [`--dev-block-time`]). References: +//! * [`crate::guides::your_first_node`] +//! * External tools such as `chopsticks`, `zombienet`. +//! * See the `README.md` file of the `polkadot-sdk-parachain-template`. +//! * For production `polkadot-omni-node` can be used out of the box. +//! * For further customization [`polkadot_omni_node_lib`] can be used. +//! +//! ## Appendix +//! +//! This section describes how the interdependence between the node and the runtime is related to +//! the consensus engine. This information is useful for those who want to understand the +//! historical context of the node and the runtime. +//! +//! ### Consensus Engine +//! +//! In any given substrate-based chain, both the node and the runtime will have their own +//! opinion/information about what consensus engine is going to be used. +//! +//! In practice, the majority of the implementation of any consensus engine is in the node side, but +//! the runtime also typically needs to expose a custom runtime-api to enable the particular +//! consensus engine to work, and that particular runtime-api is implemented by a pallet +//! corresponding to that consensus engine. +//! +//! For example, taking a snippet from [`solochain_template_runtime`], the runtime has to provide +//! this additional runtime-api (compared to [`minimal_template_runtime`]), if the node software is +//! configured to use the Aura consensus engine: +//! +//! ```text +//! impl sp_consensus_aura::AuraApi for Runtime { +//! fn slot_duration() -> sp_consensus_aura::SlotDuration { +//! ... +//! } +//! fn authorities() -> Vec { +//! ... +//! } +//! } +//! ``` +//! +//! For simplicity, we can break down "consensus" into two main parts: +//! +//! * Block Authoring: Deciding who gets to produce the next block. +//! * Finality: Deciding when a block is considered final. +//! +//! For block authoring, there are a number of options: +//! +//! * [`sc_consensus_manual_seal`]: Useful for testing, where any node can produce a block at any +//! time. This is often combined with a fixed interval at which a block is produced. +//! * [`sc_consensus_aura`]/[`pallet_aura`]: A simple round-robin block authoring mechanism. +//! * [`sc_consensus_babe`]/[`pallet_babe`]: A more advanced block authoring mechanism, capable of +//! anonymizing the next block author. +//! * [`sc_consensus_pow`]: Proof of Work block authoring. +//! +//! For finality, there is one main option shipped with polkadot-sdk: +//! +//! * [`sc_consensus_grandpa`]/[`pallet_grandpa`]: A finality gadget that uses a voting mechanism to +//! decide when a block +//! +//! **The most important lesson here is that the node and the runtime must have matching consensus +//! components.** +//! +//! ### Consequences for OmniNode +//! +//! +//! The consequence of the above is that anyone using the OmniNode must also be aware of the +//! consensus system used in the runtime, and be aware if it is matching that of the OmniNode or +//! not. For the time being, [`polkadot-omni-node`] only supports: +//! +//! * Parachain-based Aura consensus, with 6s async-backing block-time, and before full elastic +//! scaling). [`polkadot_omni_node_lib::cli::Cli::experimental_use_slot_based`] for fixed factor +//! scaling (a step +//! * Ability to run any runtime with [`--dev-block-time`] flag. This uses +//! [`sc_consensus_manual_seal`] under the hood, and has no restrictions on the runtime's +//! consensus. +//! +//! [This](https://github.com/paritytech/polkadot-sdk/issues/5565) future improvement to OmniNode +//! aims to make such checks automatic. +//! +//! +//! [`templates`]: crate::polkadot_sdk::templates +//! [`parachain-template`]: https://github.com/paritytech/polkadot-sdk-parachain-template +//! [`--dev-block-time`]: polkadot_omni_node_lib::cli::Cli::dev_block_time +//! [`polkadot-omni-node`]: https://crates.io/crates/polkadot-omni-node +//! [`chain-spec-builder`]: https://crates.io/crates/staging-chain-spec-builder diff --git a/docs/sdk/src/reference_docs/umbrella_crate.rs b/docs/sdk/src/reference_docs/umbrella_crate.rs index 1074cde37693..8d9bcdfc2089 100644 --- a/docs/sdk/src/reference_docs/umbrella_crate.rs +++ b/docs/sdk/src/reference_docs/umbrella_crate.rs @@ -5,6 +5,7 @@ //! crate. This helps with selecting the right combination of crate versions, since otherwise 3rd //! party tools are needed to select a compatible set of versions. //! +//! //! ## Features //! //! The umbrella crate supports no-std builds and can therefore be used in the runtime and node. diff --git a/prdoc/pr_6094.prdoc b/prdoc/pr_6094.prdoc new file mode 100644 index 000000000000..23391c889155 --- /dev/null +++ b/prdoc/pr_6094.prdoc @@ -0,0 +1,21 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Polkadot OmniNode Docs + +doc: + - audience: ... + description: | + Adds documentation in https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/index.html and rust-docs for polkadot-omni-node project. + +crates: + - name: sp-genesis-builder + bump: patch + - name: pallet-aura + bump: patch + - name: polkadot-omni-node-lib + bump: patch + - name: polkadot-sdk-frame # since the crate is "experimental, we don't need to bump yet." + bump: major + - name: polkadot-omni-node + bump: patch diff --git a/substrate/bin/node/cli/src/cli.rs b/substrate/bin/node/cli/src/cli.rs index c0dcacb2e4b4..1d7001a5dccf 100644 --- a/substrate/bin/node/cli/src/cli.rs +++ b/substrate/bin/node/cli/src/cli.rs @@ -59,6 +59,7 @@ pub enum Subcommand { Inspect(node_inspect::cli::InspectCmd), /// Sub-commands concerned with benchmarking. + /// /// The pallet benchmarking moved to the `pallet` sub-command. #[command(subcommand)] Benchmark(frame_benchmarking_cli::BenchmarkCmd), diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 595fb5a19b04..2d0daf82997d 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -44,8 +44,10 @@ sp-offchain = { optional = true, workspace = true } sp-session = { optional = true, workspace = true } sp-consensus-aura = { optional = true, workspace = true } sp-consensus-grandpa = { optional = true, workspace = true } +sp-genesis-builder = { optional = true, workspace = true } sp-inherents = { optional = true, workspace = true } sp-storage = { optional = true, workspace = true } +sp-keyring = { optional = true, workspace = true } frame-executive = { optional = true, workspace = true } frame-system-rpc-runtime-api = { optional = true, workspace = true } @@ -73,7 +75,9 @@ runtime = [ "sp-block-builder", "sp-consensus-aura", "sp-consensus-grandpa", + "sp-genesis-builder", "sp-inherents", + "sp-keyring", "sp-offchain", "sp-session", "sp-storage", @@ -97,8 +101,10 @@ std = [ "sp-consensus-aura?/std", "sp-consensus-grandpa?/std", "sp-core/std", + "sp-genesis-builder?/std", "sp-inherents?/std", "sp-io/std", + "sp-keyring?/std", "sp-offchain?/std", "sp-runtime/std", "sp-session?/std", diff --git a/substrate/frame/aura/src/lib.rs b/substrate/frame/aura/src/lib.rs index f829578fb285..c74e864ea0d9 100644 --- a/substrate/frame/aura/src/lib.rs +++ b/substrate/frame/aura/src/lib.rs @@ -400,7 +400,9 @@ impl OnTimestampSet for Pallet { assert_eq!( CurrentSlot::::get(), timestamp_slot, - "Timestamp slot must match `CurrentSlot`" + "Timestamp slot must match `CurrentSlot`. This likely means that the configured block \ + time in the node and/or rest of the runtime is not compatible with Aura's \ + `SlotDuration`", ); } } diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index fcd96b40c3c4..ade1095cc504 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -222,7 +222,12 @@ pub mod runtime { // Types often used in the runtime APIs. pub use sp_core::OpaqueMetadata; + pub use sp_genesis_builder::{ + PresetId, Result as GenesisBuilderResult, DEV_RUNTIME_PRESET, + LOCAL_TESTNET_RUNTIME_PRESET, + }; pub use sp_inherents::{CheckInherentsResult, InherentData}; + pub use sp_keyring::AccountKeyring; pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; } @@ -246,6 +251,7 @@ pub mod runtime { pub use sp_block_builder::*; pub use sp_consensus_aura::*; pub use sp_consensus_grandpa::*; + pub use sp_genesis_builder::*; pub use sp_offchain::*; pub use sp_session::runtime_api::*; pub use sp_transaction_pool::runtime_api::*; @@ -396,8 +402,12 @@ pub mod deps { #[cfg(feature = "runtime")] pub use sp_consensus_grandpa; #[cfg(feature = "runtime")] + pub use sp_genesis_builder; + #[cfg(feature = "runtime")] pub use sp_inherents; #[cfg(feature = "runtime")] + pub use sp_keyring; + #[cfg(feature = "runtime")] pub use sp_offchain; #[cfg(feature = "runtime")] pub use sp_storage; diff --git a/substrate/primitives/genesis-builder/src/lib.rs b/substrate/primitives/genesis-builder/src/lib.rs index 07317bc4cb52..4763aa06341e 100644 --- a/substrate/primitives/genesis-builder/src/lib.rs +++ b/substrate/primitives/genesis-builder/src/lib.rs @@ -17,17 +17,33 @@ #![cfg_attr(not(feature = "std"), no_std)] -//! Substrate genesis config builder +//! # Substrate genesis config builder. //! -//! For FRAME based runtimes, this runtime interface provides means to interact with -//! `RuntimeGenesisConfig`. Runtime provides a default `RuntimeGenesisConfig` structure in a form of -//! the JSON blob. +//! This crate contains [`GenesisBuilder`], a runtime-api to be implemented by runtimes, in order to +//! express their genesis state. //! -//! For non-FRAME runtimes this interface is intended to build genesis state of the runtime basing -//! on some input arbitrary bytes array. This documentation uses term `RuntimeGenesisConfig`, which -//! for non-FRAME runtimes may be understood as the runtime-side entity representing initial runtime -//! configuration. The representation of the preset is an arbitrary `Vec` and does not -//! necessarily have to represent a JSON blob. +//! The overall flow of the methods in [`GenesisBuilder`] is as follows: +//! +//! 1. [`GenesisBuilder::preset_names`]: A runtime exposes a number of different +//! `RuntimeGenesisConfig` variations, each of which is called a `preset`, and is identified by a +//! [`PresetId`]. All runtimes are encouraged to expose at least [`DEV_RUNTIME_PRESET`] and +//! [`LOCAL_TESTNET_RUNTIME_PRESET`] presets for consistency. +//! 2. [`GenesisBuilder::get_preset`]: Given a `PresetId`, this the runtime returns the JSON blob +//! representation of the `RuntimeGenesisConfig` for that preset. This JSON blob is often mixed +//! into the broader `chain_spec`. If `None` is given, [`GenesisBuilder::get_preset`] provides a +//! JSON represention of the default `RuntimeGenesisConfig` (by simply serializing the +//! `RuntimeGenesisConfig::default()` value into JSON format). This is used as a base for +//! applying patches / presets. + +//! 3. [`GenesisBuilder::build_state`]: Given a JSON blob, this method should deserialize it and +//! enact it (using `frame_support::traits::BuildGenesisConfig` for Frame-based runtime), +//! essentially writing it to the state. +//! +//! The first two flows are often done in between a runtime, and the `chain_spec_builder` binary. +//! The latter is used when a new blockchain is launched to enact and store the genesis state. See +//! the documentation of `chain_spec_builder` for more info. +//! +//! ## Patching //! //! The runtime may provide a number of partial predefined `RuntimeGenesisConfig` configurations in //! the form of patches which shall be applied on top of the default `RuntimeGenesisConfig`. The @@ -35,19 +51,22 @@ //! customized in the default runtime genesis config. These predefined configurations are referred //! to as presets. //! -//! This allows the runtime to provide a number of predefined configs (e.g. for different -//! testnets or development) without neccessity to leak the runtime types outside the itself (e.g. -//! node or chain-spec related tools). +//! This allows the runtime to provide a number of predefined configs (e.g. for different testnets +//! or development) without necessarily to leak the runtime types outside itself (e.g. node or +//! chain-spec related tools). +//! +//! ## FRAME vs. non-FRAME +//! +//! For FRAME based runtimes [`GenesisBuilder`] provides means to interact with +//! `RuntimeGenesisConfig`. +//! +//! For non-FRAME runtimes this interface is intended to build genesis state of the runtime basing +//! on some input arbitrary bytes array. This documentation uses term `RuntimeGenesisConfig`, which +//! for non-FRAME runtimes may be understood as the "runtime-side entity representing initial +//! runtime genesis configuration". The representation of the preset is an arbitrary `Vec` and +//! does not necessarily have to represent a JSON blob. //! -//! This Runtime API allows to interact with `RuntimeGenesisConfig`, in particular: -//! - provide the list of available preset names, -//! - provide a number of named presets of `RuntimeGenesisConfig`, -//! - provide a JSON represention of the default `RuntimeGenesisConfig` (by simply serializing the -//! default `RuntimeGenesisConfig` struct into JSON format), -//! - deserialize the full `RuntimeGenesisConfig` from given JSON blob and put the resulting -//! `RuntimeGenesisConfig` structure into the state storage creating the initial runtime's state. -//! Allows to build customized genesis. This operation internally calls `GenesisBuild::build` -//! function for all runtime pallets. +//! ## Genesis Block State //! //! Providing externalities with an empty storage and putting `RuntimeGenesisConfig` into storage //! (by calling `build_state`) allows to construct the raw storage of `RuntimeGenesisConfig` @@ -75,14 +94,15 @@ pub const DEV_RUNTIME_PRESET: &'static str = "development"; pub const LOCAL_TESTNET_RUNTIME_PRESET: &'static str = "local_testnet"; sp_api::decl_runtime_apis! { - /// API to interact with RuntimeGenesisConfig for the runtime + /// API to interact with `RuntimeGenesisConfig` for the runtime pub trait GenesisBuilder { /// Build `RuntimeGenesisConfig` from a JSON blob not using any defaults and store it in the /// storage. /// - /// In the case of a FRAME-based runtime, this function deserializes the full `RuntimeGenesisConfig` from the given JSON blob and - /// puts it into the storage. If the provided JSON blob is incorrect or incomplete or the - /// deserialization fails, an error is returned. + /// In the case of a FRAME-based runtime, this function deserializes the full + /// `RuntimeGenesisConfig` from the given JSON blob and puts it into the storage. If the + /// provided JSON blob is incorrect or incomplete or the deserialization fails, an error + /// is returned. /// /// Please note that provided JSON blob must contain all `RuntimeGenesisConfig` fields, no /// defaults will be used. @@ -91,7 +111,7 @@ sp_api::decl_runtime_apis! { /// Returns a JSON blob representation of the built-in `RuntimeGenesisConfig` identified by /// `id`. /// - /// If `id` is `None` the function returns JSON blob representation of the default + /// If `id` is `None` the function should return JSON blob representation of the default /// `RuntimeGenesisConfig` struct of the runtime. Implementation must provide default /// `RuntimeGenesisConfig`. /// diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 464cad4e3da0..4f914a823bf6 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -324,7 +324,7 @@ impl_runtime_apis! { } fn preset_names() -> Vec { - self::genesis_config_presets::preset_names() + crate::genesis_config_presets::preset_names() } } } diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index 322c91f4f136..394bde0be770 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -76,6 +76,8 @@ fn local_testnet_genesis() -> Value { ], Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), Sr25519Keyring::Alice.to_account_id(), + // TODO: this is super opaque, how should one know they should configure this? add to + // README! 1000.into(), ) }