docs: add Pezkuwi-SDK documentation

- Technical documentation
- SDK guides
- Architecture overview
- Whitepaper
- Contributor guides
This commit is contained in:
2025-12-13 05:55:40 +03:00
commit b4dfaaf5bb
154 changed files with 13550 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
[package]
name = "pezkuwi-sdk-docs"
description = "The one stop shop for developers of the pezkuwi-sdk"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage = "https://docs.pezkuwichain.io/sdk/"
repository.workspace = true
authors.workspace = true
edition.workspace = true
# This crate is not publish-able to crates.io for now because of docify.
publish = false
version = "0.0.1"
[lints]
workspace = true
[dependencies]
# Needed for all FRAME-based code
codec = { workspace = true }
frame = { features = [
"experimental",
"runtime",
], workspace = true, default-features = true }
pallet-contracts = { workspace = true }
pallet-default-config-example = { workspace = true, default-features = true }
pallet-example-offchain-worker = { workspace = true, default-features = true }
pallet-examples = { workspace = true }
scale-info = { workspace = true }
# How we build docs in rust-docs
docify = { workspace = true }
serde_json = { workspace = true }
simple-mermaid = { workspace = true }
# Pezkuwi SDK deps, typically all should only be in scope such that we can link to their doc item.
chain-spec-builder = { workspace = true, default-features = true }
frame-benchmarking = { workspace = true }
frame-executive = { workspace = true }
frame-metadata-hash-extension = { workspace = true, default-features = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
kitchensink-runtime = { workspace = true }
log = { workspace = true, default-features = true }
node-cli = { workspace = true }
pallet-example-authorization-tx-extension = { workspace = true, default-features = true }
pallet-example-single-block-migrations = { workspace = true, default-features = true }
pezkuwi-sdk = { features = [
"runtime-full",
], workspace = true, default-features = true }
subkey = { workspace = true, default-features = true }
# Substrate Client
sc-chain-spec = { workspace = true, default-features = true }
sc-cli = { workspace = true, default-features = true }
sc-client-db = { workspace = true, default-features = true }
sc-consensus-aura = { workspace = true, default-features = true }
sc-consensus-babe = { workspace = true, default-features = true }
sc-consensus-beefy = { workspace = true, default-features = true }
sc-consensus-grandpa = { workspace = true, default-features = true }
sc-consensus-manual-seal = { workspace = true, default-features = true }
sc-consensus-pow = { workspace = true, default-features = true }
sc-executor = { workspace = true, default-features = true }
sc-network = { workspace = true, default-features = true }
sc-rpc = { workspace = true, default-features = true }
sc-rpc-api = { workspace = true, default-features = true }
sc-service = { workspace = true, default-features = true }
substrate-wasm-builder = { workspace = true, default-features = true }
# Cumulus
cumulus-client-service = { workspace = true, default-features = true }
cumulus-pallet-aura-ext = { workspace = true, default-features = true }
cumulus-pallet-teyrchain-system = { workspace = true, default-features = true }
cumulus-pallet-weight-reclaim = { workspace = true, default-features = true }
cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true }
teyrchain-info = { workspace = true, default-features = true }
# Omni Node
pezkuwi-omni-node-lib = { workspace = true, default-features = true }
# Pallets and FRAME internals
pallet-asset-conversion-tx-payment = { workspace = true, default-features = true }
pallet-asset-tx-payment = { workspace = true, default-features = true }
pallet-assets = { workspace = true, default-features = true }
pallet-aura = { workspace = true, default-features = true }
pallet-babe = { workspace = true, default-features = true }
pallet-balances = { workspace = true, default-features = true }
pallet-collective = { workspace = true, default-features = true }
pallet-democracy = { workspace = true, default-features = true }
pallet-grandpa = { workspace = true, default-features = true }
pallet-nfts = { workspace = true, default-features = true }
pallet-preimage = { workspace = true, default-features = true }
pallet-scheduler = { workspace = true, default-features = true }
pallet-skip-feeless-payment = { workspace = true, default-features = true }
pallet-timestamp = { workspace = true, default-features = true }
pallet-transaction-payment = { workspace = true, default-features = true }
pallet-uniques = { workspace = true, default-features = true }
# Primitives
sp-api = { workspace = true, default-features = true }
sp-arithmetic = { workspace = true, default-features = true }
sp-core = { workspace = true, default-features = true }
sp-genesis-builder = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
sp-keyring = { workspace = true, default-features = true }
sp-offchain = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }
sp-runtime-interface = { workspace = true, default-features = true }
sp-std = { workspace = true, default-features = true }
sp-storage = { workspace = true, default-features = true }
sp-tracing = { workspace = true, default-features = true }
sp-version = { workspace = true, default-features = true }
sp-weights = { workspace = true, default-features = true }
# XCM
pallet-xcm = { workspace = true }
xcm = { workspace = true, default-features = true }
xcm-builder = { workspace = true }
xcm-docs = { workspace = true }
xcm-executor = { workspace = true }
xcm-simulator = { workspace = true }
# Runtime guides
chain-spec-guide-runtime = { workspace = true, default-features = true }
# Templates
minimal-template-runtime = { workspace = true, default-features = true }
solochain-template-runtime = { workspace = true, default-features = true }
# local packages
first-runtime = { workspace = true, default-features = true }
[dev-dependencies]
assert_cmd = { workspace = true }
cmd_lib = { workspace = true }
rand = { workspace = true, default-features = true }
tokio = { workspace = true }
[features]
runtime-benchmarks = [
"chain-spec-builder/runtime-benchmarks",
"chain-spec-guide-runtime/runtime-benchmarks",
"cumulus-client-service/runtime-benchmarks",
"cumulus-pallet-aura-ext/runtime-benchmarks",
"cumulus-pallet-teyrchain-system/runtime-benchmarks",
"cumulus-pallet-weight-reclaim/runtime-benchmarks",
"cumulus-primitives-proof-size-hostfunction/runtime-benchmarks",
"first-runtime/runtime-benchmarks",
"frame-benchmarking/runtime-benchmarks",
"frame-executive/runtime-benchmarks",
"frame-metadata-hash-extension/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"frame/runtime-benchmarks",
"kitchensink-runtime/runtime-benchmarks",
"minimal-template-runtime/runtime-benchmarks",
"node-cli/runtime-benchmarks",
"pallet-asset-conversion-tx-payment/runtime-benchmarks",
"pallet-asset-tx-payment/runtime-benchmarks",
"pallet-assets/runtime-benchmarks",
"pallet-aura/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-collective/runtime-benchmarks",
"pallet-contracts/runtime-benchmarks",
"pallet-default-config-example/runtime-benchmarks",
"pallet-democracy/runtime-benchmarks",
"pallet-example-authorization-tx-extension/runtime-benchmarks",
"pallet-example-offchain-worker/runtime-benchmarks",
"pallet-example-single-block-migrations/runtime-benchmarks",
"pallet-examples/runtime-benchmarks",
"pallet-grandpa/runtime-benchmarks",
"pallet-nfts/runtime-benchmarks",
"pallet-preimage/runtime-benchmarks",
"pallet-scheduler/runtime-benchmarks",
"pallet-skip-feeless-payment/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"pallet-uniques/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
"pezkuwi-omni-node-lib/runtime-benchmarks",
"pezkuwi-sdk/runtime-benchmarks",
"sc-chain-spec/runtime-benchmarks",
"sc-cli/runtime-benchmarks",
"sc-client-db/runtime-benchmarks",
"sc-consensus-aura/runtime-benchmarks",
"sc-consensus-babe/runtime-benchmarks",
"sc-consensus-beefy/runtime-benchmarks",
"sc-consensus-grandpa/runtime-benchmarks",
"sc-consensus-manual-seal/runtime-benchmarks",
"sc-consensus-pow/runtime-benchmarks",
"sc-executor/runtime-benchmarks",
"sc-network/runtime-benchmarks",
"sc-rpc-api/runtime-benchmarks",
"sc-rpc/runtime-benchmarks",
"sc-service/runtime-benchmarks",
"solochain-template-runtime/runtime-benchmarks",
"sp-api/runtime-benchmarks",
"sp-genesis-builder/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-keyring/runtime-benchmarks",
"sp-offchain/runtime-benchmarks",
"sp-runtime-interface/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-version/runtime-benchmarks",
"subkey/runtime-benchmarks",
"substrate-wasm-builder/runtime-benchmarks",
"teyrchain-info/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-docs/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm-simulator/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
+2
View File
@@ -0,0 +1,2 @@
<script> mermaid.init({ startOnLoad: true, theme: "dark" }, "pre.language-mermaid > code");</script>
+147
View File
@@ -0,0 +1,147 @@
<script>
function createToC() {
let sidebar = document.querySelector(".sidebar");
let headers = document.querySelectorAll("#main-content h2, #main-content h3, #main-content h4");
console.log(`detected polkadot_sdk_docs: headers: ${headers.length}`);
let toc = document.createElement("div");
toc.classList.add("sidebar-table-of-contents");
toc.appendChild(document.createElement("h2").appendChild(document.createTextNode("Table of Contents")).parentNode);
let modules = document.querySelectorAll("main .item-table a.mod");
// the first two headers are always junk
headers.forEach(header => {
let link = document.createElement("a");
link.href = "#" + header.id;
const headerTextContent = header.textContent.replace("§", "")
link.textContent = headerTextContent;
link.className = header.tagName.toLowerCase();
toc.appendChild(link);
if (header.id == "modules" && headerTextContent == "Modules") {
modules.forEach(module => {
let link = document.createElement("a");
link.href = module.href;
link.textContent = module.textContent;
link.className = "h3";
toc.appendChild(link);
});
}
});
// insert toc as the second child in sidebar
let sidebar_children = sidebar.children;
if (sidebar_children.length > 1) {
sidebar.insertBefore(toc, sidebar_children[1]);
} else {
sidebar.appendChild(toc);
}
}
function hideSidebarElements() {
// Create the 'Expand for More' button
var expandButton = document.createElement('button');
expandButton.innerText = 'Expand More Items';
expandButton.classList.add('expand-button');
// Insert the button at the top of the sidebar or before the '.sidebar-elems'
var sidebarElems = document.querySelector('.sidebar-elems');
sidebarElems.parentNode.insertBefore(expandButton, sidebarElems);
// Initially hide the '.sidebar-elems'
sidebarElems.style.display = 'none';
// Add click event listener to the button
expandButton.addEventListener('click', function () {
// Toggle the display of the '.sidebar-elems'
if (sidebarElems.style.display === 'none') {
sidebarElems.style.display = 'block';
expandButton.innerText = 'Collapse';
} else {
sidebarElems.style.display = 'none';
expandButton.innerText = 'Expand for More';
}
});
}
window.addEventListener("DOMContentLoaded", (event) => {
// if the crate is one that starts with `polkadot_sdk_docs`
let crate_name = document.querySelector("#main-content > div > h1 > a:nth-child(1)");
if (!crate_name.textContent.startsWith("polkadot_sdk_docs")) {
console.log("skipping -- not `polkadot_sdk_docs`");
return;
} else {
// insert class 'sdk-docs' to the body, so it enables the custom css rules.
document.body.classList.add("sdk-docs");
}
createToC();
hideSidebarElements();
console.log("updating page based on being `polkadot_sdk_docs` crate");
});
</script>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<style>
body.sdk-docs {
nav.side-bar {
width: 300px;
}
.sidebar-table-of-contents {
margin-bottom: 1em;
padding: 0.5em;
}
.sidebar-table-of-contents a {
display: block;
margin: 0.2em 0;
}
.sidebar-table-of-contents .h2 {
font-weight: bold;
margin-left: 0;
}
.sidebar-table-of-contents .h3 {
margin-left: 1em;
}
.sidebar-table-of-contents .h4 {
margin-left: 2em;
}
.sidebar h2.location {
display: none;
}
.sidebar-elems {
display: none;
}
/* Center the 'Expand for More' button */
.expand-button {
display: inline-block;
/* Use inline-block for sizing */
margin: 10px auto;
/* Auto margins for horizontal centering */
padding: 5px 10px;
background-color: #007bff;
color: white;
text-align: center;
cursor: pointer;
border: none;
border-radius: 5px;
width: auto;
/* Centering the button within its parent container */
position: relative;
left: 50%;
transform: translateX(-50%);
}
}
</style>
+57
View File
@@ -0,0 +1,57 @@
:root {
--polkadot-pink: #E6007A;
--polkadot-green: #56F39A;
--polkadot-lime: #D3FF33;
--polkadot-cyan: #00B2FF;
--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;
height: 52px;
}
nav.sidebar {
flex: 0 0 250px;
}
}
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;
}
@@ -0,0 +1,27 @@
[package]
name = "pezkuwi-sdk-docs-first-pallet"
description = "A simple pallet created for the pezkuwi-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 }
docify = { workspace = true }
frame = { workspace = true, features = ["runtime"] }
scale-info = { workspace = true }
[features]
default = ["std"]
std = ["codec/std", "frame/std", "scale-info/std"]
runtime-benchmarks = ["frame/runtime-benchmarks"]
+481
View File
@@ -0,0 +1,481 @@
// 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<T>(_);
}
#[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<T>(_);
#[docify::export]
/// Single storage item, of type `Balance`.
#[pallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
/// A mapping from `T::AccountId` to `Balance`
#[pallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[docify::export(impl_pallet)]
#[pallet::call]
impl<T: Config> Pallet<T> {
/// 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 `<T: Config>` remains as `<T>`.
Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
// update total issuance.
TotalIssuance::<T>::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::<T>::get(&sender).ok_or("NonExistentAccount")?;
if sender_balance < amount {
return Err("InsufficientBalance".into());
}
let remainder = sender_balance - amount;
// update sender and dest balances.
Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
Balances::<T>::insert(&sender, remainder);
Ok(())
}
}
#[allow(unused)]
impl<T: Config> Pallet<T> {
#[docify::export]
pub fn transfer_better(
origin: T::RuntimeOrigin,
dest: T::AccountId,
amount: Balance,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let sender_balance = Balances::<T>::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::<T>::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<Runtime>;
// within pallet we just said `<T as frame_system::Config>::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::<Runtime>::insert(who, amount);
TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
}
});
state
}
#[docify::export]
pub(crate) struct StateBuilder {
balances: Vec<(<Runtime as frame_system::Config>::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: <Runtime as frame_system::Config>::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::<Runtime>::insert(who, amount);
TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
}
});
ext.execute_with(test);
// assertions that must always hold
ext.execute_with(|| {
assert_eq!(
Balances::<Runtime>::iter().map(|(_, x)| x).sum::<u128>(),
TotalIssuance::<Runtime>::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::<Runtime>::get(&ALICE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), None);
// mint some funds into Alice's account.
assert_ok!(Pallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
ALICE,
100
));
// re-check the above
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(100));
})
}
#[docify::export]
#[test]
fn state_builder_works() {
StateBuilder::default().build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
});
}
#[docify::export]
#[test]
fn state_builder_add_balance() {
StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(42));
assert_eq!(TotalIssuance::<Runtime>::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::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
})
}
#[docify::export]
#[test]
fn mint_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pallet::<Runtime>::mint_unsafe(RuntimeOrigin::signed(ALICE), BOB, 100));
// then:
assert_eq!(Balances::<Runtime>::get(&BOB), Some(200));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(300));
// given:
assert_ok!(Pallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
CHARLIE,
100
));
// then:
assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(400));
});
}
#[docify::export]
#[test]
fn transfer_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
// when:
assert_ok!(Pallet::<Runtime>::transfer(RuntimeOrigin::signed(BOB), ALICE, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(TotalIssuance::<Runtime>::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::<Runtime>::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10),
"NonExistentAccount"
);
// then nothing has changed.
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::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.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>
+ TryInto<Event<Self>>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[pallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
#[pallet::error]
pub enum Error<T> {
/// 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<T: Config> {
/// A transfer succeeded.
Transferred { from: T::AccountId, to: T::AccountId, amount: Balance },
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[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::<T>::get(&sender).ok_or(Error::<T>::NonExistentAccount)?;
let remainder =
sender_balance.checked_sub(amount).ok_or(Error::<T>::InsufficientBalance)?;
Balances::<T>::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount));
Balances::<T>::insert(&sender, remainder);
Self::deposit_event(Event::<T>::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<Runtime>;
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::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
// now we can also check that an event has been deposited:
assert_eq!(
System::read_events_for_pallet::<Event<Runtime>>(),
vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }]
);
});
}
}
}
@@ -0,0 +1,71 @@
[package]
name = "pezkuwi-sdk-docs-first-runtime"
description = "A simple runtime created for the pezkuwi-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 = ["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 pezkuwi-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",
]
runtime-benchmarks = [
"first-pallet/runtime-benchmarks",
"frame/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",
"pallet-transaction-payment-rpc-runtime-api/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"sp-keyring/runtime-benchmarks",
"substrate-wasm-builder?/runtime-benchmarks",
]
@@ -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();
}
}
@@ -0,0 +1,299 @@
// 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: alloc::borrow::Cow::Borrowed("first-runtime"),
impl_name: alloc::borrow::Cow::Borrowed("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<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
);
pub(super) type Block = frame::runtime::types_common::BlockOf<Runtime, SignedExtra>;
pub(super) type Header = HeaderFor<Runtime>;
pub(super) type RuntimeExecutive = Executive<
Runtime,
Block,
frame_system::ChainContext<Runtime>,
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<<Runtime as pallet_balances::Config>::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<Balances, ()>;
// We specify a fixed length to fee here, which essentially means all transactions charge
// exactly 1 unit of fee.
type LengthToFee = FixedFee<1, <Self as pallet_balances::Config>::Balance>;
type WeightToFee = NoFee<<Self as pallet_balances::Config>::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 frame::deps::frame_support::build_struct_json_patch;
use serde_json::Value;
/// Returns a development genesis config preset.
#[docify::export]
pub fn development_config_genesis() -> Value {
let endowment = <MinimumBalance as Get<Balance>>::get().max(1) * 1000;
build_struct_json_patch!(RuntimeGenesisConfig {
balances: BalancesConfig {
balances: Sr25519Keyring::iter()
.map(|a| (a.to_account_id(), endowment))
.collect::<Vec<_>>(),
},
sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) },
})
}
/// Get the set of the available genesis config presets.
#[docify::export]
pub fn get_preset(id: &PresetId) -> Option<Vec<u8>> {
let patch = match id.as_ref() {
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<PresetId> {
vec![PresetId::from(DEV_RUNTIME_PRESET)]
}
}
impl_runtime_apis! {
impl apis::Core<Block> for Runtime {
fn version() -> RuntimeVersion {
VERSION
}
fn execute_block(block: <Block as frame::traits::Block>::LazyBlock) {
RuntimeExecutive::execute_block(block)
}
fn initialize_block(header: &Header) -> ExtrinsicInclusionMode {
RuntimeExecutive::initialize_block(header)
}
}
impl apis::Metadata<Block> for Runtime {
fn metadata() -> OpaqueMetadata {
OpaqueMetadata::new(Runtime::metadata().into())
}
fn metadata_at_version(version: u32) -> Option<OpaqueMetadata> {
Runtime::metadata_at_version(version)
}
fn metadata_versions() -> Vec<u32> {
Runtime::metadata_versions()
}
}
impl apis::BlockBuilder<Block> for Runtime {
fn apply_extrinsic(extrinsic: ExtrinsicFor<Runtime>) -> ApplyExtrinsicResult {
RuntimeExecutive::apply_extrinsic(extrinsic)
}
fn finalize_block() -> HeaderFor<Runtime> {
RuntimeExecutive::finalize_block()
}
fn inherent_extrinsics(data: InherentData) -> Vec<ExtrinsicFor<Runtime>> {
data.create_extrinsics()
}
fn check_inherents(
block: <Block as frame::traits::Block>::LazyBlock,
data: InherentData,
) -> CheckInherentsResult {
data.check_extrinsics(&block)
}
}
impl apis::TaggedTransactionQueue<Block> for Runtime {
fn validate_transaction(
source: TransactionSource,
tx: ExtrinsicFor<Runtime>,
block_hash: <Runtime as frame_system::Config>::Hash,
) -> TransactionValidity {
RuntimeExecutive::validate_transaction(source, tx, block_hash)
}
}
impl apis::OffchainWorkerApi<Block> for Runtime {
fn offchain_worker(header: &HeaderFor<Runtime>) {
RuntimeExecutive::offchain_worker(header)
}
}
impl apis::SessionKeys<Block> for Runtime {
fn generate_session_keys(_seed: Option<Vec<u8>>) -> Vec<u8> {
Default::default()
}
fn decode_session_keys(
_encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, apis::KeyTypeId)>> {
Default::default()
}
}
impl apis::AccountNonceApi<Block, interface::AccountId, interface::Nonce> for Runtime {
fn account_nonce(account: interface::AccountId) -> interface::Nonce {
System::account_nonce(account)
}
}
impl apis::GenesisBuilder<Block> for Runtime {
fn build_state(config: Vec<u8>) -> GenesisBuilderResult {
build_state::<RuntimeGenesisConfig>(config)
}
fn get_preset(id: &Option<PresetId>) -> Option<Vec<u8>> {
get_preset::<RuntimeGenesisConfig>(id, self::genesis_config_presets::get_preset)
}
fn preset_names() -> Vec<PresetId> {
crate::genesis_config_presets::preset_names()
}
}
impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<
Block,
interface::Balance,
> for Runtime {
fn query_info(uxt: ExtrinsicFor<Runtime>, len: u32) -> RuntimeDispatchInfo<interface::Balance> {
TransactionPayment::query_info(uxt, len)
}
fn query_fee_details(uxt: ExtrinsicFor<Runtime>, len: u32) -> FeeDetails<interface::Balance> {
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 = <Runtime as frame_system::Config>::AccountId;
pub type Nonce = <Runtime as frame_system::Config>::Nonce;
pub type Hash = <Runtime as frame_system::Config>::Hash;
pub type Balance = <Runtime as pallet_balances::Config>::Balance;
pub type MinimumBalance = <Runtime as pallet_balances::Config>::ExistentialDeposit;
}
+14
View File
@@ -0,0 +1,14 @@
//! # External Resources
//!
//! A non-exhaustive, un-opinionated list of external resources about Pezkuwi SDK.
//!
//! Unlike [`crate::guides`], or [`crate::pezkuwi_sdk::templates`] that contain material directly
//! maintained in the `pezkuwi-sdk` repository, the list of resources here are maintained by
//! third-parties, and are therefore subject to more variability. Any further resources may be added
//! by opening a pull request to the `pezkuwi-sdk` repository.
//!
//! - [Pezkuwi NFT Marketplace Tutorial by Pezkuwi Fellow Shawn Tabrizi](https://www.shawntabrizi.com/substrate-collectables-workshop/)
//! - [HEZ Code School](https://pezkuwichain.io/docs/introduction)
//! - [Pezkuwi Developers Github Organization](https://github.com/polkadot-developers/)
//! - [Pezkuwi Blockchain Academy](https://github.com/pezkuwichain/kurdistan_blockchain-akademy)
//! - [Pezkuwi Wiki](https://wiki.network.pezkuwichain.io/)
+254
View File
@@ -0,0 +1,254 @@
//! # Upgrade Teyrchain for Asynchronous Backing Compatibility
//!
//! This guide is relevant for cumulus based teyrchain projects started in 2023 or before, whose
//! backing process is synchronous where parablocks can only be built on the latest Relay Chain
//! block. Async Backing allows collators to build parablocks on older Relay Chain blocks and create
//! pipelines of multiple pending parablocks. This parallel block generation increases efficiency
//! and throughput. For more information on Async backing and its terminology, refer to the document
//! on [the Pezkuwi SDK docs.](https://docs.pezkuwichain.io/sdk/master/polkadot_sdk_docs/guides/async_backing_guide/index.html)
//!
//! > If starting a new teyrchain project, please use an async backing compatible template such as
//! > the
//! > [teyrchain template](https://github.com/pezkuwichain/pezkuwi-sdk/tree/master/templates/teyrchain).
//! The rollout process for Async Backing has three phases. Phases 1 and 2 below put new
//! infrastructure in place. Then we can simply turn on async backing in phase 3.
//!
//! ## Prerequisite
//!
//! The relay chain needs to have async backing enabled so double-check that the relay-chain
//! configuration contains the following three parameters (especially when testing locally e.g. with
//! zombienet):
//!
//! ```json
//! "async_backing_params": {
//! "max_candidate_depth": 3,
//! "allowed_ancestry_len": 2
//! },
//! "scheduling_lookahead": 2
//! ```
//!
//! <div class="warning"><code>scheduling_lookahead</code> must be set to 2, otherwise teyrchain
//! block times will degrade to worse than with sync backing!</div>
//!
//! ## Phase 1 - Update Teyrchain Runtime
//!
//! This phase involves configuring your teyrchains runtime `/runtime/src/lib.rs` to make use of
//! async backing system.
//!
//! 1. Establish and ensure constants for `capacity` and `velocity` are both set to 1 in the
//! runtime.
//! 2. Establish and ensure the constant relay chain slot duration measured in milliseconds equal to
//! `6000` in the runtime.
//! ```rust
//! // Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the
//! // relay chain.
//! pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1;
//! // How many teyrchain blocks are processed by the relay chain per parent. Limits the number of
//! // blocks authored per slot.
//! pub const BLOCK_PROCESSING_VELOCITY: u32 = 1;
//! // Relay chain slot duration, in milliseconds.
//! pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
//! ```
//!
//! 3. Establish constants `MILLISECS_PER_BLOCK` and `SLOT_DURATION` if not already present in the
//! runtime.
//! ```ignore
//! // `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked
//! // up by `pallet_aura` to implement `fn slot_duration()`.
//! //
//! // Change this to adjust the block time.
//! pub const MILLISECS_PER_BLOCK: u64 = 12000;
//! pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
//! ```
//!
//! 4. Configure `cumulus_pallet_teyrchain_system` in the runtime.
//!
//! - Define a `FixedVelocityConsensusHook` using our capacity, velocity, and relay slot duration
//! constants. Use this to set the teyrchain system `ConsensusHook` property.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", ConsensusHook)]
//! ```ignore
//! impl cumulus_pallet_teyrchain_system::Config for Runtime {
//! ..
//! type ConsensusHook = ConsensusHook;
//! ..
//! }
//! ```
//! - Set the teyrchain system property `CheckAssociatedRelayNumber` to
//! `RelayNumberMonotonicallyIncreases`
//! ```ignore
//! impl cumulus_pallet_teyrchain_system::Config for Runtime {
//! ..
//! type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases;
//! ..
//! }
//! ```
//!
//! 5. Configure `pallet_aura` in the runtime.
//!
//! - Set `AllowMultipleBlocksPerSlot` to `false` (don't worry, we will set it to `true` when we
//! activate async backing in phase 3).
//!
//! - Define `pallet_aura::SlotDuration` using our constant `SLOT_DURATION`
//! ```ignore
//! impl pallet_aura::Config for Runtime {
//! ..
//! type AllowMultipleBlocksPerSlot = ConstBool<false>;
//! #[cfg(feature = "experimental")]
//! type SlotDuration = ConstU64<SLOT_DURATION>;
//! ..
//! }
//! ```
//!
//! 6. Update `sp_consensus_aura::AuraApi::slot_duration` in `sp_api::impl_runtime_apis` to match
//! the constant `SLOT_DURATION`
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/apis.rs", impl_slot_duration)]
//!
//! 7. Implement the `AuraUnincludedSegmentApi`, which allows the collator client to query its
//! runtime to determine whether it should author a block.
//!
//! - Add the dependency `cumulus-primitives-aura` to the `runtime/Cargo.toml` file for your
//! runtime
//! ```ignore
//! ..
//! cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false }
//! ..
//! ```
//!
//! - In the same file, add `"cumulus-primitives-aura/std",` to the `std` feature.
//!
//! - Inside the `impl_runtime_apis!` block for your runtime, implement the
//! `cumulus_primitives_aura::AuraUnincludedSegmentApi` as shown below.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/apis.rs", impl_can_build_upon)]
//!
//! **Note:** With a capacity of 1 we have an effective velocity of ½ even when velocity is
//! configured to some larger value. This is because capacity will be filled after a single block is
//! produced and will only be freed up after that block is included on the relay chain, which takes
//! 2 relay blocks to accomplish. Thus with capacity 1 and velocity 1 we get the customary 12 second
//! teyrchain block time.
//!
//! 8. If your `runtime/src/lib.rs` provides a `CheckInherents` type to `register_validate_block`,
//! remove it. `FixedVelocityConsensusHook` makes it unnecessary. The following example shows how
//! `register_validate_block` should look after removing `CheckInherents`.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", register_validate_block)]
//!
//!
//! ## Phase 2 - Update Teyrchain Nodes
//!
//! This phase consists of plugging in the new lookahead collator node.
//!
//! 1. Import `cumulus_primitives_core::ValidationCode` to `node/src/service.rs`.
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", cumulus_primitives)]
//!
//! 2. In `node/src/service.rs`, modify `sc_service::spawn_tasks` to use a clone of `Backend` rather
//! than the original
//! ```ignore
//! sc_service::spawn_tasks(sc_service::SpawnTasksParams {
//! ..
//! backend: backend.clone(),
//! ..
//! })?;
//! ```
//!
//! 3. Add `backend` as a parameter to `start_consensus()` in `node/src/service.rs`
//! ```text
//! fn start_consensus(
//! ..
//! backend: Arc<TeyrchainBackend>,
//! ..
//! ```
//! ```ignore
//! if validator {
//! start_consensus(
//! ..
//! backend.clone(),
//! ..
//! )?;
//! }
//! ```
//!
//! 4. In `node/src/service.rs` import the lookahead collator rather than the basic collator
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", lookahead_collator)]
//!
//! 5. In `start_consensus()` replace the `BasicAuraParams` struct with `AuraParams`
//! - Change the struct type from `BasicAuraParams` to `AuraParams`
//! - In the `para_client` field, pass in a cloned para client rather than the original
//! - Add a `para_backend` parameter after `para_client`, passing in our para backend
//! - Provide a `code_hash_provider` closure like that shown below
//! - Increase `authoring_duration` from 500 milliseconds to 2000
//! ```ignore
//! let params = AuraParams {
//! ..
//! para_client: client.clone(),
//! para_backend: backend.clone(),
//! ..
//! code_hash_provider: move |block_hash| {
//! client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash())
//! },
//! ..
//! authoring_duration: Duration::from_millis(2000),
//! ..
//! };
//! ```
//!
//! **Note:** Set `authoring_duration` to whatever you want, taking your own hardware into account.
//! But if the backer who should be slower than you due to reading from disk, times out at two
//! seconds your candidates will be rejected.
//!
//! 6. In `start_consensus()` replace `basic_aura::run` with `aura::run`
//! ```ignore
//! let fut =
//! aura::run::<Block, sp_consensus_aura::sr25519::AuthorityPair, _, _, _, _, _, _, _, _, _>(
//! params,
//! );
//! task_manager.spawn_essential_handle().spawn("aura", None, fut);
//! ```
//!
//! ## Phase 3 - Activate Async Backing
//!
//! This phase consists of changes to your teyrchains runtime that activate async backing feature.
//!
//! 1. Configure `pallet_aura`, setting `AllowMultipleBlocksPerSlot` to true in
//! `runtime/src/lib.rs`.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/configs/mod.rs", aura_config)]
//!
//! 2. Increase the maximum `UNINCLUDED_SEGMENT_CAPACITY` in `runtime/src/lib.rs`.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", async_backing_params)]
//!
//! 3. Decrease `MILLISECS_PER_BLOCK` to 6000.
//!
//! - Note: For a teyrchain which measures time in terms of its own block number rather than by
//! relay block number it may be preferable to increase velocity. Changing block time may cause
//! complications, requiring additional changes. See the section “Timing by Block Number”.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", block_times)]
//!
//! 4. Update `MAXIMUM_BLOCK_WEIGHT` to reflect the increased time available for block production.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", max_block_weight)]
//!
//! 5. Add a feature flagged alternative for `MinimumPeriod` in `pallet_timestamp`. The type should
//! be `ConstU64<0>` with the feature flag experimental, and `ConstU64<{SLOT_DURATION / 2}>`
//! without.
//! ```ignore
//! impl pallet_timestamp::Config for Runtime {
//! ..
//! #[cfg(feature = "experimental")]
//! type MinimumPeriod = ConstU64<0>;
//! #[cfg(not(feature = "experimental"))]
//! type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>;
//! ..
//! }
//! ```
//!
//! ## Timing by Block Number
//!
//! With asynchronous backing it will be possible for teyrchains to opt for a block time of 6
//! seconds rather than 12 seconds. But modifying block duration isnt so simple for a teyrchain
//! which was measuring time in terms of its own block number. It could result in expected and
//! actual time not matching up, stalling the teyrchain.
//!
//! One strategy to deal with this issue is to instead rely on relay chain block numbers for timing.
//! Relay block number is kept track of by each teyrchain in `pallet-teyrchain-system` with the
//! storage value `LastRelayChainBlockNumber`. This value can be obtained and used wherever timing
//! based on block number is needed.
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
+1
View File
@@ -0,0 +1 @@
//! # Changing Consensus
@@ -0,0 +1 @@
//! # Cumulus Enabled Teyrchain
+35
View File
@@ -0,0 +1,35 @@
# DHT Bootnodes (RFC-8)
The "DHT bootnodes" mechanism, as defined in [RFC-0008: Store teyrchain bootnodes in relay chain
DHT](https://polkadot-fellows.github.io/RFCs/approved/0008-parachain-bootnodes-dht.html)
and implemented in Pezkuwi, enables teyrchain nodes to bootstrap without requiring hardcoded
bootnode addresses in the chainspec.
## How It Works
This mechanism, enabled by default, allows any teyrchain node to serve as a bootnode. In each
epoch, 20 teyrchain nodes are selected as bootnodes based on the proximity of their relay chain
peer IDs to the teyrchain key for that epoch. These selected nodes register themselves in the relay
chain's Kademlia DHT as [_content providers._](
https://github.com/libp2p/specs/tree/master/kad-dht#content-provider-advertisement-and-discovery)
Other nodes can then discover and query them to obtain the multiaddresses of their teyrchain
instances.
## Information for Teyrchain Operators
The DHT bootnode mechanism simplifies teyrchain deployment by removing the need for dedicated
bootnodes and hardcoded addresses in the chainspec. It also reduces the risk of single points
of failure if predefined bootnodes become unreachable.
However, since this feature is relatively new, high-value teyrchains are still advised to include
a set of dedicated bootnodes in the chainspec as a fallback mechanism. Also, the bootnodes
specified via the `--bootnodes` command-line option are always used.
## Command-Line Options
There are two independent CLI options controlling the mechanism:
- `--no-dht-bootnode` prevents a node from acting as a DHT bootnode.
- `--no-dht-bootnode-discovery` disables discovery of other teyrchain nodes via the DHT bootnode
mechanism.
+182
View File
@@ -0,0 +1,182 @@
//! # Enable elastic scaling for a teyrchain
//!
//! <div class="warning">This guide assumes full familiarity with Asynchronous Backing and its
//! terminology, as defined in <a href="https://docs.pezkuwichain.io/sdk/master/polkadot_sdk_docs/guides/async_backing_guide/index.html">the Pezkuwi SDK Docs</a>.
//! </div>
//!
//! ## Quick introduction to Elastic Scaling
//!
//! [Elastic scaling](https://www.parity.io/blog/polkadot-web3-cloud) is a feature that enables teyrchains (rollups) to use multiple cores.
//! Teyrchains can adjust their usage of core resources on the fly to increase TPS and decrease
//! latency.
//!
//! ### When do you need Elastic Scaling?
//!
//! Depending on their use case, applications might have an increased need for the following:
//! - compute (CPU weight)
//! - bandwidth (proof size)
//! - lower latency (block time)
//!
//! ### High throughput (TPS) and lower latency
//!
//! If the main bottleneck is the CPU, then your teyrchain needs to maximize the compute usage of
//! each core while also achieving a lower latency.
//! 3 cores provide the best balance between CPU, bandwidth and latency: up to 6s of execution,
//! 5MB/s of DA bandwidth and fast block time of just 2 seconds.
//!
//! ### High bandwidth
//!
//! Useful for applications that are bottlenecked by bandwidth.
//! By using 6 cores, applications can make use of up to 6s of compute, 10MB/s of bandwidth
//! while also achieving 1 second block times.
//!
//! ### Ultra low latency
//!
//! When latency is the primary requirement, Elastic scaling is currently the only solution. The
//! caveat is the efficiency of core time usage decreases as more cores are used.
//!
//! For example, using 12 cores enables fast transaction confirmations with 500ms blocks and up to
//! 20 MB/s of DA bandwidth.
//!
//! ## Dependencies
//!
//! Prerequisites: Pezkuwi-SDK `2509` or newer.
//!
//! To ensure the security and reliability of your chain when using this feature you need the
//! following:
//! - An omni-node based collator. This has already become the default choice for collators.
//! - UMP signal support.
//! [RFC103](https://github.com/polkadot-fellows/RFCs/blob/main/text/0103-introduce-core-index-commitment.md).
//! This is mandatory protection against PoV replay attacks.
//! - Enabling the relay parent offset feature. This is required to ensure the teyrchain block times
//! and transaction in-block confidence are not negatively affected by relay chain forks. Read
//! [`crate::guides::handling_teyrchain_forks`] for more information.
//! - Block production configuration adjustments.
//!
//! ### Upgrade to Pezkuwi Omni node
//!
//! Your collators need to run `pezkuwi-teyrchain` or `pezkuwi-omni-node` with the `--authoring
//! slot-based` CLI argument.
//! To avoid potential issues and get best performance it is recommeneded to always run the
//! latest release on all of the collators.
//!
//! Further information about omni-node and how to upgrade is available:
//! - [high level docs](https://docs.pezkuwichain.io/develop/toolkit/parachains/polkadot-omni-node/)
//! - [`crate::reference_docs::omni_node`]
//!
//! ### UMP signals
//!
//! UMP signals are now enabled by default in the `teyrchain-system` pallet and are used for
//! elastic scaling. You can find more technical details about UMP signals and their usage for
//! elastic scaling
//! [here](https://github.com/polkadot-fellows/RFCs/blob/main/text/0103-introduce-core-index-commitment.md).
//!
//! ### Enable the relay parent offset feature
//!
//! It is recommended to use an offset of `1`, which is sufficient to eliminate any issues
//! with relay chain forks.
//!
//! Configure the relay parent offset like this:
//! ```ignore
//! /// Build with an offset of 1 behind the relay chain best block.
//! const RELAY_PARENT_OFFSET: u32 = 1;
//!
//! impl cumulus_pallet_teyrchain_system::Config for Runtime {
//! // ...
//! type RelayParentOffset = ConstU32<RELAY_PARENT_OFFSET>;
//! }
//! ```
//!
//! Implement the runtime API to retrieve the offset on the client side.
//! ```ignore
//! impl cumulus_primitives_core::RelayParentOffsetApi<Block> for Runtime {
//! fn relay_parent_offset() -> u32 {
//! RELAY_PARENT_OFFSET
//! }
//! }
//! ```
//!
//! ### Block production configuration
//!
//! This configuration directly controls the minimum block time and maximum number of cores
//! the teyrchain can use.
//!
//! Example configuration for a 3 core teyrchain:
//! ```ignore
//! /// The upper limit of how many teyrchain blocks are processed by the relay chain per
//! /// parent. Limits the number of blocks authored per slot. This determines the minimum
//! /// block time of the teyrchain:
//! /// `RELAY_CHAIN_SLOT_DURATION_MILLIS/BLOCK_PROCESSING_VELOCITY`
//! const BLOCK_PROCESSING_VELOCITY: u32 = 3;
//!
//! /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included
//! /// into the relay chain.
//! const UNINCLUDED_SEGMENT_CAPACITY: u32 = (2 + RELAY_PARENT_OFFSET) *
//! BLOCK_PROCESSING_VELOCITY + 1;
//!
//! /// Relay chain slot duration, in milliseconds.
//! const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
//!
//! type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook<
//! Runtime,
//! RELAY_CHAIN_SLOT_DURATION_MILLIS,
//! BLOCK_PROCESSING_VELOCITY,
//! UNINCLUDED_SEGMENT_CAPACITY,
//! >;
//!
//! ```
//!
//! ### Teyrchain Slot Duration
//!
//! A common source of confusion is the correct configuration of the `SlotDuration` that is passed
//! to `pallet-aura`.
//! ```ignore
//! impl pallet_aura::Config for Runtime {
//! // ...
//! type SlotDuration = ConstU64<SLOT_DURATION>;
//! }
//! ```
//!
//! The slot duration determines the length of each author's turn and is decoupled from the block
//! production interval. During their slot, authors are allowed to produce multiple blocks. **The
//! slot duration is required to be at least 6s (same as on the relay chain).**
//!
//! **Configuration recommendations:**
//! - For new teyrchains starting from genesis: use a slot duration of 24 seconds
//! - For existing live teyrchains: leave the slot duration unchanged
//!
//!
//! ## Current limitations
//!
//! ### Maximum execution time per relay chain block.
//!
//! Since teyrchain block authoring is sequential, the next block can only be built after
//! the previous one has been imported.
//! At present, a core allows up to 2 seconds of execution per relay chain block.
//!
//! If we assume a 6s teyrchain slot, and each block takes the full 2 seconds to execute,
//! the teyrchain will not be able to fully utilize the compute resources of all 3 cores.
//!
//! If the collator hardware is faster, it can author and import full blocks more quickly,
//! making it possible to utilize even more than 3 cores efficiently.
//!
//! #### Why?
//!
//! Within a 6-second teyrchain slot, collators can author multiple teyrchain blocks.
//! Before building the first block in a slot, the new block author must import the last
//! block produced by the previous author.
//! If the import of the last block is not completed before the next relay chain slot starts,
//! the new author will build on its parent (assuming it was imported). This will create a fork
//! which degrades the teyrchain block confidence and block times.
//!
//! This means that, on reference hardware, a teyrchain with a slot time of 6s can
//! effectively utilize up to 4 seconds of execution per relay chain block, because it needs to
//! ensure the next block author has enough time to import the last block.
//! Hardware with higher single-core performance can enable a teyrchain to fully utilize more
//! cores.
//!
//! ### Fixed factor scaling.
//!
//! For true elasticity, a teyrchain needs to acquire more cores when needed in an automated
//! manner. This functionality is not yet available in the SDK, thus acquiring additional
//! on-demand or bulk cores has to be managed externally.
+88
View File
@@ -0,0 +1,88 @@
//! # Enable metadata hash verification
//!
//! This guide will teach you how to enable the metadata hash verification in your runtime.
//!
//! ## What is metadata hash verification?
//!
//! Each FRAME based runtime exposes metadata about itself. This metadata is used by consumers of
//! the runtime to interpret the state, to construct transactions etc. Part of this metadata are the
//! type information. These type information can be used to e.g. decode storage entries or to decode
//! a transaction. So, the metadata is quite useful for wallets to interact with a FRAME based
//! chain. Online wallets can fetch the metadata directly from any node of the chain they are
//! connected to, but offline wallets can not do this. So, for the offline wallet to have access to
//! the metadata it needs to be transferred and stored on the device. The problem is that the
//! metadata has a size of several hundreds of kilobytes, which takes quite a while to transfer to
//! these offline wallets and the internal storage of these devices is also not big enough to store
//! the metadata for one or more networks. The next problem is that the offline wallet/user can not
//! trust the metadata to be correct. It is very important for the metadata to be correct or
//! otherwise an attacker could change them in a way that the offline wallet decodes a transaction
//! in a different way than what it will be decoded to on chain. So, the user may sign an incorrect
//! transaction leading to unexpected behavior.
//!
//! The metadata hash verification circumvents the issues of the huge metadata and the need to trust
//! some metadata blob to be correct. To generate a hash for the metadata, the metadata is chunked,
//! these chunks are put into a merkle tree and then the root of this merkle tree is the "metadata
//! hash". For a more technical explanation on how it works, see
//! [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html). At compile
//! time the metadata hash is generated and "baked" into the runtime. This makes it extremely cheap
//! for the runtime to verify on chain that the metadata hash is correct. By having the runtime
//! verify the hash on chain, the user also doesn't need to trust the offchain metadata. If the
//! metadata hash doesn't match the on chain metadata hash the transaction will be rejected. The
//! metadata hash itself is added to the data of the transaction that is signed, this means the
//! actual hash does not appear in the transaction. On chain the same procedure is repeated with the
//! metadata hash that is known by the runtime and if the metadata hash doesn't match the signature
//! verification will fail. As the metadata hash is actually the root of a merkle tree, the offline
//! wallet can get proofs of individual types to decode a transaction. This means that the offline
//! wallet does not require the entire metadata to be present on the device.
//!
//! ## Integrating metadata hash verification into your runtime
//!
//! The integration of the metadata hash verification is split into two parts, first the actual
//! integration into the runtime and secondly the enabling of the metadata hash generation at
//! compile time.
//!
//! ### Runtime integration
//!
//! From the runtime side only the
//! [`CheckMetadataHash`](frame_metadata_hash_extension::CheckMetadataHash) needs to be added to the
//! list of signed extension:
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", template_signed_extra)]
//!
//! > **Note:**
//! >
//! > Adding the signed extension changes the encoding of the transaction and adds one extra byte
//! > per transaction!
//!
//! This signed extension will make sure to decode the requested `mode` and will add the metadata
//! hash to the signed data depending on the requested `mode`. The `mode` gives the user/wallet
//! control over deciding if the metadata hash should be verified or not. The metadata hash itself
//! is drawn from the `RUNTIME_METADATA_HASH` environment variable. If the environment variable is
//! not set, any transaction that requires the metadata hash is rejected with the error
//! `CannotLookup`. This is a security measurement to prevent including invalid transactions.
//!
//! <div class="warning">
//!
//! The extension does not work with the native runtime, because the
//! `RUNTIME_METADATA_HASH` environment variable is not set when building the
//! `frame-metadata-hash-extension` crate.
//!
//! </div>
//!
//! ### Enable metadata hash generation
//!
//! The metadata hash generation needs to be enabled when building the wasm binary. The
//! `substrate-wasm-builder` supports this out of the box:
#![doc = docify::embed!("../../templates/teyrchain/runtime/build.rs", template_enable_metadata_hash)]
//!
//! > **Note:**
//! >
//! > The `metadata-hash` feature needs to be enabled for the `substrate-wasm-builder` to enable the
//! > code for being able to generate the metadata hash. It is also recommended to put the metadata
//! > hash generation behind a feature in the runtime as shown above. The reason behind is that it
//! > adds a lot of code which increases the compile time and the generation itself also increases
//! > the compile time. Thus, it is recommended to enable the feature only when the metadata hash is
//! > required (e.g. for an on-chain build).
//!
//! The two parameters to `enable_metadata_hash` are the token symbol and the number of decimals of
//! the primary token of the chain. These information are included for the wallets to show token
//! related operations in a more user friendly way.
+88
View File
@@ -0,0 +1,88 @@
//! # Enable storage weight reclaiming
//!
//! This guide will teach you how to enable storage weight reclaiming for a teyrchain. The
//! explanations in this guide assume a project structure similar to the one detailed in
//! the [substrate documentation](crate::pezkuwi_sdk::substrate#anatomy-of-a-binary-crate). Full
//! technical details are available in the original [pull request](https://github.com/paritytech/polkadot-sdk/pull/3002).
//!
//! # What is PoV reclaim?
//! When a teyrchain submits a block to a relay chain like Pezkuwi or Kusama, it sends the block
//! itself and a storage proof. Together they form the Proof-of-Validity (PoV). The PoV allows the
//! relay chain to validate the teyrchain block by re-executing it. Relay chain
//! validators distribute this PoV among themselves over the network. This distribution is costly
//! and limits the size of the storage proof. The storage weight dimension of FRAME weights reflects
//! this cost and limits the size of the storage proof. However, the storage weight determined
//! during [benchmarking](crate::reference_docs::frame_benchmarking_weight) represents the worst
//! case. In reality, runtime operations often consume less space in the storage proof. PoV reclaim
//! offers a mechanism to reclaim the difference between the benchmarked worst-case and the real
//! proof-size consumption.
//!
//!
//! # How to enable PoV reclaim
//! ## 1. Add the host function to your node
//!
//! To reclaim excess storage weight, a teyrchain runtime needs the
//! ability to fetch the size of the storage proof from the node. The reclaim
//! mechanism uses the
//! [`storage_proof_size`](cumulus_primitives_proof_size_hostfunction::storage_proof_size)
//! host function for this purpose. For convenience, cumulus provides
//! [`TeyrchainHostFunctions`](cumulus_client_service::TeyrchainHostFunctions), a set of
//! host functions typically used by cumulus-based teyrchains. In the binary crate of your
//! teyrchain, find the instantiation of the [`WasmExecutor`](sc_executor::WasmExecutor) and set the
//! correct generic type.
//!
//! This example from the teyrchain-template shows a type definition that includes the correct
//! host functions.
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", wasm_executor)]
//!
//! > **Note:**
//! >
//! > If you see error `runtime requires function imports which are not present on the host:
//! > 'env:ext_storage_proof_size_storage_proof_size_version_1'`, it is likely
//! > that this step in the guide was not set up correctly.
//!
//! ## 2. Enable storage proof recording during import
//!
//! The reclaim mechanism reads the size of the currently recorded storage proof multiple times
//! during block authoring and block import. Proof recording during authoring is already enabled on
//! teyrchains. You must also ensure that storage proof recording is enabled during block import.
//! Find where your node builds the fundamental substrate components by calling
//! [`new_full_parts`](sc_service::new_full_parts). Replace this
//! with [`new_full_parts_record_import`](sc_service::new_full_parts_record_import) and
//! pass `true` as the last parameter to enable import recording.
#![doc = docify::embed!("../../templates/teyrchain/node/src/service.rs", component_instantiation)]
//!
//! > **Note:**
//! >
//! > If you see error `Storage root must match that calculated.` during block import, it is likely
//! > that this step in the guide was not
//! > set up correctly.
//!
//! ## 3. Add the TransactionExtension to your runtime
//!
//! In your runtime, you will find a list of TransactionExtensions.
//! To enable the reclaiming,
//! set [`StorageWeightReclaim`](cumulus_pallet_weight_reclaim::StorageWeightReclaim)
//! as a warpper of that list.
//! It is necessary that this extension wraps all the other transaction extensions in order to catch
//! the whole PoV size of the transactions.
//! The extension will check the size of the storage proof before and after an extrinsic execution.
//! It reclaims the difference between the calculated size and the benchmarked size.
#![doc = docify::embed!("../../templates/teyrchain/runtime/src/lib.rs", template_signed_extra)]
//!
//! ## Optional: Verify that reclaim works
//!
//! Start your node with the log target `runtime::storage_reclaim` set to `trace` to enable full
//! logging for `StorageWeightReclaim`. The following log is an example from a local testnet. To
//! trigger the log, execute any extrinsic on the network.
//!
//! ```ignore
//! ...
//! 2024-04-22 17:31:48.014 TRACE runtime::storage_reclaim: [ferdie] Reclaiming storage weight. benchmarked: 3593, consumed: 265 unspent: 0
//! ...
//! ```
//!
//! In the above example we see a benchmarked size of 3593 bytes, while the extrinsic only consumed
//! 265 bytes of proof size. This results in 3328 bytes of reclaim.
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
@@ -0,0 +1,90 @@
//! # Teyrchain forks
//!
//! In this guide, we will examine how AURA-based teyrchains handle forks. AURA (Authority Round) is
//! a consensus mechanism where block authors rotate at fixed time intervals. Each author gets a
//! predetermined time slice during which they are allowed to author a block. On its own, this
//! mechanism is fork-free.
//!
//! However, since the relay chain provides security and serves as the source of truth for
//! teyrchains, the teyrchain is dependent on it. This relationship can introduce complexities that
//! lead to forking scenarios.
//!
//! ## Background
//! Each teyrchain block has a relay parent, which is a relay chain block that provides context to
//! our teyrchain block. The constraints the relay chain imposes on our teyrchain can cause forks
//! under certain conditions. With asynchronous-backing enabled chains, the node side is building
//! blocks on all relay chain forks. This means that no matter which fork of the relay chain
//! ultimately progressed, the teyrchain would have a block ready for that fork. The situation
//! changes when teyrchains want to produce blocks at a faster cadence. In a scenario where a
//! teyrchain might author on 3 cores with elastic scaling, it is not possible to author on all
//! relay chain forks. The time constraints do not allow it. Building on two forks would result in 6
//! blocks. The authoring of these blocks would consume more time than we have available before the
//! next relay chain block arrives. This limitation requires a more fork-resistant approach to
//! block-building.
//!
//! ## Impact of Forks
//! When a relay chain fork occurs and the teyrchain builds on a fork that will not be extended in
//! the future, the blocks built on that fork are lost and need to be rebuilt. This increases
//! latency and reduces throughput, affecting the overall performance of the teyrchain.
//!
//! # Building on Older Pelay Parents
//! Cumulus offers a way to mitigate the occurence of forks. Instead of picking a block at the tip
//! of the relay chain to build blocks, the node side can pick a relay chain block that is older. By
//! building on 12s old relay chain blocks, forks will already have settled and the teyrchain can
//! build fork-free.
//!
//! ```text
//! Without offset:
//! Relay Chain: A --- B --- C --- D --- E
//! \
//! --- D' --- E'
//! Teyrchain: X --- Y --- ? (builds on both D and D', wasting resources)
//!
//! With offset (2 blocks):
//! Relay Chain: A --- B --- C --- D --- E
//! \
//! --- D' --- E'
//! Teyrchain: X(A) - Y (B) - Z (on C, fork already resolved)
//! ```
//! **Note:** It is possible that relay chain forks extend over more than 1-2 blocks. However, it is
//! unlikely.
//! ## Tradeoffs
//! Fork-free teyrchains come with a few tradeoffs:
//! - The latency of incoming XCM messages will be delayed by `N * 6s`, where `N` is the number of
//! relay chain blocks we want to offset by. For example, by building 2 relay chain blocks behind
//! the tip, the XCM latency will be increased by 12 seconds.
//! - The available PoV space will be slightly reduced. Assuming a 10mb PoV, teyrchains need to be
//! ready to sacrifice around 0.5% of PoV space.
//!
//! ## Enabling Guide
//! The decision whether the teyrchain should build on older relay parents is embedded into the
//! runtime. After the changes are implemented, the runtime will enforce that no author can build
//! with an offset smaller than the desired offset. If you wish to keep your current teyrchain
//! behaviour and do not want aforementioned tradeoffs, set the offset to 0.
//!
//! **Note:** The APIs mentioned here are available in `pezkuwi-sdk` versions after `stable-2506`.
//!
//! 1. Define the relay parent offset your teyrchain should respect in the runtime.
//! ```ignore
//! const RELAY_PARENT_OFFSET = 2;
//! ```
//! 2. Pass this constant to the `teyrchain-system` pallet.
//!
//! ```ignore
//! impl cumulus_pallet_teyrchain_system::Config for Runtime {
//! // Other config items here
//! ...
//! type RelayParentOffset = ConstU32<RELAY_PARENT_OFFSET>;
//! }
//! ```
//! 3. Implement the `RelayParentOffsetApi` runtime API for your runtime.
//!
//! ```ignore
//! impl cumulus_primitives_core::RelayParentOffsetApi<Block> for Runtime {
//! fn relay_parent_offset() -> u32 {
//! RELAY_PARENT_OFFSET
//! }
//! }
//! ```
//! 4. Increase the `UNINCLUDED_SEGMENT_CAPICITY` for your runtime. It needs to be increased by
//! `RELAY_PARENT_OFFSET * BLOCK_PROCESSING_VELOCITY`.
+50
View File
@@ -0,0 +1,50 @@
//! # Pezkuwi SDK Docs Guides
//!
//! This crate contains a collection of guides that are foundational to the developers of
//! Pezkuwi SDK. They are common user-journeys that are traversed in the Pezkuwi ecosystem.
//!
//! 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 Pezkuwi-SDK-based blockchain!
//!
//! Once done, feel free to step up into one of our templates: [`crate::pezkuwi_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.
/// Write your first simple pallet, learning the most most basic features of FRAME along the way.
pub mod your_first_pallet;
/// Write your first real [runtime](`crate::reference_docs::wasm_meta_protocol`),
/// compiling it to [WASM](crate::pezkuwi_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.
pub mod your_first_node;
/// How to enhance a given runtime and node to be cumulus-enabled, run it as a teyrchain
/// and connect it to a relay-chain.
// pub mod your_first_teyrchain;
/// How to enable storage weight reclaiming in a teyrchain node and runtime.
pub mod enable_pov_reclaim;
/// How to enable Async Backing on teyrchain projects that started in 2023 or before.
pub mod async_backing_guide;
/// How to enable metadata hash verification in the runtime.
pub mod enable_metadata_hash;
/// How to enable elastic scaling on a teyrchain.
pub mod enable_elastic_scaling;
/// How to parameterize teyrchain forking in relation to relay chain forking.
pub mod handling_teyrchain_forks;
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
//! # XCM Enabled Teyrchain
+342
View File
@@ -0,0 +1,342 @@
//! # 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`]).
//! `pezkuwi-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 `pezkuwi-omni-node` can either be downloaded from the latest [Release](https://github.com/pezkuwichain/pezkuwi-sdk/releases/) of `pezkuwi-sdk`,
//! or installed using `cargo`:
//!
//! ```text
//! cargo install pezkuwi-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 `pezkuwi-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_runtime.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 `pezkuwi-omni-node`, which is tailored for
//! running teyrchains. 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 <path-to-output> \
//! create \
//! --relay-chain dontcare \
//! --runtime pezkuwi_sdk_docs_first_runtime.wasm \
//! named-preset development
//! ```
//!
//! Equivalent code in tests:
#![doc = docify::embed!("./src/guides/your_first_node.rs", csb)]
//!
//!
//! ### Running `pezkuwi-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
//! pezkuwi-omni-node \
//! --tmp \
//! --dev-block-time 1000 \
//! --chain <chain_spec_file>.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`]: (pezkuwi_omni_node_lib::cli::Cli::dev_block_time)
#[cfg(test)]
mod tests {
use assert_cmd::assert::OutputAssertExt;
use cmd_lib::*;
use rand::Rng;
use sc_chain_spec::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET};
use sp_genesis_builder::PresetId;
use std::{
io::{BufRead, BufReader},
path::PathBuf,
process::{ChildStderr, Command, Stdio},
time::Duration,
};
const PARA_RUNTIME: &'static str = "teyrchain-template-runtime";
const CHAIN_SPEC_BUILDER: &'static str = "chain-spec-builder";
const OMNI_NODE: &'static str = "pezkuwi-omni-node";
fn cargo() -> Command {
Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()))
}
fn get_target_directory() -> Option<PathBuf> {
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<PathBuf> {
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<PathBuf> {
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 teyrchain-template-runtime...");
Command::new("cargo")
.arg("build")
.arg("--release")
.arg("-p")
.arg(PARA_RUNTIME)
.assert()
.success();
}
assert!(find_wasm(PARA_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 pezkuwi-omni-node...");
Command::new("cargo")
.arg("build")
.arg("--release")
.arg("-p")
.arg("pezkuwi-omni-node")
.assert()
.success();
}
}
async fn imported_block_found(stderr: ChildStderr, block: u64, timeout: u64) -> bool {
tokio::time::timeout(Duration::from_secs(timeout), async {
let want = format!("Imported #{}", block);
let reader = BufReader::new(stderr);
let mut found_block = false;
for line in reader.lines() {
if line.unwrap().contains(&want) {
found_block = true;
break;
}
}
found_block
})
.await
.unwrap()
}
async fn test_runtime_preset(
runtime: &'static str,
block_time: u64,
maybe_preset: Option<PresetId>,
) {
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(["--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 mut child = Command::new(omni_node)
.arg("--tmp")
.args(["--chain", chain_spec_file.to_str().unwrap()])
.args(["--dev-block-time", block_time.to_string().as_str()])
.stderr(Stdio::piped())
.spawn()
.unwrap();
// Take stderr and parse it with timeout.
let stderr = child.stderr.take().unwrap();
let expected_blocks = (10_000 / block_time).saturating_div(2);
assert!(expected_blocks > 0, "test configuration is bad, should give it more time");
assert_eq!(imported_block_found(stderr, expected_blocks, 100).await, true);
std::fs::remove_file(chain_spec_file).unwrap();
child.kill().unwrap();
}
// Sets up omni-node to run a text exercise based on a chain spec.
async fn omni_node_test_setup(chain_spec_path: PathBuf) {
maybe_build_omni_node();
let omni_node = find_release_binary(OMNI_NODE).unwrap();
let mut child = Command::new(omni_node)
.arg("--dev")
.args(["--chain", chain_spec_path.to_str().unwrap()])
.stderr(Stdio::piped())
.spawn()
.unwrap();
let stderr = child.stderr.take().unwrap();
assert_eq!(imported_block_found(stderr, 7, 100).await, true);
child.kill().unwrap();
}
#[tokio::test]
async fn works_with_different_block_times() {
test_runtime_preset(PARA_RUNTIME, 100, Some(DEV_RUNTIME_PRESET.into())).await;
test_runtime_preset(PARA_RUNTIME, 3000, Some(DEV_RUNTIME_PRESET.into())).await;
// we need this snippet just for docs
#[docify::export_content(csb)]
fn build_teyrchain_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";
let runtime_str = runtime_path.to_str().unwrap();
run_cmd!(
$chain_spec_builder -c $output create --relay-chain dontcare -r $runtime_str named-preset development
).expect("Failed to run command");
std::fs::remove_file(output).unwrap();
}
build_teyrchain_spec_works();
}
#[tokio::test]
async fn teyrchain_runtime_works() {
// TODO: None doesn't work. But maybe it should? it would be misleading as many users might
// use it.
for preset in [Some(DEV_RUNTIME_PRESET.into()), Some(LOCAL_TESTNET_RUNTIME_PRESET.into())] {
test_runtime_preset(PARA_RUNTIME, 1000, preset).await;
}
}
#[tokio::test]
async fn omni_node_dev_mode_works() {
//Omni Node in dev mode works with teyrchain's template `dev_chain_spec`
let dev_chain_spec = std::env::current_dir()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.join("templates")
.join("teyrchain")
.join("dev_chain_spec.json");
omni_node_test_setup(dev_chain_spec).await;
}
#[tokio::test]
// This is a regresion test so that we still remain compatible with runtimes that use
// `para-id` in chain specs, instead of implementing the
// `cumulus_primitives_core::GetTeyrchainInfo`.
async fn omni_node_dev_mode_works_without_getteyrchaininfo() {
let dev_chain_spec = std::env::current_dir()
.unwrap()
.join("src/guides/teyrchain_without_getteyrchaininfo.json");
omni_node_test_setup(dev_chain_spec).await;
}
}
+789
View File
@@ -0,0 +1,789 @@
//! # Currency Pallet
//!
//! By the end of this guide, you will have written a small FRAME pallet (see
//! [`crate::pezkuwi_sdk::frame_runtime`]) that is capable of handling a simple crypto-currency.
//! This pallet will:
//!
//! 1. Allow anyone to mint new tokens into accounts (which is obviously not a great idea for a real
//! system).
//! 2. Allow any user that owns tokens to transfer them to others.
//! 3. Track the total issuance of all tokens at all times.
//!
//! > This guide will build a currency pallet from scratch using only the lowest primitives of
//! > FRAME, and is mainly intended for education, not *applicability*. For example, almost all
//! > FRAME-based runtimes use various techniques to re-use a currency pallet instead of writing
//! > one. Further advanced FRAME related topics are discussed in [`crate::reference_docs`].
//!
//! ## Writing Your First Pallet
//!
//! To get started, clone one of the templates mentioned in [`crate::pezkuwi_sdk::templates`]. We
//! recommend using the `pezkuwi-sdk-minimal-template`. You might need to change small parts of
//! this guide, namely the crate/package names, based on which template you use.
//!
//! > Be aware that you can read the entire source code backing this tutorial by clicking on the
//! > `source` button at the top right of the page.
//!
//! You should have studied the following modules as a prelude to this guide:
//!
//! - [`crate::reference_docs::blockchain_state_machines`]
//! - [`crate::reference_docs::trait_based_programming`]
//! - [`crate::pezkuwi_sdk::frame_runtime`]
//!
//! ## Topics Covered
//!
//! The following FRAME topics are covered in this guide:
//!
//! - [`pallet::storage`]
//! - [`pallet::call`]
//! - [`pallet::event`]
//! - [`pallet::error`]
//! - Basics of testing a pallet
//! - [Constructing a runtime](frame::runtime::prelude::construct_runtime)
//!
//! ### Shell Pallet
//!
//! Consider the following as a "shell pallet". We continue building the rest of this pallet based
//! on this template.
//!
//! [`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!("./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`.
//!
//! ### Storage
//!
//! First, we will need to create two onchain storage declarations.
//!
//! One should be a mapping from account-ids to a balance type, and one value that is the total
//! issuance.
//!
//! > 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!("./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!("./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!("./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:
//!
//! - Where do `T::AccountId` and `T::RuntimeOrigin` come from? These are both defined in
//! [`frame::prelude::frame_system::Config`], therefore we can access them in `T`.
//! - What is `ensure_signed`, and what does it do with the aforementioned `T::RuntimeOrigin`? This
//! is outside the scope of this guide, and you can learn more about it in the origin reference
//! document ([`crate::reference_docs::frame_origin`]). For now, you should only know the
//! signature of the function: it takes a generic `T::RuntimeOrigin` and returns a
//! `Result<T::AccountId, _>`. So by the end of this function call, we know that this dispatchable
//! was signed by `sender`.
#![doc = docify::embed!("../../substrate/frame/system/src/lib.rs", ensure_signed)]
//!
//! - Where does `mutate`, `get` and `insert` and other storage APIs come from? All of them are
//! explained in the corresponding `type`, for example, for `Balances::<T>::insert`, you can look
//! into [`frame::prelude::StorageMap::insert`].
//!
//! - The return type of all dispatchable functions is [`frame::prelude::DispatchResult`]:
#![doc = docify::embed!("../../substrate/frame/support/src/dispatch.rs", DispatchResult)]
//!
//! Which is more or less a normal Rust `Result`, with a custom [`frame::prelude::DispatchError`] as
//! the `Err` variant. We won't cover this error in detail here, but importantly you should know
//! that there is an `impl From<&'static string> for DispatchError` provided (see
//! [here](`frame::prelude::DispatchError#impl-From<%26str>-for-DispatchError`)). Therefore,
//! we can use basic string literals as our error type and `.into()` them into `DispatchError`.
//!
//! - Why are all `get` and `mutate` functions returning an `Option`? This is the default behavior
//! of FRAME storage APIs. You can learn more about how to override this by looking into
//! [`pallet::storage`], and [`frame::prelude::ValueQuery`]/[`frame::prelude::OptionQuery`]
//!
//! ### Improving Errors
//!
//! 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!("./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!("./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!
//!
//! ### Your First (Test) Runtime
//!
//! The typical testing code of a pallet lives in a module that imports some preludes useful for
//! testing, similar to:
//!
//! ```
//! pub mod pallet {
//! // snip -- actually pallet code.
//! }
//!
//! #[cfg(test)]
//! mod tests {
//! // bring in the testing prelude of frame
//! use frame::testing_prelude::*;
//! // bring in all pallet items
//! use super::pallet::*;
//!
//! // snip -- rest of the testing code.
//! }
//! ```
//!
//! Next, we create a "test runtime" in order to test our pallet. Recall from
//! [`crate::pezkuwi_sdk::frame_runtime`] that a runtime is a collection of pallets, expressed
//! 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!("./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.
//!
//! Recall that within our pallet, (almost) all blocks of code are generic over `<T: Config>`. And,
//! because `trait Config: frame_system::Config`, we can get access to all items in `Config` (or
//! `frame_system::Config`) using `T::NameOfItem`. This is all within the boundaries of how
//! Rust traits and generics work. If unfamiliar with this pattern, read
//! [`crate::reference_docs::trait_based_programming`] before going further.
//!
//! Crucially, a typical FRAME runtime contains a `struct Runtime`. The main role of this `struct`
//! is to implement the `trait Config` of all pallets. That is, anywhere within your pallet code
//! where you see `<T: Config>` (read: *"some type `T` that implements `Config`"*), in the runtime,
//! it can be replaced with `<Runtime>`, because `Runtime` implements `Config` of all pallets, as we
//! see above.
//!
//! Another way to think about this is that within a pallet, a lot of types are "unknown" and, we
//! only know that they will be provided at some later point. For example, when you write
//! `T::AccountId` (which is short for `<T as frame_system::Config>::AccountId`) in your pallet,
//! you are in fact saying "*Some type `AccountId` that will be known later*". That "later" is in
//! fact when you specify these types when you implement all `Config` traits for `Runtime`.
//!
//! As you see above, `frame_system::Config` is setting the `AccountId` to `u64`. Of course, a real
//! runtime will not use this type, and instead reside to a proper type like a 32-byte standard
//! public key. This is a HUGE benefit that FRAME developers can tap into: through the framework
//! being so generic, different types can always be customized to simple things when needed.
//!
//! > Imagine how hard it would have been if all tests had to use a real 32-byte account id, as
//! > opposed to just a u64 number 🙈.
//!
//! ### Your First Test
//!
//! The above is all you need to execute the dispatchables of your pallet. The last thing you need
//! 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!("./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.
//!
//! As noted above, the `T::AccountId` is now `u64`. Moreover, `Runtime` is replacing `<T: Config>`.
//! This is why for example you see `Balances::<Runtime>::get(..)`. Finally, notice that the
//! dispatchables are simply functions that can be called on top of the `Pallet` struct.
//!
//! Congratulations! You have written your first pallet and tested it! Next, we learn a few optional
//! steps to improve our pallet.
//!
//! ## Improving the Currency Pallet
//!
//! ### Better Test Setup
//!
//! Idiomatic FRAME pallets often use Builder pattern to define their initial state.
//!
//! > The Pezkuwi Blockchain Academy's Rust entrance exam has a
//! > [section](https://github.com/pezkuwichain/kurdistan_blockchain-akademy/blob/main/src/m_builder.rs)
//! > on this that you can use to learn the Builder Pattern.
//!
//! Let's see how we can implement a better test setup using this pattern. First, we define a
//! `struct 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!("./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!("./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:
//!
//! 1. It would consume `self` to produce our `TestState` based on the properties that we attached
//! to `self`.
//! 2. It would execute any test function that we pass in as closure.
//! 3. A nifty trick, this allows our test setup to have some code that is executed both before and
//! 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!("./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!("./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!("./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!("./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.
//!
//! ### Event and Error
//!
//! Our pallet is mainly missing two parts that are common in most FRAME pallets: Events, and
//! Errors. First, let's understand what each is.
//!
//! - **Error**: The static string-based error scheme we used so far is good for readability, but it
//! has a few drawbacks. The biggest problem with strings are that they are not type safe, e.g. a
//! match statement cannot be exhaustive. These string literals will bloat the final wasm blob,
//! and are relatively heavy to transmit and encode/decode. Moreover, it is easy to mistype them
//! by one character. FRAME errors are exactly a solution to maintain readability, whilst fixing
//! the drawbacks mentioned. In short, we use an enum to represent different variants of our
//! error. These variants are then mapped in an efficient way (using only `u8` indices) to
//! [`sp_runtime::DispatchError::Module`]. Read more about this in [`pallet::error`].
//!
//! - **Event**: Events are akin to the return type of dispatchables. They are mostly data blobs
//! emitted by the runtime to let outside world know what is happening inside the pallet. Since
//! otherwise, the outside world does not have an easy access to the state changes. They should
//! represent what happened at the end of a dispatch operation. Therefore, the convention is to
//! use passive tense for event names (eg. `SomethingHappened`). This allows other sub-systems or
//! external parties (eg. a light-node, a DApp) to listen to particular events happening, without
//! needing to re-execute the whole state transition function.
//!
//! 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!("./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
//! two things:
//!
//! 1. Declare a type in its `Config` that refers to the overarching event type of the runtime. In
//! short, by doing this, the pallet is expressing an important bound: `type RuntimeEvent:
//! From<Event<Self>>`. Read: a `RuntimeEvent` exists, and it can be created from the local `enum
//! Event` of this pallet. This enables the pallet to convert its `Event` into `RuntimeEvent`, and
//! store it where needed.
//!
//! 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!("./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!("./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!("./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
//! [`construct_runtime`](frame::runtime::prelude::construct_runtime). An interesting way to inspect
//! this type is to see its definition in rust-docs:
//! [`crate::guides::your_first_pallet::pallet_v2::tests::runtime_v2::RuntimeEvent`].
//!
//!
//! ## What Next?
//!
//! The following topics where used in this guide, but not covered in depth. It is suggested to
//! study them subsequently:
//!
//! - [`crate::reference_docs::defensive_programming`].
//! - [`crate::reference_docs::frame_origin`].
//! - [`crate::reference_docs::frame_runtime_types`].
//! - The pallet we wrote in this guide was using `dev_mode`, learn more in [`pallet::config`].
//! - Learn more about the individual pallet items/macros, such as event and errors and call, in
//! [`frame::pallet_macros`].
//!
//! [`pallet::storage`]: frame_support::pallet_macros::storage
//! [`pallet::call`]: frame_support::pallet_macros::call
//! [`pallet::event`]: frame_support::pallet_macros::event
//! [`pallet::error`]: frame_support::pallet_macros::error
//! [`pallet::pallet`]: frame_support::pallet
//! [`pallet::config`]: frame_support::pallet_macros::config
//! [`pallet::generate_deposit`]: frame_support::pallet_macros::generate_deposit
#[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<T>(_);
}
#[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<T>(_);
#[docify::export]
/// Single storage item, of type `Balance`.
#[pallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
/// A mapping from `T::AccountId` to `Balance`
#[pallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[docify::export(impl_pallet)]
#[pallet::call]
impl<T: Config> Pallet<T> {
/// 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 `<T: Config>` remains as `<T>`.
Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
// update total issuance.
TotalIssuance::<T>::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::<T>::get(&sender).ok_or("NonExistentAccount")?;
if sender_balance < amount {
return Err("InsufficientBalance".into());
}
let remainder = sender_balance - amount;
// update sender and dest balances.
Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
Balances::<T>::insert(&sender, remainder);
Ok(())
}
}
#[allow(unused)]
impl<T: Config> Pallet<T> {
#[docify::export]
pub fn transfer_better(
origin: T::RuntimeOrigin,
dest: T::AccountId,
amount: Balance,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let sender_balance = Balances::<T>::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::<T>::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::guides::your_first_pallet::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::guides::your_first_pallet::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<Runtime>;
// within pallet we just said `<T as frame_system::Config>::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::<Runtime>::insert(who, amount);
TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
}
});
state
}
#[docify::export]
pub(crate) struct StateBuilder {
balances: Vec<(<Runtime as frame_system::Config>::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: <Runtime as frame_system::Config>::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::<Runtime>::insert(who, amount);
TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
}
});
ext.execute_with(test);
// assertions that must always hold
ext.execute_with(|| {
assert_eq!(
Balances::<Runtime>::iter().map(|(_, x)| x).sum::<u128>(),
TotalIssuance::<Runtime>::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::<Runtime>::get(&ALICE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), None);
// mint some funds into Alice's account.
assert_ok!(Pallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
ALICE,
100
));
// re-check the above
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(100));
})
}
#[docify::export]
#[test]
fn state_builder_works() {
StateBuilder::default().build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
});
}
#[docify::export]
#[test]
fn state_builder_add_balance() {
StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(42));
assert_eq!(TotalIssuance::<Runtime>::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::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
})
}
#[docify::export]
#[test]
fn mint_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pallet::<Runtime>::mint_unsafe(RuntimeOrigin::signed(ALICE), BOB, 100));
// then:
assert_eq!(Balances::<Runtime>::get(&BOB), Some(200));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(300));
// given:
assert_ok!(Pallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
CHARLIE,
100
));
// then:
assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(400));
});
}
#[docify::export]
#[test]
fn transfer_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
// when:
assert_ok!(Pallet::<Runtime>::transfer(RuntimeOrigin::signed(BOB), ALICE, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(TotalIssuance::<Runtime>::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::<Runtime>::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10),
"NonExistentAccount"
);
// then nothing has changed.
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::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.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>
+ TryInto<Event<Self>>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[pallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
#[pallet::error]
pub enum Error<T> {
/// 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<T: Config> {
/// A transfer succeeded.
Transferred { from: T::AccountId, to: T::AccountId, amount: Balance },
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[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::<T>::get(&sender).ok_or(Error::<T>::NonExistentAccount)?;
let remainder =
sender_balance.checked_sub(amount).ok_or(Error::<T>::InsufficientBalance)?;
Balances::<T>::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount));
Balances::<T>::insert(&sender, remainder);
Self::deposit_event(Event::<T>::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::guides::your_first_pallet::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<Runtime>;
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::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
// then:
assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
// now we can also check that an event has been deposited:
assert_eq!(
System::read_events_for_pallet::<Event<Runtime>>(),
vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }]
);
});
}
}
}
+186
View File
@@ -0,0 +1,186 @@
//! # 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::pezkuwi_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<Block> 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::pezkuwi_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<Block> for Runtime {
//! fn build_state(config: Vec<u8>) -> GenesisBuilderResult {
//! build_state::<RuntimeGenesisConfig>(config)
//! }
//!
//! fn get_preset(id: &Option<PresetId>) -> Option<Vec<u8>> {
//! get_preset::<RuntimeGenesisConfig>(id, self::genesis_config_presets::get_preset)
//! }
//!
//! fn preset_names() -> Vec<PresetId> {
//! 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::pezkuwi_sdk::templates`].
//!
//! [`SolochainDefaultConfig`]: struct@frame_system::pallet::config_preludes::SolochainDefaultConfig
#[cfg(test)]
mod tests {
use cmd_lib::run_cmd;
const FIRST_RUNTIME: &'static str = "pezkuwi-sdk-docs-first-runtime";
#[docify::export_content]
#[test]
fn build_runtime() {
run_cmd!(
cargo build --release -p $FIRST_RUNTIME
)
.expect("Failed to run command");
}
}
+50
View File
@@ -0,0 +1,50 @@
//! # Pezkuwi SDK Docs
//!
//! The Pezkuwi SDK Developer Documentation.
//!
//! This crate is a *minimal*, *always-accurate* and low level source of truth about Pezkuwi-SDK.
//! For more high level docs, please go to [docs.pezkuwi.com](https://docs.pezkuwichain.io).
//!
//! ## Getting Started
//!
//! We suggest the following reading sequence:
//!
//! - Start by learning about [`pezkuwi_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 Pezkuwi SDK.
//! - Whilst reading the guides, you might find back-links to [`reference_docs`].
//! - [`external_resources`] for a list of 3rd party guides and tutorials.
//! - Finally, <https://paritytech.github.io> is the parent website of this crate that contains the
//! list of further tools related to the Pezkuwi SDK.
//!
//! ## Information Architecture
//!
//! This section paints a picture over the high-level information architecture of this crate.
#![doc = simple_mermaid::mermaid!("../../mermaid/IA.mmd")]
#![warn(rustdoc::broken_intra_doc_links)]
#![warn(rustdoc::private_intra_doc_links)]
// Frame macros reference features which this crate does not have
#![allow(unexpected_cfgs)]
#![doc(html_favicon_url = "https://pezkuwichain.io/favicon.ico")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/images/Polkadot_Logo_Horizontal_Pink_White.png"
)]
#![doc(issue_tracker_base_url = "https://github.com/pezkuwichain/pezkuwi-sdk/issues")]
/// Meta information about this crate, how it is built, what principles dictates its evolution and
/// how one can contribute to it.
pub mod meta_contributing;
/// A list of external resources and learning material about Pezkuwi SDK.
pub mod external_resources;
/// In-depth guides about the most common components of the Pezkuwi SDK. They are slightly more
/// high level and broad than [`reference_docs`].
pub mod guides;
/// An introduction to the Pezkuwi SDK. Read this module to learn about the structure of the SDK,
/// the tools that are provided as a part of it, and to gain a high level understanding of each.
pub mod pezkuwi_sdk;
/// Reference documents covering in-depth topics across the Pezkuwi SDK. It is suggested to read
/// these on-demand, while you are going through the [`guides`] or other content.
pub mod reference_docs;
+151
View File
@@ -0,0 +1,151 @@
//! # Contribution
//!
//! The following sections cover more detailed information about this crate and how it should be
//! maintained.
//!
//! ## Why Rust Docs?
//!
//! We acknowledge that blockchain based systems, particularly a cutting-edge one like Pezkuwi SDK
//! is a software artifact that is complex, and rapidly evolving. This makes the task of documenting
//! it externally extremely difficult, especially with regards to making sure it is up-to-date.
//!
//! Consequently, we argue that the best hedge against this is to move as much of the documentation
//! near the source code as possible. This would further incentivize developers to keep the
//! documentation up-to-date, as the overhead is reduced by making sure everything is in one
//! repository, and everything being in `.rs` files.
//!
//! > This is not to say that a more visually appealing version of this crate (for example as an
//! > `md-book`) cannot exist, but it would be outside the scope of this crate.
//!
//! Moreover, we acknowledge that a major pain point has been not only outdated *concepts*, but also
//! *outdated code*. For this, we commit to making sure no code-snippet in this crate is left as
//! `///ignore` or `///no_compile`, making sure all code snippets are self-contained, compile-able,
//! and correct at every single revision of the entire repository.
//!
//! > This also allows us to have a clear versioning on the entire content of this crate. For every
//! commit of the Pezkuwi SDK, there would be one version of this crate that is guaranteed to be
//! correct.
//!
//! > To achieve this, we often use [`docify`](https://github.com/sam0x17/docify), a nifty invention
//! > of `@sam0x17`.
//!
//! Also see: <https://github.com/pezkuwichain/pezkuwi-sdk/issues/109>.
//!
//! ## Scope
//!
//! The above would NOT be attainable if we don't acknowledge that the scope of this crate MUST be
//! limited, or else its maintenance burden would be infeasible or not worthwhile. In short, future
//! maintainers should always strive to keep the content of this repository as minimal as possible.
//! Some of the following principles are specifically there to be the guidance for this.
//!
//! ## Principles
//!
//! The following guidelines are meant to be the guiding torch of those who contribute to this
//! crate.
//!
//! 1. 🔺 Ground Up: Information should be laid out in the most ground-up fashion. The lowest level
//! (i.e. "ground") is Rust-docs. The highest level (i.e. "up") is "outside of this crate". In
//! between lies [`reference_docs`] and [`guides`], from low to high. The point of this principle
//! is to document as much of the information as possible in the lower level media, as it is
//! easier to maintain and more reachable. Then, use excessive linking to back-link when writing
//! in a more high level.
//!
//! > A prime example of this, the details of the FRAME storage APIs should NOT be explained in a
//! > high level tutorial. They should be explained in the rust-doc of the corresponding type or
//! > macro.
//!
//! 2. 🧘 Less is More: For reasons mentioned [above](#why-rust-docs), the more concise this crate
//! is, the better.
//! 3. √ Dont Repeat Yourself DRY: A summary of the above two points. Authors should always
//! strive to avoid any duplicate information. Every concept should ideally be documented in
//! *ONE* place and one place only. This makes the task of maintaining topics significantly
//! easier.
//!
//! > A prime example of this, the list of CLI arguments of a particular binary should not be
//! > documented in multiple places across this crate. It should be only be documented in the
//! > corresponding crate (e.g. `sc_cli`).
//!
//! > Moreover, this means that as a contributor, **it is your responsibility to have a grasp over
//! > 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/pezkuwichain/pezkuwi-sdk/blob/master/docs/contributor/DOCUMENTATION_GUIDELINES.md).
//!
//! #### Example: Explaining `#[pallet::call]`
//!
//! <details>
//! <summary>
//! Let's consider the seemingly simple example of explaining to someone dead-simple code of a FRAME
//! call and see how we can use the above principles.
//! </summary>
//!
//!
//! ```
//! #[frame::pallet(dev_mode)]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[pallet::call]
//! impl<T: Config> Pallet<T> {
//! pub fn a_simple_call(origin: OriginFor<T>, data: u32) -> DispatchResult {
//! ensure!(data > 10, "SomeStaticString");
//! todo!();
//! }
//! }
//! }
//! ```
//!
//! * Before even getting started, what is with all of this `<T: Config>`? We link to
//! [`crate::reference_docs::trait_based_programming`].
//! * First, the name. Why is this called `pallet::call`? This goes back to `enum Call`, which is
//! explained in [`crate::reference_docs::frame_runtime_types`]. Build on top of this!
//! * Then, what is `origin`? Just an account id? [`crate::reference_docs::frame_origin`].
//! * Then, what is `DispatchResult`? Why is this called *dispatch*? Probably something that can be
//! explained in the documentation of [`frame::prelude::DispatchResult`].
//! * Why is `"SomeStaticString"` a valid error? Because there is implementation for it that you can
//! see [here](frame::prelude::DispatchError#impl-From<%26'static+str>-for-DispatchError).
//!
//!
//! All of these are examples of underlying information that a contributor should:
//!
//! 1. Try and create and they are going along.
//! 2. Back-link to if they already exist.
//!
//! Of course, all of this is not set in stone as a either/or rule. Sometimes, it is necessary to
//! rephrase a concept in a new context.
//!
//! </details>
//!
//! ## `crates.io` and Publishing
//!
//! As it stands now, this crate cannot be published to crates.io because of its use of
//! [workspace-level `docify`](https://github.com/sam0x17/docify/issues/22). For now, we accept this
//! 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 `pezkuwi-sdk`. A
//! few solutions have been proposed to improve this, please see
//! [here](https://github.com/pezkuwichain/pezkuwi-sdk/issues/146).
//!
//! ## How to Develop Locally
//!
//! To view the docs specific [`crate`] locally for development, including the correct HTML headers
//! injected, run:
//!
//! ```sh
//! SKIP_WASM_BUILD=1 \
//! RUSTDOCFLAGS="--html-in-header $(pwd)/docs/sdk/assets/header.html --extend-css $(pwd)/docs/sdk/assets/theme.css --default-theme=ayu" \
//! cargo doc -p pezkuwi-sdk-docs --no-deps --open
//! ```
//!
//! If even faster build time for docs is needed, you can temporarily remove most of the
//! substrate/cumulus dependencies that are only used for linking purposes.
//!
//! For more on local development, see [`crate::reference_docs::development_environment_advice`].
+130
View File
@@ -0,0 +1,130 @@
//! # Cumulus
//!
//! Substrate provides a framework ([FRAME]) through which a blockchain node and runtime can easily
//! be created. Cumulus aims to extend the same approach to creation of Pezkuwi teyrchains.
//!
//! > Cumulus clouds are shaped sort of like dots; together they form a system that is intricate,
//! > beautiful and functional.
//!
//! ## Example: Runtime
//!
//! A Cumulus-based runtime is fairly similar to other [FRAME]-based runtimes. Most notably, the
//! following changes are applied to a normal FRAME-based runtime to make it a Cumulus-based
//! runtime:
//!
//! #### Cumulus Pallets
//!
//! A teyrchain runtime should use a number of pallets that are provided by Cumulus and Substrate.
//! Notably:
//!
//! - [`frame-system`](frame::prelude::frame_system), like all FRAME-based runtimes.
//! - [`cumulus_pallet_teyrchain_system`]
//! - [`teyrchain_info`]
#![doc = docify::embed!("./src/pezkuwi_sdk/cumulus.rs", system_pallets)]
//!
//! Given that all Cumulus-based runtimes use a simple Aura-based consensus mechanism, the following
//! pallets also need to be added:
//!
//! - [`pallet_timestamp`]
//! - [`pallet_aura`]
//! - [`cumulus_pallet_aura_ext`]
#![doc = docify::embed!("./src/pezkuwi_sdk/cumulus.rs", consensus_pallets)]
//!
//!
//! Finally, a separate macro, similar to
//! [`impl_runtime_api`](frame::runtime::prelude::impl_runtime_apis), which creates the default set
//! of runtime APIs, will generate the teyrchain runtime's validation runtime API, also known as
//! teyrchain validation function (PVF). Without this API, the relay chain is unable to validate
//! blocks produced by our teyrchain.
#![doc = docify::embed!("./src/pezkuwi_sdk/cumulus.rs", validate_block)]
//!
//! ---
//!
//! [FRAME]: crate::pezkuwi_sdk::frame_runtime
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
#[cfg(test)]
mod tests {
mod runtime {
pub use frame::{
deps::sp_consensus_aura::sr25519::AuthorityId as AuraId, prelude::*,
runtime::prelude::*, testing_prelude::*,
};
#[docify::export(CR)]
construct_runtime!(
pub enum Runtime {
// system-level pallets.
System: frame_system,
Timestamp: pallet_timestamp,
TeyrchainSystem: cumulus_pallet_teyrchain_system,
TeyrchainInfo: teyrchain_info,
// teyrchain consensus support -- mandatory.
Aura: pallet_aura,
AuraExt: cumulus_pallet_aura_ext,
}
);
#[docify::export]
mod system_pallets {
use super::*;
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
type OnSetCode = cumulus_pallet_teyrchain_system::TeyrchainSetCode<Self>;
}
impl cumulus_pallet_teyrchain_system::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type OnSystemEvent = ();
type SelfParaId = teyrchain_info::Pallet<Runtime>;
type OutboundXcmpMessageSource = ();
type XcmpMessageHandler = ();
type ReservedDmpWeight = ();
type ReservedXcmpWeight = ();
type CheckAssociatedRelayNumber =
cumulus_pallet_teyrchain_system::RelayNumberMonotonicallyIncreases;
type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook<
Runtime,
6000, // relay chain block time
1,
1,
>;
type WeightInfo = ();
type DmpQueue = frame::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>;
type RelayParentOffset = ConstU32<0>;
}
impl teyrchain_info::Config for Runtime {}
}
#[docify::export]
mod consensus_pallets {
use super::*;
impl pallet_aura::Config for Runtime {
type AuthorityId = AuraId;
type DisabledValidators = ();
type MaxAuthorities = ConstU32<100_000>;
type AllowMultipleBlocksPerSlot = ConstBool<false>;
type SlotDuration = pallet_aura::MinimumPeriodTimesTwo<Self>;
}
#[docify::export(timestamp)]
#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)]
impl pallet_timestamp::Config for Runtime {}
impl cumulus_pallet_aura_ext::Config for Runtime {}
}
#[docify::export(validate_block)]
cumulus_pallet_teyrchain_system::register_validate_block! {
Runtime = Runtime,
BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::<Runtime, Executive>,
}
}
}
+175
View File
@@ -0,0 +1,175 @@
//! # FRAME
//!
//! ```no_compile
//! ______ ______ ________ ___ __ __ ______
//! /_____/\ /_____/\ /_______/\ /__//_//_/\ /_____/\
//! \::::_\/_\:::_ \ \ \::: _ \ \\::\| \| \ \\::::_\/_
//! \:\/___/\\:(_) ) )_\::(_) \ \\:. \ \\:\/___/\
//! \:::._\/ \: __ `\ \\:: __ \ \\:.\-/\ \ \\::___\/_
//! \:\ \ \ \ `\ \ \\:.\ \ \ \\. \ \ \ \\:\____/\
//! \_\/ \_\/ \_\/ \__\/\__\/ \__\/ \__\/ \_____\/
//! ```
//!
//! > **F**ramework for **R**untime **A**ggregation of **M**odularized **E**ntities: Substrate's
//! > State Transition Function (Runtime) Framework.
//!
//! ## Introduction
//!
//! As described in [`crate::reference_docs::wasm_meta_protocol`], at a high-level Substrate-based
//! blockchains are composed of two parts:
//!
//! 1. A *runtime* which represents the state transition function (i.e. "Business Logic") of a
//! blockchain, and is encoded as a WASM blob.
//! 2. A node whose primary purpose is to execute the given runtime.
#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_simple.mmd")]
//!
//! *FRAME is the Substrate's framework of choice to build a runtime.*
//!
//! FRAME is composed of two major components, **pallets** and a **runtime**.
//!
//! ## Pallets
//!
//! A pallet is a unit of encapsulated logic. It has a clearly defined responsibility and can be
//! linked to other pallets. In order to be reusable, pallets shipped with FRAME strive to only care
//! about its own responsibilities and make as few assumptions about the general runtime as
//! possible. A pallet is analogous to a _module_ in the runtime.
//!
//! A pallet is defined as a `mod pallet` wrapped by the [`frame::pallet`] macro. Within this macro,
//! pallet components/parts can be defined. Most notable of these parts are:
//!
//! - [Config](frame::pallet_macros::config), allowing a pallet to make itself configurable and
//! generic over types, values and such.
//! - [Storage](frame::pallet_macros::storage), allowing a pallet to define onchain storage.
//! - [Dispatchable function](frame::pallet_macros::call), allowing a pallet to define extrinsics
//! that are callable by end users, from the outer world.
//! - [Events](frame::pallet_macros::event), allowing a pallet to emit events.
//! - [Errors](frame::pallet_macros::error), allowing a pallet to emit well-formed errors.
//!
//! Some of these pallet components resemble the building blocks of a smart contract. While both
//! models are programming state transition functions of blockchains, there are crucial differences
//! between the two. See [`crate::reference_docs::runtime_vs_smart_contract`] for more.
//!
//! Most of these components are defined using macros, the full list of which can be found in
//! [`frame::pallet_macros`].
//!
//! ### Example
//!
//! The following example showcases a minimal pallet.
#![doc = docify::embed!("src/pezkuwi_sdk/frame_runtime.rs", pallet)]
//!
//! ## Runtime
//!
//! A runtime is a collection of pallets that are amalgamated together. Each pallet typically has
//! some configurations (exposed as a `trait Config`) that needs to be *specified* in the runtime.
//! This is done with [`frame::runtime::prelude::construct_runtime`].
//!
//! A (real) runtime that actually wishes to compile to WASM needs to also implement a set of
//! runtime-apis. These implementation can be specified using the
//! [`frame::runtime::prelude::impl_runtime_apis`] macro.
//!
//! ### Example
//!
//! The following example shows a (test) runtime that is composing the pallet demonstrated above,
//! next to the [`frame::prelude::frame_system`] pallet, into a runtime.
#![doc = docify::embed!("src/pezkuwi_sdk/frame_runtime.rs", runtime)]
//!
//! ## More Examples
//!
//! You can find more FRAME examples that revolve around specific features at [`pallet_examples`].
//!
//! ## Alternatives 🌈
//!
//! There is nothing in the Substrate's node side code-base that mandates the use of FRAME. While
//! FRAME makes it very simple to write Substrate-based runtimes, it is by no means intended to be
//! the only one. At the end of the day, any WASM blob that exposes the right set of runtime APIs is
//! a valid Runtime form the point of view of a Substrate client (see
//! [`crate::reference_docs::wasm_meta_protocol`]). Notable examples are:
//!
//! * writing a runtime in pure Rust, as done in [this template](https://github.com/JoshOrndorff/frameless-node-template).
//! * writing a runtime in AssemblyScript, as explored in [this project](https://github.com/LimeChain/subsembly).
/// A FRAME based pallet. This `mod` is the entry point for everything else. All
/// `#[pallet::xxx]` macros must be defined in this `mod`. Although, frame also provides an
/// experimental feature to break these parts into different `mod`s. See [`pallet_examples`] for
/// more.
#[docify::export]
#[frame::pallet(dev_mode)]
pub mod pallet {
use frame::prelude::*;
/// The configuration trait of a pallet. Mandatory. Allows a pallet to receive types at a
/// later point from the runtime that wishes to contain it. It allows the pallet to be
/// parameterized over both types and values.
#[pallet::config]
pub trait Config: frame_system::Config {
/// A type that is not known now, but the runtime that will contain this pallet will
/// know it later, therefore we define it here as an associated type.
#[allow(deprecated)]
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
/// A parameterize-able value that we receive later via the `Get<_>` trait.
type ValueParameter: Get<u32>;
/// Similar to [`Config::ValueParameter`], but using `const`. Both are functionally
/// equal, but offer different tradeoffs.
const ANOTHER_VALUE_PARAMETER: u32;
}
/// A mandatory struct in each pallet. All functions callable by external users (aka.
/// transactions) must be attached to this type (see [`frame::pallet_macros::call`]). For
/// convenience, internal (private) functions can also be attached to this type.
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
/// The events that this pallet can emit.
#[pallet::event]
pub enum Event<T: Config> {}
/// A storage item that this pallet contains. This will be part of the state root trie
/// of the blockchain.
#[pallet::storage]
pub type Value<T> = StorageValue<Value = u32>;
/// All *dispatchable* call functions (aka. transactions) are attached to `Pallet` in a
/// `impl` block.
#[pallet::call]
impl<T: Config> Pallet<T> {
/// This will be callable by external users, and has two u32s as a parameter.
pub fn some_dispatchable(
_origin: OriginFor<T>,
_param: u32,
_other_para: u32,
) -> DispatchResult {
Ok(())
}
}
}
/// A simple runtime that contains the above pallet and `frame_system`, the mandatory pallet of
/// all runtimes. This runtime is for testing, but it shares a lot of similarities with a *real*
/// runtime.
#[docify::export]
pub mod runtime {
use super::pallet as pallet_example;
use frame::{prelude::*, testing_prelude::*};
// The major macro that amalgamates pallets into `enum Runtime`
construct_runtime!(
pub enum Runtime {
System: frame_system,
Example: pallet_example,
}
);
// These `impl` blocks specify the parameters of each pallet's `trait Config`.
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
}
impl pallet_example::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type ValueParameter = ConstU32<42>;
const ANOTHER_VALUE_PARAMETER: u32 = 42;
}
}
+158
View File
@@ -0,0 +1,158 @@
//! # Pezkuwi SDK
//!
//! [Pezkuwi SDK](https://github.com/pezkuwichain/pezkuwi-sdk) provides the main resources needed to
//! start building on the [Pezkuwi network](https://pezkuwichain.io/), a scalable, multi-chain
//! blockchain platform that enables different blockchains to securely interoperate.
//!
//! [![StackExchange](https://img.shields.io/badge/StackExchange-Polkadot%20and%20Substrate-222222?logo=stackexchange)](https://exchange.pezkuwichain.app/)
//!
//! [![awesomeDot](https://img.shields.io/badge/polkadot-awesome-e6007a?logo=polkadot)](https://github.com/Awsmdot/awesome-dot)
//! [![wiki](https://img.shields.io/badge/polkadot-wiki-e6007a?logo=polkadot)](https://wiki.network.pezkuwichain.io/)
//! [![forum](https://img.shields.io/badge/polkadot-forum-e6007a?logo=polkadot)](https://forum.polkadot.network/)
//!
//! [![RFCs](https://img.shields.io/badge/fellowship-RFCs-e6007a?logo=polkadot)](https://github.com/polkadot-fellows/rfcs)
//! [![Runtime](https://img.shields.io/badge/fellowship-runtimes-e6007a?logo=polkadot)](https://github.com/polkadot-fellows/runtimes)
//! [![Manifesto](https://img.shields.io/badge/fellowship-manifesto-e6007a?logo=polkadot)](https://github.com/polkadot-fellows/manifesto/blob/main/manifesto.pdf)
//!
//! ## Getting Started
//!
//! The primary way to get started with the Pezkuwi SDK is to start writing a FRAME-based runtime.
//! See:
//!
//! * [`pezkuwi`], to understand what is Pezkuwi as a development platform.
//! * [`substrate`], for an overview of what Substrate as the main blockchain framework of Pezkuwi
//! SDK.
//! * [`frame`], to learn about how to write blockchain applications aka. "App Chains".
//! * Continue with the [`pezkuwi_sdk_docs`'s "getting started"](crate#getting-started).
//!
//! ## Components
//!
//! #### Substrate
//!
//! [![Substrate-license](https://img.shields.io/badge/License-GPL3%2FApache2.0-blue)](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/substrate/LICENSE-APACHE2)
//! [![GitHub
//! Repo](https://img.shields.io/badge/github-substrate-2324CC85)](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/substrate)
//!
//! [`substrate`] is the base blockchain framework used to power the Pezkuwi SDK. It is a full
//! toolkit to create sovereign blockchains, including but not limited to those which connect to
//! Pezkuwi as teyrchains.
//!
//! #### FRAME
//!
//! [![Substrate-license](https://img.shields.io/badge/License-Apache2.0-blue)](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/substrate/LICENSE-APACHE2)
//! [![GitHub
//! Repo](https://img.shields.io/badge/github-frame-2324CC85)](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/substrate/frame)
//!
//! [`frame`] is the framework used to create Substrate-based application logic, aka. runtimes.
//! Learn more about the distinction of a runtime and node in
//! [`reference_docs::wasm_meta_protocol`].
//!
//! #### Cumulus
//!
//! [![Cumulus-license](https://img.shields.io/badge/License-GPL3-blue)](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/cumulus/LICENSE)
//! [![GitHub
//! Repo](https://img.shields.io/badge/github-cumulus-white)](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/cumulus)
//!
//! [`cumulus`] transforms FRAME-based runtimes into Pezkuwi-compatible teyrchain runtimes, and
//! Substrate-based nodes into Pezkuwi/Teyrchain-compatible nodes.
//!
//! #### XCM
//!
//! [![XCM-license](https://img.shields.io/badge/License-GPL3-blue)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/LICENSE)
//! [![GitHub
//! Repo](https://img.shields.io/badge/github-XCM-e6007a?logo=polkadot)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm)
//!
//! [`xcm`], short for "cross consensus message", is the primary format that is used for
//! communication between teyrchains, but is intended to be extensible to other use cases as well.
//!
//! #### Pezkuwi
//!
//! [![Pezkuwi-license](https://img.shields.io/badge/License-GPL3-blue)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/LICENSE)
//! [![GitHub
//! Repo](https://img.shields.io/badge/github-polkadot-e6007a?logo=polkadot)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot)
//!
//! [`pezkuwi`] is an implementation of a Pezkuwi node in Rust, by `@paritytech`. The Pezkuwi
//! runtimes are located under the
//! [`pezkuwi-fellows/runtimes`](https://github.com/polkadot-fellows/runtimes) repository.
//!
//! ### Binaries
//!
//! The main binaries that are part of the Pezkuwi SDK are:
//! * [`pezkuwi`]: The Pezkuwi relay chain node binary, as noted above.
//! * [`pezkuwi-omni-node`]: A white-labeled teyrchain collator node. See more in
//! [`crate::reference_docs::omni_node`].
//! * [`pezkuwi-teyrchain-bin`]: The collator node used to run collators for all Pezkuwi system
//! teyrchains.
//! * [`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 Pezkuwi SDK work together:
#![doc = simple_mermaid::mermaid!("../../../mermaid/pezkuwi_sdk_substrate.mmd")]
//!
//! A Substrate-based chain is a blockchain composed of a runtime and a node. As noted above, the
//! runtime is the application logic of the blockchain, and the node is everything else.
//! See [`reference_docs::wasm_meta_protocol`] for an in-depth explanation of this. The
//! former is built with [`frame`], and the latter is built with rest of Substrate.
//!
//! > You can think of a Substrate-based chain as a white-labeled blockchain.
#![doc = simple_mermaid::mermaid!("../../../mermaid/pezkuwi_sdk_pezkuwi.mmd")]
//! Pezkuwi is itself a Substrate-based chain, composed of the exact same two components. It has
//! specialized logic in both the node and the runtime side, but it is not "special" in any way.
//!
//! A teyrchain is a "special" Substrate-based chain, whereby both the node and the runtime
//! components have became "Pezkuwi-aware" using Cumulus.
#![doc = simple_mermaid::mermaid!("../../../mermaid/pezkuwi_sdk_teyrchain.mmd")]
//!
//! ## Notable Upstream Crates
//!
//! - [`parity-scale-codec`](https://github.com/paritytech/parity-scale-codec)
//! - [`parity-db`](https://github.com/paritytech/parity-db)
//! - [`trie`](https://github.com/paritytech/trie)
//! - [`parity-common`](https://github.com/paritytech/parity-common)
//!
//! ## Trophy Section: Notable Downstream Projects
//!
//! A list of projects and tools in the blockchain ecosystem that one way or another use parts of
//! the Pezkuwi SDK:
//!
//! * [Avail](https://github.com/availproject/avail)
//! * [Cardano Partner Chains](https://iohk.io/en/blog/posts/2023/11/03/partner-chains-are-coming-to-cardano/)
//! * [Starknet's Madara Sequencer](https://github.com/keep-starknet-strange/madara)
//! * [Polymesh](https://polymesh.network/)
//!
//! [`substrate`]: crate::pezkuwi_sdk::substrate
//! [`frame`]: crate::pezkuwi_sdk::frame_runtime
//! [`cumulus`]: crate::pezkuwi_sdk::cumulus
//! [`pezkuwi`]: crate::pezkuwi_sdk::pezkuwi
//! [`xcm`]: crate::pezkuwi_sdk::xcm
//! [`frame-omni-bencher`]: https://crates.io/crates/frame-omni-bencher
//! [`pezkuwi-teyrchain-bin`]: https://crates.io/crates/polkadot-parachain-bin
//! [`pezkuwi-omni-node`]: https://crates.io/crates/polkadot-omni-node
/// Learn about Cumulus, the framework that transforms [`substrate`]-based chains into
/// [`pezkuwi`]-enabled teyrchains.
pub mod cumulus;
/// Learn about FRAME, the framework used to build Substrate runtimes.
pub mod frame_runtime;
/// Learn about Pezkuwi as a platform.
pub mod pezkuwi;
/// Learn about different ways through which smart contracts can be utilized on top of Substrate,
/// and in the Pezkuwi ecosystem.
pub mod smart_contracts;
/// Learn about Substrate, the main blockchain framework used in the Pezkuwi ecosystem.
pub mod substrate;
/// Index of all the templates that can act as first scaffold for a new project.
pub mod templates;
/// Learn about XCM, the de-facto communication language between different consensus systems.
pub mod xcm;
+96
View File
@@ -0,0 +1,96 @@
//! # Pezkuwi
//!
//! Implementation of the Pezkuwi node/host in Rust.
//!
//! ## Learn More and Get Involved
//!
//! - [Pezkuwi Forum](https://forum.polkadot.network/)
//! - [Pezkuwi Teyrchains](https://parachains.info/)
//! - [Pezkuwi (multi-chain) Explorer: Subscan](https://subscan.io/)
//! - Pezkuwi Fellowship
//! - [Manifesto](https://github.com/polkadot-fellows/manifesto/blob/main/manifesto.pdf)
//! - [Runtimes](https://github.com/polkadot-fellows/runtimes)
//! - [RFCs](https://github.com/polkadot-fellows/rfcs)
//! - [Dashboard](https://polkadot-fellows.github.io/dashboard/)
//! - [Pezkuwi Specs](http://spec.polkadot.network)
//! - [The Pezkuwi Teyrchain Host Implementers' Guide](https://docs.pezkuwichain.io/sdk/book/)
//! - [Pezkuwi papers](https://pezkuwichain.io/papers/)
//! - [JAM Graypaper](https://graypaper.com)
//!
//! ## Alternative Node Implementations 🌈
//!
//! - [Smoldot](https://docs.rs/crate/smoldot-light/latest). Pezkuwi light node/client.
//! - [KAGOME](https://github.com/qdrvm/kagome). C++ implementation of the Pezkuwi host.
//! - [Gossamer](https://github.com/ChainSafe/gossamer). Golang implementation of the Pezkuwi host.
//!
//! ## Platform
//!
//! In this section, we examine what platform Pezkuwi exactly provides to developers.
//!
//! ### Pezkuwi White Paper
//!
//! The original vision of Pezkuwi (everything in the whitepaper, which was eventually called
//! **Pezkuwi 1.0**) revolves around the following arguments:
//!
//! * Future is multi-chain, because we need different chains with different specialization to
//! achieve widespread goals.
//! * In other words, no single chain is good enough to achieve all goals.
//! * A multi-chain future will inadvertently suffer from fragmentation of economic security.
//! * This stake fragmentation will make communication over consensus system with varying security
//! levels inherently unsafe.
//!
//! Pezkuwi's answer to the above is:
//!
//! > The chains of the future must have a way to share their economic security, whilst maintaining
//! > their execution and governance sovereignty. These chains are called "Teyrchains".
//!
//! * Shared Security: The idea of shared economic security sits at the core of Pezkuwi. Pezkuwi
//! enables different teyrchains to pool their economic security from Pezkuwi (i.e. "*Relay
//! Chain*").
//! * (heterogenous) Sharded Execution: Yet, each teyrchain is free to have its own execution logic
//! (runtime), which also encompasses governance and sovereignty. Moreover, Pezkuwi ensures the
//! correct execution of all teyrchains, without having all of its validators re-execute all
//! teyrchain blocks. When seen from this perspective, Pezkuwi achieves the ability to verify
//! the validity of the block execution of multiple teyrchains using the same set of validators as
//! the Relay Chain. In practice, this means that the shards (teyrchains) share the same economic
//! security as the Relay Chain.
//! Learn about this process called [Approval Checking](https://pezkuwichain.io/blog/polkadot-v1-0-sharding-and-economic-security#approval-checking-and-finality).
//! * A framework to build blockchains: In order to materialize the ecosystem of teyrchains, an easy
//! blockchain framework must exist. This is [Substrate](crate::pezkuwi_sdk::substrate),
//! [FRAME](crate::pezkuwi_sdk::frame_runtime) and [Cumulus](crate::pezkuwi_sdk::cumulus).
//! * A communication language between blockchains: In order for these blockchains to communicate,
//! they need a shared language. [XCM](crate::pezkuwi_sdk::xcm) is one such language, and the one
//! that is most endorsed in the Pezkuwi ecosystem.
//!
//! > Note that the interoperability promised by Pezkuwi is unparalleled in that any two teyrchains
//! > connected to Pezkuwi have the same security and can have much better guarantees about the
//! > security of the recipient of any message.
//! > Bridges enable transaction and information flow between different consensus systems, crucial
//! > for Pezkuwi's multi-chain architecture. However, they can become the network's most
//! > vulnerable points. If a bridge's security measures are weaker than those of the connected
//! > blockchains, it poses a significant risk. Attackers might exploit these weaknesses to launch
//! > attacks such as theft or disruption of services.
//!
//! Pezkuwi delivers the above vision, alongside a flexible means for teyrchains to schedule
//! themselves with the Relay Chain. To achieve this, Pezkuwi has been developed with an
//! architecture similar to that of a computer. Pezkuwi Relay Chain has a number of "cores". Each
//! core is (in simple terms) capable of progressing 1 teyrchain at a time. For example, a teyrchain
//! can schedule itself on a single core for 5 relay chain blocks.
//!
//! Within the scope of Pezkuwi 1.x, two main scheduling ways have been considered:
//!
//! * Long term Teyrchains, obtained through locking a sum of HEZ in an auction system.
//! * On-demand Teyrchains, purchased through paying HEZ to the relay-chain whenever needed.
//!
//! ### The Future
//!
//! After delivering Pezkuwi 1.x, the future of Pezkuwi as a protocol and platform is in the hands
//! of the community and the fellowship. This is happening most notable through the RFC process.
//! Some of the RFCs that do alter Pezkuwi as a platform and have already passed are as follows:
//!
//! - RFC#1: [Agile-coretime](https://github.com/polkadot-fellows/RFCs/blob/main/text/0001-agile-coretime.md):
//! Agile periodic-sale-based model for assigning Coretime on the Pezkuwi Ubiquitous Computer.
//! - RFC#5: [Coretime-interface](https://github.com/polkadot-fellows/RFCs/blob/main/text/0005-coretime-interface.md):
//! Interface for manipulating the usage of cores on the Pezkuwi Ubiquitous Computer.
//!
//! Learn more about [Pezkuwi as a Computational Resource](https://wiki.network.pezkuwichain.io/docs/polkadot-direction#polkadot-as-a-computational-resource).
+9
View File
@@ -0,0 +1,9 @@
//! # Smart Contracts
//!
//! TODO: @cmichi <https://github.com/pezkuwichain/pezkuwi-sdk/issues/161>
//!
//! - WASM and EVM based, pallet-contracts and pallet-evm.
//! - single-daap-chain, transition from ink! to FRAME.
//! - Link to `use.ink`
//! - Link to [`crate::reference_docs::runtime_vs_smart_contract`].
//! - <https://use.ink/migrate-ink-contracts-to-polkadot-frame-parachain/>
+137
View File
@@ -0,0 +1,137 @@
//! # Substrate
//!
//! Substrate is a Rust framework for building blockchains in a modular and extensible way. While in
//! itself un-opinionated, it is the main engine behind the Pezkuwi ecosystem.
//!
//! ## Overview, Philosophy
//!
//! Substrate approaches blockchain development with an acknowledgement of a few self-evident
//! truths:
//!
//! 1. Society and technology evolves.
//! 2. Humans are fallible.
//!
//! This makes the task of designing a correct, safe and long-lasting blockchain system hard.
//!
//! Nonetheless, in strive towards achieving this goal, Substrate embraces the following:
//!
//! 1. Use of **Rust** as a modern and safe programming language, which limits human error through
//! various means, most notably memory and type safety.
//! 2. Substrate is written from the ground-up with a *generic, modular and extensible* design. This
//! ensures that software components can be easily swapped and upgraded. Examples of this is
//! multiple consensus mechanisms provided by Substrate, as listed below.
//! 3. Lastly, the final blockchain system created with the above properties needs to be
//! upgradeable. In order to achieve this, Substrate is designed as a meta-protocol, whereby the
//! application logic of the blockchain (called "Runtime") is encoded as a WASM blob, and is
//! stored in the state. The rest of the system (called "node") acts as the executor of the WASM
//! blob.
//!
//! In essence, the meta-protocol of all Substrate based chains is the "Runtime as WASM blob"
//! accord. This enables the Runtime to become inherently upgradeable, crucially without [forks](https://en.wikipedia.org/wiki/Fork_(blockchain)). The
//! upgrade is merely a matter of the WASM blob being changed in the state, which is, in principle,
//! same as updating an account's balance. Learn more about this in detail in
//! [`crate::reference_docs::wasm_meta_protocol`].
//!
//! > A great analogy for substrate is the following: Substrate node is a gaming console, and a WASM
//! > runtime, possibly created with FRAME is the game being inserted into the console.
//!
//! [`frame`], Substrate's default runtime development library, takes the above safety practices
//! even further by embracing a declarative programming model whereby correctness is enhanced and
//! the system is highly configurable through parameterization. Learn more about this in
//! [`crate::reference_docs::trait_based_programming`].
//!
//! ## How to Get Started
//!
//! Substrate offers different options at the spectrum of technical freedom <-> development ease.
//!
//! * The easiest way to use Substrate is to use one of the templates (some of which listed at
//! [`crate::pezkuwi_sdk::templates`]) and only tweak the parameters of the runtime or node. This
//! allows you to launch a blockchain in minutes, but is limited in technical freedom.
//! * Next, most developers wish to develop their custom runtime modules, for which the de-facto way
//! is [`frame`](crate::pezkuwi_sdk::frame_runtime).
//! * Finally, Substrate is highly configurable at the node side as well, but this is the most
//! technically demanding.
//!
//! > A notable Substrate-based blockchain that has built both custom FRAME pallets and custom
//! > node-side components is <https://github.com/Cardinal-Cryptography/aleph-node>.
#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_dev.mmd")]
//!
//! ## Structure
//!
//! Substrate contains a large number of crates, therefore it is useful to have an overview of what
//! they are, and how they are organized. In broad terms, these crates are divided into three
//! categories:
//!
//! * `sc-*` (short for *Substrate-client*) crates, located under `./client` folder. These are all
//! the crates that lead to the node software. Notable examples are [`sc_network`], various
//! consensus crates, RPC ([`sc_rpc_api`]) and database ([`sc_client_db`]), all of which are
//! expected to reside in the node side.
//! * `sp-*` (short for *substrate-primitives*) crates, located under `./primitives` folder. These
//! are crates that facilitate both the node and the runtime, but are not opinionated about what
//! framework is using for building the runtime. Notable examples are [`sp_api`] and [`sp_io`],
//! which form the communication bridge between the node and runtime.
//! * `pallet-*` and `frame-*` crates, located under `./frame` folder. These are the crates related
//! to FRAME. See [`frame`] for more information.
//!
//! ### WASM Build
//!
//! Many of the Substrate crates, such as entire `sp-*`, need to compile to both WASM (when a WASM
//! runtime is being generated) and native (for example, when testing). To achieve this, Substrate
//! follows the convention of the Rust community, and uses a `feature = "std"` to signify that a
//! crate is being built with the standard library, and is built for native. Otherwise, it is built
//! for `no_std`.
//!
//! This can be summarized in `#![cfg_attr(not(feature = "std"), no_std)]`, which you can often find
//! in any Substrate-based runtime.
//!
//! Substrate-based runtimes use [`substrate_wasm_builder`] in their `build.rs` to automatically
//! build their WASM files as a part of normal build command (e.g. `cargo build`). Once built, the
//! wasm file is placed in `./target/{debug|release}/wbuild/{runtime_name}/{runtime_name}.wasm`.
//!
//! In order to ensure that the WASM build is **deterministic**, the [Substrate Runtime Toolbox (srtool)](https://github.com/paritytech/srtool) can be used.
//!
//! ### Anatomy of a Binary Crate
//!
//! From the above, [`node_cli`]/[`kitchensink_runtime`] and `node-template` are essentially
//! blueprints of a Substrate-based project, as the name of the latter is implying. Each
//! Substrate-based project typically contains the following:
//!
//! * Under `./runtime`, a `./runtime/src/lib.rs` which is the top level runtime amalgamator file.
//! This file typically contains the [`frame::runtime::prelude::construct_runtime`] and
//! [`frame::runtime::prelude::impl_runtime_apis`] macro calls, which is the final definition of a
//! runtime.
//!
//! * Under `./node`, a `main.rs`, which is the starting point, and a `./service.rs`, which contains
//! all the node side components. Skimming this file yields an overview of the networking,
//! database, consensus and similar node side components.
//!
//! > The above two are conventions, not rules.
//!
//! > See <https://github.com/pezkuwichain/pezkuwi-sdk/issues/94> for an update on how the node side
//! > components are being amalgamated.
//!
//! ## Teyrchain?
//!
//! As noted above, Substrate is the main engine behind the Pezkuwi ecosystem. One of the ways
//! through which Pezkuwi can be utilized is by building "teyrchains", blockchains that are
//! connected to Pezkuwi's shared security.
//!
//! To build a teyrchain, one could use [Cumulus](crate::pezkuwi_sdk::cumulus), the library on
//! top of Substrate, empowering any substrate-based chain to be a Pezkuwi teyrchain.
//!
//! ## Where To Go Next?
//!
//! Additional noteworthy crates within substrate:
//!
//! - RPC APIs of a Substrate node: [`sc_rpc_api`]/[`sc_rpc`]
//! - CLI Options of a Substrate node: [`sc_cli`]
//! - All of the consensus related crates provided by Substrate:
//! - [`sc_consensus_aura`]
//! - [`sc_consensus_babe`]
//! - [`sc_consensus_grandpa`]
//! - [`sc_consensus_beefy`] (TODO: @adrian, add some high level docs <https://github.com/pezkuwichain/pezkuwi-sdk/issues/162>)
//! - [`sc_consensus_manual_seal`]
//! - [`sc_consensus_pow`]
#[doc(hidden)]
pub use crate::pezkuwi_sdk;
+44
View File
@@ -0,0 +1,44 @@
//! # Templates
//!
//! This document enumerates a non-exhaustive list of templates that one can use to get started with
//! pezkuwi-sdk.
//!
//! > Know more tools/templates that are not listed here? please contribute them by opening a PR.
//!
//! ## Internal
//!
//! The following templates are maintained as a part of the `pezkuwi-sdk` repository:
//!
//! - [`minimal-template`](https://github.com/pezkuwichain/pezkuwi-sdk/issues/25): A minimal
//! template that contains the least amount of features to be a functioning blockchain. Suitable
//! for learning and testing.
//! - [`solochain-template`](https://github.com/pezkuwichain/pezkuwi-sdk/issues/25): Formerly known
//! as "substrate-node-template", is a white-labeled substrate-based blockchain (aka. solochain)
//! that contains moderate features, such as a basic consensus engine and some FRAME pallets. This
//! template can act as a good starting point for those who want to launch a solochain.
//! - [`teyrchain-template`](https://github.com/pezkuwichain/pezkuwi-sdk-teyrchain-template):
//! A teyrchain template ready to be connected to a relay-chain, such as [Paseo](https://github.com/paseo-network/.github)
//! , Kusama or Pezkuwi.
//!
//! Note that these templates are mirrored automatically from [this](https://github.com/pezkuwichain/pezkuwi-sdk/blob/master/templates)
//! directory of pezkuwi-sdk, therefore any changes to them should be made as a PR to this repo.
//!
//! ## OpenZeppelin
//!
//! In June 2023, OpenZeppelin was awarded a grant from the Pezkuwi
//! treasury for building a number of Pezkuwi-sdk
//! based templates. These templates are a great starting point for developers and newcomers.
//! So far OpenZeppelin has released two templates, which have been fully [audited](https://github.com/pezkuwichain/polkadot-runtime-templates/tree/main/audits):
//! - [`generic-runtime-template`](https://github.com/pezkuwichain/polkadot-runtime-templates?tab=readme-ov-file#generic-runtime-template):
//! A minimal template that has all the common pallets that teyrchains use with secure defaults.
//! - [`evm-runtime-template`](https://github.com/pezkuwichain/polkadot-runtime-templates/tree/main?tab=readme-ov-file#evm-template):
//! This template has EVM compatibility out of the box and allows migrating your solidity contracts
//! or EVM compatible dapps easily. It also uses 20 byte addresses like Ethereum and has some
//! Account Abstraction support.
//!
//! ## POP-CLI
//!
//! Is a CLI tool capable of scaffolding a new pezkuwi-sdk-based project, possibly removing the
//! need for templates.
//!
//! - <https://pop.r0gue.io/cli/>
+70
View File
@@ -0,0 +1,70 @@
//! # XCM
//!
//! XCM, or Cross-Consensus Messaging, is a **language** to communicate **intentions** between
//! **consensus systems**.
//!
//! ## Overview
//!
//! XCM is a standard, specification of which lives in the [xcm format repo](https://github.com/paritytech/xcm-format).
//! It's agnostic both in programming language and blockchain platform, which means it could be used
//! in Rust in Pezkuwi, or in Go or C++ in any other platform like Cosmos or Ethereum.
//!
//! It enables different consensus systems to communicate with each other in an expressive manner.
//! Consensus systems include blockchains, smart contracts, and any other state machine that
//! achieves consensus in some way.
//!
//! XCM is executed on a virtual machine called the XCVM.
//! Scripts can be written with the XCM language, which are often called XCMs, messages or XCM
//! programs. Each program is a series of instructions, which get executed one after the other by
//! the virtual machine. These instructions aim to encompass all major things users typically do in
//! consensus systems. There are instructions on asset transferring, teleporting, locking, among
//! others. New instructions are added and changes to the XCVM are made via the [RFC process](https://github.com/paritytech/xcm-format/blob/master/proposals/0032-process.md).
//!
//! ## In Pezkuwi SDK
//!
//! The Pezkuwi SDK allows for easily deploying sovereign blockchains from scratch, all very
//! customizable. Dealing with many heterogeneous blockchains can be cumbersome.
//! XCM allows all these blockchains to communicate with an agreed-upon language.
//! As long as an implementation of the XCVM is implemented, the same XCM program can be executed in
//! all blockchains and perform the same task.
//!
//! ## Implementation
//!
//! A ready-to-use Rust implementation lives in the [pezkuwi-sdk repo](https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/xcm),
//! but will be moved to its own repo in the future.
//!
//! Its main components are:
//! - [`xcm`](::xcm): The definition of the basic types and instructions.
//! - [`xcm_executor`]: An implementation of the virtual machine to execute instructions.
//! - [`pallet_xcm`]: A FRAME pallet for interacting with the executor.
//! - [`xcm_builder`]: A collection of types to configure the executor.
//! - [`xcm_simulator`]: A playground for trying out different XCM programs and executor
//! configurations.
//!
//! ## Example
//!
//! To perform the very usual operation of transferring assets, the following XCM program can be
//! used:
#![doc = docify::embed!("src/pezkuwi_sdk/xcm.rs", example_transfer)]
//!
//! ## Get started
//!
//! To learn how it works and to get started, go to the [XCM docs](xcm_docs).
#[cfg(test)]
mod tests {
use xcm::latest::prelude::*;
#[docify::export]
#[test]
fn example_transfer() {
let _transfer_program = Xcm::<()>(vec![
WithdrawAsset((Here, 100u128).into()),
BuyExecution { fees: (Here, 100u128).into(), weight_limit: Unlimited },
DepositAsset {
assets: All.into(),
beneficiary: AccountId32 { id: [0u8; 32].into(), network: None }.into(),
},
]);
}
}
@@ -0,0 +1,29 @@
//! # State Transition Function
//!
//! This document briefly explains how in the context of Substrate-based blockchains, we view the
//! blockchain as a **decentralized state transition function**.
//!
//! Recall that a blockchain's main purpose is to help a permissionless set of entities to agree on
//! a shared data-set, and how it evolves. This is called the **State**, also referred to as
//! "onchain" data, or *Storage* in the context of FRAME. The state is where the account balance of
//! each user is, for example, stored, and there is a canonical version of it that everyone agrees
//! upon.
//!
//! Then, recall that a typical blockchain system will alter its state through execution of blocks.
//! *The component that dictates how this state alteration can happen is called the state transition
//! function*.
#![doc = simple_mermaid::mermaid!("../../../mermaid/stf_simple.mmd")]
//!
//! In Substrate-based blockchains, the state transition function is called the *Runtime*. This is
//! explained further in [`crate::reference_docs::wasm_meta_protocol`].
//!
//! With this in mind, we can paint a complete picture of a blockchain as a state machine:
#![doc = simple_mermaid::mermaid!("../../../mermaid/stf.mmd")]
//!
//! In essence, the state of the blockchain at block N is the outcome of applying the state
//! transition function to the previous state, and the current block as input. This can be
//! mathematically represented as:
//!
//! ```math
//! STF = F(State_N, Block_N) -> State_{N+1}
//! ```
@@ -0,0 +1,200 @@
//! # What is a chain specification
//!
//! A chain specification file defines the set of properties that are required to run the node as
//! part of the chain. The chain specification consists of two main parts:
//! - initial state of the runtime,
//! - network / logical properties of the chain, the most important property being the list of
//! bootnodes.
//!
//! This document describes how the initial state is handled in pallets and runtime, and how to
//! interact with the runtime in order to build the genesis state.
//!
//! For more information on chain specification and its properties, refer to
//! [`sc_chain_spec#from-initial-state-to-raw-genesis`].
//!
//! The initial genesis state can be provided in the following formats:
//! - full
//! - patch
//! - raw
//!
//! Each of the formats is explained in [_chain-spec-format_][`sc_chain_spec#chain-spec-formats`].
//!
//!
//! # `GenesisConfig` for `pallet`
//!
//! Every frame pallet may have its initial state which is defined by the `GenesisConfig` internal
//! struct. It is a regular Rust struct, annotated with the [`pallet::genesis_config`] attribute.
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_bar_GenesisConfig)]
//!
//! The struct shall be defined within the pallet `mod`, as in the following code:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_bar)]
//!
//! The initial state conveyed in the `GenesisConfig` struct is transformed into state storage
//! items by means of the [`BuildGenesisConfig`] trait, which shall be implemented for the pallet's
//! `GenesisConfig` struct. The [`pallet::genesis_build`] attribute shall be attached to the `impl`
//! block:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_bar_build)]
//!
//! `GenesisConfig` may also contain more complicated types, including nested structs or enums, as
//! in the example for `pallet_foo`:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_foo_GenesisConfig)]
//!
//! Note that [`serde`] attributes can be used to control how the data
//! structures are stored into JSON. In the following example, the [`sp_core::bytes`] function is
//! used to serialize the `values` field.
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", SomeFooData2)]
//!
//! Please note that fields of `GenesisConfig` may not be directly mapped to storage items. In the
//! following example, the initial struct fields are used to compute (sum) the value that will be
//! stored in the state as `ProcessedEnumValue`:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/pallets.rs", pallet_foo_build)]
//!
//! # `GenesisConfig` for `runtimes`
//!
//! The runtime genesis config struct consists of configs for every pallet. For the [_demonstration
//! runtime_][`chain_spec_guide_runtime`] used in this guide, it consists of `SystemConfig`,
//! `BarConfig`, and `FooConfig`. This structure was automatically generated by a macro and it can
//! be sneak-peeked here: [`RuntimeGenesisConfig`]. For further reading on generated runtime
//! types, refer to [`frame_runtime_types`].
//!
//! The macro automatically adds an attribute that renames all the fields to [`camelCase`]. It is a
//! good practice to add it to nested structures too, to have the naming of the JSON keys consistent
//! across the chain-spec file.
//!
//! ## `Default` for `GenesisConfig`
//!
//! `GenesisConfig` of all pallets must implement the `Default` trait. These are aggregated into
//! the runtime's `RuntimeGenesisConfig`'s `Default`.
//!
//! The default value of `RuntimeGenesisConfig` can be queried by the [`GenesisBuilder::get_preset`]
//! function provided by the runtime with `id:None`.
//!
//! A default value for `RuntimeGenesisConfig` usually is not operational. This is because for some
//! pallets it is not possible to define good defaults (e.g. an initial set of authorities).
//!
//! A default value is a base upon which a patch for `GenesisConfig` is applied. A good description
//! of how it exactly works is provided in [`get_storage_for_patch`] (and also in
//! [`GenesisBuilder::get_preset`]). A patch can be provided as an external file (manually created)
//! or as a built-in runtime preset. More info on presets is in the material to follow.
//!
//! ## Implementing `GenesisBuilder` for runtime
//!
//! The runtime exposes a dedicated runtime API for interacting with its genesis config:
//! [`sp_genesis_builder::GenesisBuilder`]. The implementation shall be provided within
//! the [`sp_api::impl_runtime_apis`] macro, typically making use of some helpers provided:
//! [`build_state`], [`get_preset`].
//! A typical implementation of [`sp_genesis_builder::GenesisBuilder`] looks as follows:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/runtime.rs", runtime_impl)]
//!
//! Please note that two functions are customized: `preset_names` and `get_preset`. The first one
//! just provides a `Vec` of the names of supported presets, while the latter delegates the call
//! to a function that maps the name to an actual preset:
//! [`chain_spec_guide_runtime::presets::get_builtin_preset`]
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", get_builtin_preset)]
//!
//! ## Genesis state presets for runtime
//!
//! The runtime may provide many flavors of initial genesis state. This may be useful for predefined
//! testing networks, local development, or CI integration tests. Predefined genesis state may
//! contain a list of pre-funded accounts, predefined authorities for consensus, sudo key, and many
//! others useful for testing.
//!
//! Internally, presets can be provided in a number of ways:
//! - using [`build_struct_json_patch`] macro (**recommended**):
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_2)]
//! - JSON using runtime types to serialize values:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_3)]
//! - JSON in string form:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_1)]
//!
//! It is worth noting that a preset does not have to be the full `RuntimeGenesisConfig`, in that
//! sense that it does not have to contain all the keys of the struct. The preset is actually a JSON
//! patch that will be merged with the default value of `RuntimeGenesisConfig`. This approach should
//! simplify maintenance of built-in presets. The following example illustrates a runtime genesis
//! config patch with a single key built using [`build_struct_json_patch`] macro:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_4)]
//! This results in the following JSON blob:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", preset_4_json)]
//!
//!
//! ## Note on the importance of testing presets
//!
//! It is recommended to always test presets by adding tests that convert the preset into the
//! raw storage. Converting to raw storage involves the deserialization of the provided JSON blob,
//! which enforces the verification of the preset. The following code shows one of the approaches
//! that can be taken for testing:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", check_presets)]
//!
//! ## Note on the importance of using the `deny_unknown_fields` attribute
//!
//! It is worth noting that when manually building preset JSON blobs it is easy to make a
//! hard-to-spot mistake, as in the following example ([`FooStruct`] does not contain `fieldC`):
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", preset_invalid)]
//! Even though `preset_invalid` contains a key that does not exist, the deserialization of the JSON
//! blob does not fail. The misspelling is silently ignored due to the lack of the
//! [`deny_unknown_fields`] attribute on the [`FooStruct`] struct, which is internally used in
//! `GenesisConfig`.
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/src/presets.rs", invalid_preset_works)]
//!
//! To avoid this problem [`build_struct_json_patch`] macro shall be used whenever possible (it
//! internally instantiates the struct before serializang it JSON blob, so all unknown fields shall
//! be caught at compilation time).
//!
//! ## Runtime `GenesisConfig` raw format
//!
//! A raw format of genesis config contains just the state's keys and values as they are stored in
//! the storage. This format is used to directly initialize the genesis storage. This format is
//! useful for long-term running chains, where the `GenesisConfig` structure for pallets may be
//! evolving over time. The JSON representation created at some point in time may no longer be
//! deserializable in the future, making a chain specification useless. The raw format is
//! recommended for production chains.
//!
//! For a detailed description of how the raw format is built, please refer to
//! [_chain-spec-raw-genesis_][`sc_chain_spec#from-initial-state-to-raw-genesis`]. Plain and
//! corresponding raw examples of chain-spec are given in
//! [_chain-spec-examples_][`sc_chain_spec#json-chain-specification-example`].
//! The [`chain_spec_builder`] util supports building the raw storage.
//!
//! # Interacting with the tool
//!
//! The [`chain_spec_builder`] util allows interaction with the runtime in order to list or display
//! 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
//! ```
//! Here are some examples in the form of rust tests:
//! ## Listing available preset names:
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_list_presets)]
//! ## Displaying preset with given name
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_get_preset)]
//! ## Building a solo chain-spec (the default) using given preset
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_generate_chain_spec)]
//! ## Building a teyrchain chain-spec using given preset
#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_generate_para_chain_spec)]
//!
//! [`RuntimeGenesisConfig`]:
//! chain_spec_guide_runtime::runtime::RuntimeGenesisConfig
//! [`FooStruct`]:
//! chain_spec_guide_runtime::pallets::FooStruct
//! [`impl_runtime_apis`]: frame::runtime::prelude::impl_runtime_apis
//! [`build_state`]: frame_support::genesis_builder_helper::build_state
//! [`get_preset`]: frame_support::genesis_builder_helper::get_preset
//! [`pallet::genesis_build`]: frame_support::pallet_macros::genesis_build
//! [`pallet::genesis_config`]: frame_support::pallet_macros::genesis_config
//! [`build_struct_json_patch`]: frame_support::build_struct_json_patch
//! [`BuildGenesisConfig`]: frame_support::traits::BuildGenesisConfig
//! [`serde`]: https://serde.rs/field-attrs.html
//! [`get_storage_for_patch`]: sc_chain_spec::GenesisConfigBuilderRuntimeCaller::get_storage_for_patch
//! [`GenesisBuilder::get_preset`]: sp_genesis_builder::GenesisBuilder::get_preset
//! [`deny_unknown_fields`]: https://serde.rs/container-attrs.html#deny_unknown_fields
//! [`camelCase`]: https://serde.rs/container-attrs.html#rename_all
@@ -0,0 +1,64 @@
[package]
name = "chain-spec-guide-runtime"
description = "A minimal runtime for chain spec guide"
version = "0.0.0"
license = "MIT-0"
authors.workspace = true
homepage.workspace = true
repository.workspace = true
edition.workspace = true
publish = false
[dependencies]
codec = { workspace = true }
docify = { workspace = true }
frame-support = { workspace = true }
scale-info = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
# this is a frame-based runtime, thus importing `frame` with runtime feature enabled.
frame = { features = ["experimental", "runtime"], workspace = true }
# genesis builder that allows us to interact with runtime genesis config
sp-application-crypto = { features = ["serde"], workspace = true }
sp-core = { workspace = true }
sp-genesis-builder = { workspace = true }
sp-keyring = { workspace = true }
sp-runtime = { features = ["serde"], workspace = true }
[dev-dependencies]
cmd_lib = { workspace = true }
sc-chain-spec = { workspace = true, default-features = true }
[build-dependencies]
substrate-wasm-builder = { optional = true, workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"frame-support/std",
"frame/std",
"sp-application-crypto/std",
"sp-core/std",
"sp-genesis-builder/std",
"sp-keyring/std",
"sp-runtime/std",
"serde/std",
"serde_json/std",
"substrate-wasm-builder",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame/runtime-benchmarks",
"sc-chain-spec/runtime-benchmarks",
"sp-genesis-builder/runtime-benchmarks",
"sp-keyring/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"substrate-wasm-builder?/runtime-benchmarks",
]
@@ -0,0 +1,23 @@
// 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::build_using_defaults();
}
}
@@ -0,0 +1,29 @@
// 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.
#![cfg_attr(not(feature = "std"), no_std)]
//! A minimal runtime that shows runtime genesis state.
extern crate alloc;
pub mod pallets;
pub mod presets;
pub mod runtime;
#[cfg(feature = "std")]
pub use runtime::{WASM_BINARY, WASM_BINARY_BLOATY};
@@ -0,0 +1,138 @@
// 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 for the chain-spec demo runtime.
use alloc::vec::Vec;
use frame::prelude::*;
#[docify::export]
#[frame::pallet(dev_mode)]
pub mod pallet_bar {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub(super) type InitialAccount<T: Config> = StorageValue<Value = T::AccountId>;
/// Simple `GenesisConfig`.
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
#[docify::export(pallet_bar_GenesisConfig)]
pub struct GenesisConfig<T: Config> {
pub initial_account: Option<T::AccountId>,
}
#[pallet::genesis_build]
#[docify::export(pallet_bar_build)]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
/// The storage building function that presents a direct mapping of the initial config
/// values to the storage items.
fn build(&self) {
InitialAccount::<T>::set(self.initial_account.clone());
}
}
}
/// The sample structure used in `GenesisConfig`.
///
/// This structure does not deny unknown fields. This may lead to some problems.
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FooStruct {
pub field_a: u8,
pub field_b: u8,
}
/// The sample structure used in `GenesisConfig`.
///
/// This structure does not deny unknown fields. This may lead to some problems.
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct SomeFooData1 {
pub a: u8,
pub b: u8,
}
/// Another sample structure used in `GenesisConfig`.
///
/// The user defined serialization is used.
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[docify::export]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct SomeFooData2 {
#[serde(default, with = "sp_core::bytes")]
pub values: Vec<u8>,
}
/// Sample enum used in `GenesisConfig`.
#[derive(Default, serde::Serialize, serde::Deserialize)]
pub enum FooEnum {
#[default]
Data0,
Data1(SomeFooData1),
Data2(SomeFooData2),
}
#[docify::export]
#[frame::pallet(dev_mode)]
pub mod pallet_foo {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type ProcessedEnumValue<T> = StorageValue<Value = u64>;
#[pallet::storage]
pub type SomeInteger<T> = StorageValue<Value = u32>;
/// The more sophisticated structure for conveying initial state.
#[docify::export(pallet_foo_GenesisConfig)]
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub some_integer: u32,
pub some_enum: FooEnum,
pub some_struct: FooStruct,
#[serde(skip)]
pub _phantom: PhantomData<T>,
}
#[pallet::genesis_build]
#[docify::export(pallet_foo_build)]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
/// The build method that indirectly maps an initial config values into the storage items.
fn build(&self) {
let processed_value: u64 = match &self.some_enum {
FooEnum::Data0 => 0,
FooEnum::Data1(v) => (v.a + v.b).into(),
FooEnum::Data2(v) => v.values.iter().map(|v| *v as u64).sum(),
};
ProcessedEnumValue::<T>::set(Some(processed_value));
SomeInteger::<T>::set(Some(self.some_integer));
}
}
}
@@ -0,0 +1,164 @@
// 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.
//! Presets for the chain-spec demo runtime.
use crate::{
pallets::{FooEnum, SomeFooData1, SomeFooData2},
runtime::{BarConfig, FooConfig, RuntimeGenesisConfig},
};
use alloc::vec;
use frame_support::build_struct_json_patch;
use serde_json::{json, to_string, Value};
use sp_application_crypto::Ss58Codec;
use sp_keyring::Sr25519Keyring;
/// A demo preset with strings only.
pub const PRESET_1: &str = "preset_1";
/// A demo preset with real types.
pub const PRESET_2: &str = "preset_2";
/// Another demo preset with real types and manually created json object.
pub const PRESET_3: &str = "preset_3";
/// A single value patch preset.
pub const PRESET_4: &str = "preset_4";
/// A single value patch preset.
pub const PRESET_INVALID: &str = "preset_invalid";
#[docify::export]
/// Function provides a preset demonstrating how use string representation of preset's internal
/// values.
fn preset_1() -> Value {
json!({
"bar": {
"initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
},
"foo": {
"someEnum": {
"Data2": {
"values": "0x0c0f"
}
},
"someStruct" : {
"fieldA": 10,
"fieldB": 20
},
"someInteger": 100
},
})
}
#[docify::export]
/// Function provides a preset demonstrating how to create a preset using
/// [`build_struct_json_patch`] macro.
fn preset_2() -> Value {
build_struct_json_patch!(RuntimeGenesisConfig {
foo: FooConfig {
some_integer: 200,
some_enum: FooEnum::Data2(SomeFooData2 { values: vec![0x0c, 0x10] })
},
bar: BarConfig { initial_account: Some(Sr25519Keyring::Ferdie.public().into()) },
})
}
#[docify::export]
/// Function provides a preset demonstrating how use the actual types to manually create a JSON
/// representing the preset.
fn preset_3() -> Value {
json!({
"bar": {
"initialAccount": Sr25519Keyring::Alice.public().to_ss58check(),
},
"foo": {
"someEnum": FooEnum::Data1(
SomeFooData1 {
a: 12,
b: 16
}
),
"someInteger": 300
},
})
}
#[docify::export]
/// Function provides a minimal preset demonstrating how to patch single key in
/// `RuntimeGenesisConfig` using [`build_struct_json_patch`] macro.
pub fn preset_4() -> Value {
build_struct_json_patch!(RuntimeGenesisConfig {
foo: FooConfig { some_enum: FooEnum::Data2(SomeFooData2 { values: vec![0x0c, 0x10] }) },
})
}
#[docify::export]
/// Function provides an invalid preset demonstrating how important is use of
/// `deny_unknown_fields` in data structures used in `GenesisConfig`.
fn preset_invalid() -> Value {
json!({
"foo": {
"someStruct": {
"fieldC": 5
},
},
})
}
/// Provides a JSON representation of preset identified by given `id`.
///
/// If no preset with given `id` exits `None` is returned.
#[docify::export]
pub fn get_builtin_preset(id: &sp_genesis_builder::PresetId) -> Option<alloc::vec::Vec<u8>> {
let preset = match id.as_ref() {
PRESET_1 => preset_1(),
PRESET_2 => preset_2(),
PRESET_3 => preset_3(),
PRESET_4 => preset_4(),
PRESET_INVALID => preset_invalid(),
_ => return None,
};
Some(
to_string(&preset)
.expect("serialization to json is expected to work. qed.")
.into_bytes(),
)
}
#[test]
#[docify::export]
fn check_presets() {
let builder = sc_chain_spec::GenesisConfigBuilderRuntimeCaller::<()>::new(
crate::WASM_BINARY.expect("wasm binary shall exists"),
);
assert!(builder.get_storage_for_named_preset(Some(&PRESET_1.to_string())).is_ok());
assert!(builder.get_storage_for_named_preset(Some(&PRESET_2.to_string())).is_ok());
assert!(builder.get_storage_for_named_preset(Some(&PRESET_3.to_string())).is_ok());
assert!(builder.get_storage_for_named_preset(Some(&PRESET_4.to_string())).is_ok());
}
#[test]
#[docify::export]
fn invalid_preset_works() {
let builder = sc_chain_spec::GenesisConfigBuilderRuntimeCaller::<()>::new(
crate::WASM_BINARY.expect("wasm binary shall exists"),
);
// Even though a preset contains invalid_key, conversion to raw storage does not fail. This is
// because the [`FooStruct`] structure is not annotated with `deny_unknown_fields` [`serde`]
// attribute.
// This may lead to hard to debug problems, that's why using ['deny_unknown_fields'] is
// recommended.
assert!(builder.get_storage_for_named_preset(Some(&PRESET_INVALID.to_string())).is_ok());
}
@@ -0,0 +1,127 @@
// 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.
//! A minimal runtime that shows runtime genesis state.
// Make the WASM binary available.
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
use crate::{
pallets::{pallet_bar, pallet_foo},
presets::*,
};
use alloc::{vec, vec::Vec};
use frame::{
deps::frame_support::{
genesis_builder_helper::{build_state, get_preset},
runtime,
},
prelude::*,
runtime::{apis, prelude::*},
};
use sp_genesis_builder::PresetId;
/// The runtime version.
#[runtime_version]
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: alloc::borrow::Cow::Borrowed("minimal-template-runtime"),
impl_name: alloc::borrow::Cow::Borrowed("minimal-template-runtime"),
authoring_version: 1,
spec_version: 0,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
system_version: 1,
};
/// The signed extensions that are added to the runtime.
type SignedExtra = ();
// Composes the runtime by adding all the used pallets and deriving necessary types.
#[runtime]
mod runtime {
/// The main runtime type.
#[runtime::runtime]
#[runtime::derive(
RuntimeCall,
RuntimeEvent,
RuntimeError,
RuntimeOrigin,
RuntimeTask,
RuntimeViewFunction
)]
pub struct Runtime;
/// Mandatory system pallet that should always be included in a FRAME runtime.
#[runtime::pallet_index(0)]
pub type System = frame_system;
/// Sample pallet 1
#[runtime::pallet_index(1)]
pub type Bar = pallet_bar;
/// Sample pallet 2
#[runtime::pallet_index(2)]
pub type Foo = pallet_foo;
}
parameter_types! {
pub const Version: RuntimeVersion = VERSION;
}
/// Implements the types required for the system pallet.
#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
type Version = Version;
}
impl pallet_bar::Config for Runtime {}
impl pallet_foo::Config for Runtime {}
type Block = frame::runtime::types_common::BlockOf<Runtime, SignedExtra>;
type Header = HeaderFor<Runtime>;
#[docify::export(runtime_impl)]
impl_runtime_apis! {
impl sp_genesis_builder::GenesisBuilder<Block> for Runtime {
fn build_state(config: Vec<u8>) -> sp_genesis_builder::Result {
build_state::<RuntimeGenesisConfig>(config)
}
fn get_preset(id: &Option<sp_genesis_builder::PresetId>) -> Option<Vec<u8>> {
get_preset::<RuntimeGenesisConfig>(id, get_builtin_preset)
}
fn preset_names() -> Vec<sp_genesis_builder::PresetId> {
vec![
PresetId::from(PRESET_1),
PresetId::from(PRESET_2),
PresetId::from(PRESET_3),
PresetId::from(PRESET_4),
PresetId::from(PRESET_INVALID)
]
}
}
impl apis::Core<Block> for Runtime {
fn version() -> RuntimeVersion { VERSION }
fn execute_block(_: <Block as frame::traits::Block>::LazyBlock) { }
fn initialize_block(_: &Header) -> ExtrinsicInclusionMode { ExtrinsicInclusionMode::default() }
}
}
@@ -0,0 +1,203 @@
use cmd_lib::*;
use serde_json::{json, Value};
use std::str;
fn wasm_file_path() -> &'static str {
chain_spec_guide_runtime::runtime::WASM_BINARY_PATH
.expect("chain_spec_guide_runtime wasm should exist. qed")
}
const CHAIN_SPEC_BUILDER_PATH: &str = "../../../../../target/release/chain-spec-builder";
macro_rules! bash(
( chain-spec-builder $($a:tt)* ) => {{
let path = get_chain_spec_builder_path();
spawn_with_output!(
$path $($a)*
)
.expect("a process running. qed")
.wait_with_output()
.expect("to get output. qed.")
}}
);
fn get_chain_spec_builder_path() -> &'static str {
run_cmd!(
cargo build --release -p staging-chain-spec-builder --bin chain-spec-builder
)
.expect("Failed to execute command");
CHAIN_SPEC_BUILDER_PATH
}
#[docify::export_content]
fn cmd_list_presets(runtime_path: &str) -> String {
bash!(
chain-spec-builder list-presets -r $runtime_path
)
}
#[test]
fn list_presets() {
let output: serde_json::Value =
serde_json::from_slice(cmd_list_presets(wasm_file_path()).as_bytes()).unwrap();
assert_eq!(
output,
json!({
"presets":[
"preset_1",
"preset_2",
"preset_3",
"preset_4",
"preset_invalid"
]
}),
"Output did not match expected"
);
}
#[docify::export_content]
fn cmd_get_preset(runtime_path: &str) -> String {
bash!(
chain-spec-builder display-preset -r $runtime_path -p preset_2
)
}
#[test]
fn get_preset() {
let output: serde_json::Value =
serde_json::from_slice(cmd_get_preset(wasm_file_path()).as_bytes()).unwrap();
assert_eq!(
output,
json!({
"bar": {
"initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
},
"foo": {
"someEnum": {
"Data2": {
"values": "0x0c10"
}
},
"someInteger": 200
},
}),
"Output did not match expected"
);
}
#[docify::export_content]
fn cmd_generate_chain_spec(runtime_path: &str) -> String {
bash!(
chain-spec-builder -c /dev/stdout create -r $runtime_path named-preset preset_2
)
}
#[test]
fn generate_chain_spec() {
let mut output: serde_json::Value =
serde_json::from_slice(cmd_generate_chain_spec(wasm_file_path()).as_bytes()).unwrap();
if let Some(code) = output["genesis"]["runtimeGenesis"].as_object_mut().unwrap().get_mut("code")
{
*code = Value::String("0x123".to_string());
}
assert_eq!(
output,
json!({
"name": "Custom",
"id": "custom",
"chainType": "Live",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" },
"codeSubstitutes": {},
"genesis": {
"runtimeGenesis": {
"code": "0x123",
"patch": {
"bar": {
"initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL"
},
"foo": {
"someEnum": {
"Data2": {
"values": "0x0c10"
}
},
"someInteger": 200
}
}
}
}
}),
"Output did not match expected"
);
}
#[docify::export_content]
fn cmd_generate_para_chain_spec(runtime_path: &str) -> String {
bash!(
chain-spec-builder -c /dev/stdout create -c pezkuwi -p 1000 -r $runtime_path named-preset preset_2
)
}
#[test]
fn generate_para_chain_spec() {
let mut output: serde_json::Value =
serde_json::from_slice(cmd_generate_para_chain_spec(wasm_file_path()).as_bytes()).unwrap();
if let Some(code) = output["genesis"]["runtimeGenesis"].as_object_mut().unwrap().get_mut("code")
{
*code = Value::String("0x123".to_string());
}
assert_eq!(
output,
json!({
"name": "Custom",
"id": "custom",
"chainType": "Live",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"relay_chain": "pezkuwi",
"para_id": 1000,
"properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" },
"codeSubstitutes": {},
"genesis": {
"runtimeGenesis": {
"code": "0x123",
"patch": {
"bar": {
"initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL"
},
"foo": {
"someEnum": {
"Data2": {
"values": "0x0c10"
}
},
"someInteger": 200
}
}
}
}}),
"Output did not match expected"
);
}
#[test]
#[docify::export_content]
fn preset_4_json() {
assert_eq!(
chain_spec_guide_runtime::presets::preset_4(),
json!({
"foo": {
"someEnum": {
"Data2": {
"values": "0x0c10"
}
},
},
})
);
}
+104
View File
@@ -0,0 +1,104 @@
//! # Substrate CLI
//!
//! Let's see some examples of typical CLI arguments used when setting up and running a
//! Substrate-based blockchain. We use the [`solochain-template`](https://github.com/pezkuwichain/pezkuwi-sdk/issues/25)
//! on these examples.
//!
//! #### Checking the available CLI arguments
//! ```bash
//! ./target/debug/node-template --help
//! ```
//! - `--help`: Displays the available CLI arguments.
//!
//! #### Starting a Local Substrate Node in Development Mode
//! ```bash
//! ./target/release/node-template \
//! --dev
//! ```
//! - `--dev`: Runs the node in development mode, using a pre-defined development chain
//! specification.
//! This mode ensures a fresh state by deleting existing data on restart.
//!
//! #### Generating Custom Chain Specification
//! ```bash
//! ./target/debug/node-template \
//! build-spec \
//! --disable-default-bootnode \
//! --chain local \
//! > customSpec.json
//! ```
//!
//! - `build-spec`: A subcommand to generate a chain specification file.
//! - `--disable-default-bootnode`: Disables the default bootnodes in the node template.
//! - `--chain local`: Indicates the chain specification is for a local development chain.
//! - `> customSpec.json`: Redirects the output into a customSpec.json file.
//!
//! #### Converting Chain Specification to Raw Format
//! ```bash
//! ./target/debug/node-template build-spec \
//! --chain=customSpec.json \
//! --raw \
//! --disable-default-bootnode \
//! > customSpecRaw.json
//! ```
//!
//! - `--chain=customSpec.json`: Uses the custom chain specification as input.
//! - `--disable-default-bootnode`: Disables the default bootnodes in the node template.
//! - `--raw`: Converts the chain specification into a raw format with encoded storage keys.
//! - `> customSpecRaw.json`: Outputs to `customSpecRaw.json`.
//!
//! #### Starting the First Node in a Private Network
//! ```bash
//! ./target/debug/node-template \
//! --base-path /tmp/node01 \
//! --chain ./customSpecRaw.json \
//! --port 30333 \
//! --ws-port 9945 \
//! --rpc-port 9933 \
//! --telemetry-url "wss://telemetry.pezkuwichain.io/submit/ 0" \
//! --validator \
//! --rpc-methods Unsafe \
//! --name MyNode01
//! ```
//!
//! - `--base-path`: Sets the directory for node data.
//! - `--chain`: Specifies the chain specification file.
//! - `--port`: TCP port for peer-to-peer communication.
//! - `--ws-port`: WebSocket port for RPC.
//! - `--rpc-port`: HTTP port for JSON-RPC.
//! - `--telemetry-url`: Endpoint for sending telemetry data.
//! - `--validator`: Indicates the nodes participation in block production.
//! - `--rpc-methods Unsafe`: Allows potentially unsafe RPC methods.
//! - `--name`: Sets a human-readable name for the node.
//!
//! #### Adding a Second Node to the Network
//! ```bash
//! ./target/release/node-template \
//! --base-path /tmp/bob \
//! --chain local \
//! --bob \
//! --port 30334 \
//! --rpc-port 9946 \
//! --telemetry-url "wss://telemetry.pezkuwichain.io/submit/ 0" \
//! --validator \
//! --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp
//! ```
//!
//! - `--base-path`: Sets the directory for node data.
//! - `--chain`: Specifies the chain specification file.
//! - `--bob`: Initializes the node with the session keys of the "Bob" account.
//! - `--port`: TCP port for peer-to-peer communication.
//! - `--rpc-port`: HTTP port for JSON-RPC.
//! - `--telemetry-url`: Endpoint for sending telemetry data.
//! - `--validator`: Indicates the nodes participation in block production.
//! - `--bootnodes`: Specifies the address of the first node for peer discovery. Nodes should find
//! each other using mDNS. This command needs to be used if they don't find each other.
//!
//! ---
//!
//! > If you are interested in learning how to extend the CLI with your custom arguments, you can
//! > check out the [Customize your Substrate chain CLI](https://www.youtube.com/watch?v=IVifko1fqjw)
//! > seminar.
//! > Please note that the seminar is based on an older version of Substrate, and [Clap](https://docs.rs/clap/latest/clap/)
//! > is now used instead of [StructOpt](https://docs.rs/structopt/latest/structopt/) for parsing
//! > CLI arguments.
@@ -0,0 +1,27 @@
//! # Custom Host Functions
//!
//! Host functions are functions that the wasm instance can use to communicate with the node. Learn
//! more about this in [`crate::reference_docs::wasm_meta_protocol`].
//!
//! ## Finding Host Functions
//!
//! To declare a set of functions as host functions, you need to use the `#[runtime_interface]`
//! ([`sp_runtime_interface`]) attribute macro. The most notable set of host functions are those
//! that allow the runtime to access the chain state, namely [`sp_io::storage`]. Some other notable
//! host functions are also defined in [`sp_io`].
//!
//! ## Adding New Host Functions
//!
//! > Adding a new host function is a big commitment and should be done with care. Namely, the nodes
//! > in the network need to support all host functions forever in order to be able to sync
//! > historical blocks.
//!
//! Adding host functions is only possible when you are using a node-template, so that you have
//! access to the boilerplate of building your node.
//!
//! A group of host functions can always be grouped to gether as a tuple:
#![doc = docify::embed!("../../substrate/primitives/io/src/lib.rs", SubstrateHostFunctions)]
//!
//! The host functions are attached to the node side's [`sc_executor::WasmExecutor`]. For example in
//! the minimal template, the setup looks as follows:
#![doc = docify::embed!("../../templates/minimal/node/src/service.rs", FullClient)]
@@ -0,0 +1,77 @@
//! # Custom RPC do's and don'ts
//!
//! **TLDR:** Don't create new custom RPCs. Instead, rely on custom Runtime APIs, combined with
//! `state_call`.
//!
//! ## Background
//!
//! Pezkuwi-SDK offers the ability to query and subscribe storages directly. However what it does
//! not have is [view functions](https://github.com/pezkuwichain/pezkuwi-sdk/issues/101). This is an
//! essential feature to avoid duplicated logic between runtime and the client SDK. Custom RPC was
//! used as a solution. It allow the RPC node to expose new RPCs that clients can be used to query
//! computed properties.
//!
//! ## Problems with Custom RPC
//!
//! Unfortunately, custom RPC comes with many problems. To list a few:
//!
//! - It is offchain logic executed by the RPC node and therefore the client has to trust the RPC
//! node.
//! - To upgrade or add a new RPC logic, the RPC node has to be upgraded. This can cause significant
//! trouble when the RPC infrastructure is decentralized as we will need to coordinate multiple
//! parties to upgrade the RPC nodes.
//! - A lot of boilerplate code is required to add custom RPC.
//! - It prevents dApps from using a light client or an alternative client.
//! - It makes ecosystem tooling integration much more complicated. For example, dApps will not
//! be able to use [Chopsticks](https://github.com/AcalaNetwork/chopsticks) for testing as
//! Chopsticks will not have the custom RPC implementation.
//! - Poorly implemented custom RPC can be a DoS vector.
//!
//! Hence, we should avoid custom RPC.
//!
//! ## Alternatives
//!
//! Generally, [`sc_rpc::state::StateBackend::call`] aka. `state_call` should be used instead of
//! custom RPC.
//!
//! Usually, each custom RPC comes with a corresponding runtime API which implements the business
//! logic. So instead of invoke the custom RPC, we can use `state_call` to invoke the runtime API
//! directly. This is a trivial change on the dApp and no change on the runtime side. We may remove
//! the custom RPC from the node side if wanted.
//!
//! There are some other cases that a simple runtime API is not enough. For example, implementation
//! of Ethereum RPC requires an additional offchain database to index transactions. In this
//! particular case, we can have the RPC implemented on another client.
//!
//! For example, the Acala EVM+ RPC are implemented by
//! [eth-rpc-adapter](https://github.com/AcalaNetwork/bodhi.js/tree/master/packages/eth-rpc-adapter).
//! Alternatively, the [Frontier](https://github.com/polkadot-evm/frontier) project also provided
//! Ethereum RPC compatibility directly in the node-side software.
//!
//! ## Create a new Runtime API
//!
//! For example, let's take a look at the process through which the account nonce can be queried
//! through an RPC. First, a new runtime-api needs to be declared:
#![doc = docify::embed!("../../substrate/frame/system/rpc/runtime-api/src/lib.rs", AccountNonceApi)]
//!
//! This API is implemented at the runtime level, always inside [`sp_api::impl_runtime_apis!`].
//!
//! As noted, this is already enough to make this API usable via `state_call`.
//!
//! ## Create a new custom RPC (Legacy)
//!
//! Should you wish to implement the legacy approach of exposing this runtime-api as a custom
//! RPC-api, then a custom RPC server has to be defined.
#![doc = docify::embed!("../../substrate/utils/frame/rpc/system/src/lib.rs", SystemApi)]
//!
//! ## Add a new RPC to the node (Legacy)
//!
//! Finally, this custom RPC needs to be integrated into the node side. This is usually done in a
//! `rpc.rs` in a typical template, as follows:
#![doc = docify::embed!("../../templates/minimal/node/src/rpc.rs", create_full)]
//!
//! ## Future
//!
//! - [XCQ](https://forum.polkadot.network/t/cross-consensus-query-language-xcq/7583) will be a good
//! solution for most of the query needs.
//! - [New JSON-RPC Specification](https://github.com/paritytech/json-rpc-interface-spec)
@@ -0,0 +1,395 @@
// 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.
//! [Defensive programming](https://en.wikipedia.org/wiki/Defensive_programming) is a design paradigm that enables a program to continue
//! running despite unexpected behavior, input, or events that may arise in runtime.
//! Usually, unforeseen circumstances may cause the program to stop or, in the Rust context,
//! `panic!`. Defensive practices allow for these circumstances to be accounted for ahead of time
//! and for them to be handled gracefully, which is in line with the intended fault-tolerant and
//! deterministic nature of blockchains.
//!
//! The Pezkuwi SDK is built to reflect these principles and to facilitate their usage accordingly.
//!
//! ## General Overview
//!
//! When developing within the context of the Substrate runtime, there is one golden rule:
//!
//! ***DO NOT PANIC***. There are some exceptions, but generally, this is the default precedent.
//!
//! > Its important to differentiate between the runtime and node. The runtime refers to the core
//! > business logic of a Substrate-based chain, whereas the node refers to the outer client, which
//! > deals with telemetry and gossip from other nodes. For more information, read about
//! > [Substrate's node
//! > architecture](crate::reference_docs::wasm_meta_protocol#node-vs-runtime). Its also important
//! > to note that the criticality of the node is slightly lesser
//! > than that of the runtime, which is why you may see `unwrap()` or other “non-defensive”
//! > approaches
//! in a few places of the node's code repository.
//!
//! Most of these practices fall within Rust's
//! colloquial usage of proper error propagation, handling, and arithmetic-based edge cases.
//!
//! General guidelines:
//!
//! - **Avoid writing functions that could explicitly panic,** such as directly using `unwrap()` on
//! a [`Result`], or accessing an out-of-bounds index on a collection. Safer methods to access
//! collection types, i.e., `get()` which allow defensive handling of the resulting [`Option`] are
//! recommended to be used.
//! - **It may be acceptable to use `except()`,** but only if one is completely certain (and has
//! performed a check beforehand) that a value won't panic upon unwrapping. *Even this is
//! discouraged*, however, as future changes to that function could then cause that statement to
//! panic. It is important to ensure all possible errors are propagated and handled effectively.
//! - **If a function *can* panic,** it usually is prefaced with `unchecked_` to indicate its
//! unsafety.
//! - **If you are writing a function that could panic,** [document it!](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#documenting-components)
//! - **Carefully handle mathematical operations.** Many seemingly, simplistic operations, such as
//! **arithmetic** in the runtime, could present a number of issues [(see more later in this
//! document)](#integer-overflow). Use checked arithmetic wherever possible.
//!
//! These guidelines could be summarized in the following example, where `bad_pop` is prone to
//! panicking, and `good_pop` allows for proper error handling to take place:
//!
//!```ignore
//! // Bad pop always requires that we return something, even if vector/array is empty.
//! fn bad_pop<T>(v: Vec<T>) -> T {}
//! // Good pop allows us to return None from the Option if need be.
//! fn good_pop<T>(v: Vec<T>) -> Option<T> {}
//! ```
//!
//! ### Defensive Traits
//!
//! The [`Defensive`](frame::traits::Defensive) trait provides a number of functions, all of which
//! provide an alternative to 'vanilla' Rust functions, e.g.:
//!
//! - [`defensive_unwrap_or()`](frame::traits::Defensive::defensive_unwrap_or) instead of
//! `unwrap_or()`
//! - [`defensive_ok_or()`](frame::traits::DefensiveOption::defensive_ok_or) instead of `ok_or()`
//!
//! Defensive methods use [`debug_assertions`](https://doc.rust-lang.org/reference/conditional-compilation.html#debug_assertions), which panic in development, but in
//! production/release, they will merely log an error (i.e., `log::error`).
//!
//! The [`Defensive`](frame::traits::Defensive) trait and its various implementations can be found
//! [here](frame::traits::Defensive).
//!
//! ## Integer Overflow
//!
//! The Rust compiler prevents static overflow from happening at compile time.
//! The compiler panics in **debug** mode in the event of an integer overflow. In
//! **release** mode, it resorts to silently _wrapping_ the overflowed amount in a modular fashion
//! (from the `MAX` back to zero).
//!
//! In runtime development, we don't always have control over what is being supplied
//! as a parameter. For example, even this simple add function could present one of two outcomes
//! depending on whether it is in **release** or **debug** mode:
//!
//! ```ignore
//! fn naive_add(x: u8, y: u8) -> u8 {
//! x + y
//! }
//! ```
//! If we passed overflow-able values at runtime, this could panic (or wrap if in release).
//!
//! ```ignore
//! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4.
//! ```
//!
//! It is the silent portion of this behavior that presents a real issue. Such behavior should be
//! made obvious, especially in blockchain development, where unsafe arithmetic could produce
//! unexpected consequences like a user balance over or underflowing.
//!
//! Fortunately, there are ways to both represent and handle these scenarios depending on our
//! specific use case natively built into Rust and libraries like [`sp_arithmetic`].
//!
//! ## Infallible Arithmetic
//!
//! Both Rust and Substrate provide safe ways to deal with numbers and alternatives to floating
//! point arithmetic.
//!
//! Known scenarios that could be fallible should be avoided: i.e., avoiding the possibility of
//! dividing/modulo by zero at any point should be mitigated. One should be opting for a
//! `checked_*` method to introduce safe arithmetic in their code in most cases.
//!
//! A developer should use fixed-point instead of floating-point arithmetic to mitigate the
//! potential for inaccuracy, rounding errors, or other unexpected behavior.
//!
//! - [Fixed point types](sp_arithmetic::fixed_point) and their associated usage can be found here.
//! - [PerThing](sp_arithmetic::per_things) and its associated types can be found here.
//!
//! Using floating point number types (i.e. f32, f64) in the runtime should be avoided, as a single non-deterministic result could cause chaos for blockchain consensus along with the issues above. For more on the specifics of the peculiarities of floating point calculations, [watch this video by the Computerphile](https://www.youtube.com/watch?v=PZRI1IfStY0).
//!
//! The following methods demonstrate different ways to handle numbers natively in Rust safely,
//! without fear of panic or unexpected behavior from wrapping.
//!
//! ### Checked Arithmetic
//!
//! **Checked operations** utilize an `Option<T>` as a return type. This allows for
//! catching any unexpected behavior in the event of an overflow through simple pattern matching.
//!
//! This is an example of a valid operation:
#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", checked_add_example)]
//!
//! This is an example of an invalid operation. In this case, a simulated integer overflow, which
//! would simply result in `None`:
#![doc = docify::embed!(
"./src/reference_docs/defensive_programming.rs",
checked_add_handle_error_example
)]
//!
//! Suppose you arent sure which operation to use for runtime math. In that case, checked
//! operations are the safest bet, presenting two predictable (and erroring) outcomes that can be
//! handled accordingly (Some and None).
//!
//! The following conventions can be seen within the Pezkuwi SDK, where it is
//! handled in two ways:
//!
//! - As an [`Option`], using the `if let` / `if` or `match`
//! - As a [`Result`], via `ok_or` (or similar conversion to [`Result`] from [`Option`])
//!
//! #### Handling via Option - More Verbose
//!
//! Because wrapped operations return `Option<T>`, you can use a more verbose/explicit form of error
//! handling via `if` or `if let`:
#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", increase_balance)]
//!
//! Optionally, match may also be directly used in a more concise manner:
#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", increase_balance_match)]
//!
//! This is generally a useful convention for handling checked types and most types that return
//! `Option<T>`.
//!
//! #### Handling via Result - Less Verbose
//!
//! In the Pezkuwi SDK codebase, checked operations are handled as a `Result` via `ok_or`. This is
//! a less verbose way of expressing the above. This usage often boils down to the developers
//! preference:
#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", increase_balance_result)]
//!
//! ### Saturating Operations
//!
//! Saturating a number limits it to the types upper or lower bound, even if the integer type
//! overflowed in runtime. For example, adding to `u32::MAX` would simply limit itself to
//! `u32::MAX`:
#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", saturated_add_example)]
//!
//! Saturating calculations can be used if one is very sure that something won't overflow, but wants
//! to avoid introducing the notion of any potential-panic or wrapping behavior.
//!
//! There is also a series of defensive alternatives via
//! [`DefensiveSaturating`](frame::traits::DefensiveSaturating), which introduces the same behavior
//! of the [`Defensive`](frame::traits::Defensive) trait, only with saturating, mathematical
//! operations:
#![doc = docify::embed!(
"./src/reference_docs/defensive_programming.rs",
saturated_defensive_example
)]
//!
//! ### Mathematical Operations in Substrate Development - Further Context
//!
//! As a recap, we covered the following concepts:
//!
//! 1. **Checked** operations - using [`Option`] or [`Result`]
//! 2. **Saturating** operations - limited to the lower and upper bounds of a number type
//! 3. **Wrapped** operations (the default) - wrap around to above or below the bounds of a type
//!
//! #### The problem with 'default' wrapped operations
//!
//! **Wrapped operations** cause the overflow to wrap around to either the maximum or minimum of
//! that type. Imagine this in the context of a blockchain, where there are account balances, voting
//! counters, nonces for transactions, and other aspects of a blockchain.
//!
//! While it may seem trivial, choosing how to handle numbers is quite important. As a thought
//! exercise, here are some scenarios of which will shed more light on when to use which.
//!
//! #### Bob's Overflowed Balance
//!
//! **Bob's** balance exceeds the `Balance` type on the `EduChain`. Because the pallet developer did
//! not handle the calculation to add to Bob's balance with any regard to this overflow, **Bob's**
//! balance is now essentially `0`, the operation **wrapped**.
//!
//! <details>
//! <summary><b>Solution: Saturating or Checked</b></summary>
//! For Bob's balance problems, using a `saturating_add` or `checked_add` could've mitigated
//! this issue. They simply would've reached the upper, or lower bounds, of the particular type for
//! an on-chain balance. In other words: Bob's balance would've stayed at the maximum of the
//! Balance type. </details>
//!
//! #### Alice's 'Underflowed' Balance
//!
//! Alices balance has reached `0` after a transfer to Bob. Suddenly, she has been slashed on
//! EduChain, causing her balance to reach near the limit of `u32::MAX` - a very large amount - as
//! wrapped operations can go both ways. Alice can now successfully vote using her new, overpowered
//! token balance, destroying the chain's integrity.
//!
//! <details>
//! <summary><b>Solution: Saturating</b></summary>
//! For Alice's balance problem, using `saturated_sub` could've mitigated this issue. A saturating
//! calculation would've simply limited her balance to the lower bound of u32, as having a negative
//! balance is not a concept within blockchains. In other words: Alice's balance would've stayed
//! at "0", even after being slashed.
//!
//! This is also an example that while one system may work in isolation, shared interfaces, such
//! as the notion of balances, are often shared across multiple pallets - meaning these small
//! changes can make a big difference depending on the scenario. </details>
//!
//! #### Proposal ID Overwrite
//!
//! A `u8` parameter, called `proposals_count`, represents the type for counting the number of
//! proposals on-chain. Every time a new proposal is added to the system, this number increases.
//! With the proposal pallet's high usage, it has reached `u8::MAX`s limit of 255, causing
//! `proposals_count` to go to 0. Unfortunately, this results in new proposals overwriting old ones,
//! effectively erasing any notion of past proposals!
//!
//! <details>
//! <summary><b>Solution: Checked</b></summary>
//! For the proposal IDs, proper handling via `checked` math would've been suitable,
//! Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to
//! ensure that the next proposal ID would've been valid would've been a viable way to let the user
//! know the state of their proposal:
//!
//! ```ignore
//! let next_proposal_id = current_count.checked_add(1).ok_or_else(|| Error::TooManyProposals)?;
//! ```
//!
//! </details>
//!
//! From the above, we can clearly see the problematic nature of seemingly simple operations in the
//! runtime, and care should be given to ensure a defensive approach is taken.
//!
//! ### Edge cases of `panic!`-able instances in Substrate
//!
//! As you traverse through the codebase (particularly in `substrate/frame`, where the majority of
//! runtime code lives), you may notice that there (only a few!) occurrences where `panic!` is used
//! explicitly. This is used when the runtime should stall, rather than keep running, as that is
//! considered safer. Particularly when it comes to mission-critical components, such as block
//! authoring, consensus, or other protocol-level dependencies, going through with an action may
//! actually cause harm to the network, and thus stalling would be the better option.
//!
//! Take the example of the BABE pallet ([`pallet_babe`]), which doesn't allow for a validator to
//! participate if it is disabled (see: [`frame::traits::DisabledValidators`]):
//!
//! ```ignore
//! if T::DisabledValidators::is_disabled(authority_index) {
//! panic!(
//! "Validator with index {:?} is disabled and should not be attempting to author blocks.",
//! authority_index,
//! );
//! }
//! ```
//!
//! There are other examples in various pallets, mostly those crucial to the blockchains
//! functionality. Most of the time, you will not be writing pallets which operate at this level,
//! but these exceptions should be noted regardless.
//!
//! ## Other Resources
//!
//! - [PBA Lectures on YouTube](https://www.youtube.com/playlist?list=PL-w_i5kwVqbni1Ch2j_RwTIXiB-bwnYqq)
#![allow(dead_code)]
#[allow(unused_variables)]
mod fake_runtime_types {
// Note: The following types are purely for the purpose of example, and do not contain any
// *real* use case other than demonstrating various concepts.
pub enum RuntimeError {
Overflow,
UserDoesntExist,
}
pub type Address = ();
pub struct Runtime;
impl Runtime {
fn get_balance(account: Address) -> Result<u64, RuntimeError> {
Ok(0u64)
}
fn set_balance(account: Address, new_balance: u64) {}
}
#[docify::export]
fn increase_balance(account: Address, amount: u64) -> Result<(), RuntimeError> {
// Get a user's current balance
let balance = Runtime::get_balance(account)?;
// SAFELY increase the balance by some amount
if let Some(new_balance) = balance.checked_add(amount) {
Runtime::set_balance(account, new_balance);
Ok(())
} else {
Err(RuntimeError::Overflow)
}
}
#[docify::export]
fn increase_balance_match(account: Address, amount: u64) -> Result<(), RuntimeError> {
// Get a user's current balance
let balance = Runtime::get_balance(account)?;
// SAFELY increase the balance by some amount
let new_balance = match balance.checked_add(amount) {
Some(balance) => balance,
None => {
return Err(RuntimeError::Overflow);
},
};
Runtime::set_balance(account, new_balance);
Ok(())
}
#[docify::export]
fn increase_balance_result(account: Address, amount: u64) -> Result<(), RuntimeError> {
// Get a user's current balance
let balance = Runtime::get_balance(account)?;
// SAFELY increase the balance by some amount - this time, by using `ok_or`
let new_balance = balance.checked_add(amount).ok_or(RuntimeError::Overflow)?;
Runtime::set_balance(account, new_balance);
Ok(())
}
}
#[cfg(test)]
mod tests {
use frame::traits::DefensiveSaturating;
#[docify::export]
#[test]
fn checked_add_example() {
// This is valid, as 20 is perfectly within the bounds of u32.
let add = (10u32).checked_add(10);
assert_eq!(add, Some(20))
}
#[docify::export]
#[test]
fn checked_add_handle_error_example() {
// This is invalid - we are adding something to the max of u32::MAX, which would overflow.
// Luckily, checked_add just marks this as None!
let add = u32::MAX.checked_add(10);
assert_eq!(add, None)
}
#[docify::export]
#[test]
fn saturated_add_example() {
// Saturating add simply saturates
// to the numeric bound of that type if it overflows.
let add = u32::MAX.saturating_add(10);
assert_eq!(add, u32::MAX)
}
#[docify::export]
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
fn saturated_defensive_example() {
let saturated_defensive = u32::MAX.defensive_saturating_add(10);
assert_eq!(saturated_defensive, u32::MAX);
}
}
@@ -0,0 +1,223 @@
//! # Development Environment Advice
//!
//! Large Rust projects are known for sometimes long compile times and sluggish dev tooling, and
//! pezkuwi-sdk is no exception.
//!
//! This page contains some advice to improve your workflow when using common tooling.
//!
//! ## Rust Analyzer Configuration
//!
//! [Rust Analyzer](https://rust-analyzer.github.io/) is the defacto [LSP](https://langserver.org/) for Rust. Its default
//! settings are fine for smaller projects, but not well optimised for pezkuwi-sdk.
//!
//! Below is a suggested configuration for VSCode or any VSCode-based editor like Cursor:
//!
//! ```json
//! {
//! // Use a separate target dir for Rust Analyzer. Helpful if you want to use Rust
//! // Analyzer and cargo on the command line at the same time,
//! // at the expense of duplicating build artifacts.
//! "rust-analyzer.cargo.targetDir": "target/vscode-rust-analyzer",
//! // Improve stability
//! "rust-analyzer.server.extraEnv": {
//! "CHALK_OVERFLOW_DEPTH": "100000000",
//! "CHALK_SOLVER_MAX_SIZE": "10000000"
//! },
//! // Check feature-gated code
//! "rust-analyzer.cargo.features": "all",
//! "rust-analyzer.cargo.extraEnv": {
//! // Skip building WASM, there is never need for it here
//! "SKIP_WASM_BUILD": "1"
//! },
//! // Don't expand some problematic proc_macros
//! "rust-analyzer.procMacro.ignored": {
//! "async-trait": ["async_trait"],
//! "napi-derive": ["napi"],
//! "async-recursion": ["async_recursion"],
//! "async-std": ["async_std"]
//! },
//! // Use nightly formatting.
//! // See the pezkuwi-sdk CI job that checks formatting for the current version used in
//! // pezkuwi-sdk.
//! "rust-analyzer.rustfmt.extraArgs": ["+nightly-2024-04-10"],
//! }
//! ```
//!
//! and the same in Lua for `neovim/nvim-lspconfig`:
//!
//! ```lua
//! ["rust-analyzer"] = {
//! rust = {
//! # Use a separate target dir for Rust Analyzer. Helpful if you want to use Rust
//! # Analyzer and cargo on the command line at the same time.
//! analyzerTargetDir = "target/nvim-rust-analyzer",
//! },
//! server = {
//! # Improve stability
//! extraEnv = {
//! ["CHALK_OVERFLOW_DEPTH"] = "100000000",
//! ["CHALK_SOLVER_MAX_SIZE"] = "100000000",
//! },
//! },
//! cargo = {
//! # Check feature-gated code
//! features = "all",
//! extraEnv = {
//! # Skip building WASM, there is never need for it here
//! ["SKIP_WASM_BUILD"] = "1",
//! },
//! },
//! procMacro = {
//! # Don't expand some problematic proc_macros
//! ignored = {
//! ["async-trait"] = { "async_trait" },
//! ["napi-derive"] = { "napi" },
//! ["async-recursion"] = { "async_recursion" },
//! ["async-std"] = { "async_std" },
//! },
//! },
//! rustfmt = {
//! # Use nightly formatting.
//! # See the pezkuwi-sdk CI job that checks formatting for the current version used in
//! # pezkuwi-sdk.
//! extraArgs = { "+nightly-2024-04-10" },
//! },
//! },
//! ```
//!
//! Alternatively for neovim, if you are using [Rustaceanvim](https://github.com/mrcjkb/rustaceanvim),
//! you can achieve the same configuring `rust-analyzer` via `rustaceanvim` as follows:
//! ```lua
//! return {
//! {
//! "mrcjkb/rustaceanvim",
//! opts = {
//! server = {
//! default_settings = {
//! ["rust-analyzer"] = {
//! // put the same config as for nvim-lspconfig here
//! },
//! },
//! },
//! },
//! },
//! }
//! ```
//!
//! Similarly for Zed, you can replicate the same VSCode configuration in
//! `~/.config/zed/settings.json` as follows:
//! ```json
//! "lsp": {
//! "rust-analyzer": {
//! "initialization_options": {
//! // same config as for VSCode for rust, cargo, procMacros, ...
//! }
//! }
//! },
//! ```
//!
//! In general, refer to your favorite editor / IDE's documentation to properly configure
//! `rust-analyzer` as language server.
//! For the full set of configuration options see <https://rust-analyzer.github.io/manual.html#configuration>.
//!
//! ## Cargo Usage
//!
//! ### Using `--package` (a.k.a. `-p`)
//!
//! pezkuwi-sdk is a monorepo containing many crates. When you run a cargo command without
//! `-p`, you will almost certainly compile crates outside of the scope you are working.
//!
//! Instead, you should identify the name of the crate you are working on by checking the `name`
//! field in the closest `Cargo.toml` file. Then, use `-p` with your cargo commands to only compile
//! that crate.
//!
//! ### `SKIP_WASM_BUILD=1` environment variable
//!
//! When cargo touches a runtime crate, by default it will also compile the WASM binary,
//! approximately doubling the compilation time.
//!
//! The WASM binary is usually not needed, especially when running `check` or `test`. To skip the
//! WASM build, set the `SKIP_WASM_BUILD` environment variable to `1`. For example:
//! `SKIP_WASM_BUILD=1 cargo check -p frame-support`.
//!
//! ### Cargo Remote
//!
//! Warning: cargo remote by default doesn't transfer hidden files to the remote machine. But hidden
//! files can be useful, e.g. for sqlx usage. On the other hand using `--transfer-hidden` flag will
//! transfer `.git` which is big.
//!
//! If you have a powerful remote server available, you may consider using
//! [cargo-remote](https://github.com/sgeisler/cargo-remote) to execute cargo commands on it,
//! freeing up local resources for other tasks like `rust-analyzer`.
//!
//! When using `cargo-remote`, you can configure your editor to perform the the typical
//! "check-on-save" remotely as well. The configuration for VSCode (or any VSCode-based editor like
//! Cursor) is as follows:
//!
//! ```json
//! {
//! "rust-analyzer.cargo.buildScripts.overrideCommand": [
//! "cargo",
//! "remote",
//! "--build-env",
//! "SKIP_WASM_BUILD=1",
//! "--",
//! "check",
//! "--message-format=json",
//! "--all-targets",
//! "--all-features",
//! "--target-dir=target/rust-analyzer"
//! ],
//! "rust-analyzer.check.overrideCommand": [
//! "cargo",
//! "remote",
//! "--build-env",
//! "SKIP_WASM_BUILD=1",
//! "--",
//! "check",
//! "--workspace",
//! "--message-format=json",
//! "--all-targets",
//! "--all-features",
//! "--target-dir=target/rust-analyzer"
//! ],
//! }
//! ```
//!
//! and the same in Lua for `neovim/nvim-lspconfig`:
//!
//! ```lua
//! ["rust-analyzer"] = {
//! cargo = {
//! buildScripts = {
//! overrideCommand = {
//! "cargo",
//! "remote",
//! "--build-env",
//! "SKIP_WASM_BUILD=1",
//! "--",
//! "check",
//! "--message-format=json",
//! "--all-targets",
//! "--all-features",
//! "--target-dir=target/rust-analyzer"
//! },
//! },
//! },
//! check = {
//! overrideCommand = {
//! "cargo",
//! "remote",
//! "--build-env",
//! "SKIP_WASM_BUILD=1",
//! "--",
//! "check",
//! "--workspace",
//! "--message-format=json",
//! "--all-targets",
//! "--all-features",
//! "--target-dir=target/rust-analyzer"
//! },
//! },
//! },
//! ```
@@ -0,0 +1,333 @@
//! # Constructing and Signing Extrinsics
//!
//! Extrinsics are payloads that are stored in blocks which are responsible for altering the state
//! of a blockchain via the [_state transition
//! function_][crate::reference_docs::blockchain_state_machines].
//!
//! Substrate is configurable enough that extrinsics can take any format. In practice, runtimes
//! tend to use our [`sp_runtime::generic::UncheckedExtrinsic`] type to represent extrinsics,
//! because it's generic enough to cater for most (if not all) use cases. In Pezkuwi, this is
//! configured [here](https://github.com/polkadot-fellows/runtimes/blob/94b2798b69ba6779764e20a50f056e48db78ebef/relay/polkadot/src/lib.rs#L1478)
//! at the time of writing.
//!
//! What follows is a description of how extrinsics based on this
//! [`sp_runtime::generic::UncheckedExtrinsic`] type are encoded into bytes. Specifically, we are
//! looking at how extrinsics with a format version of 5 are encoded. This version is itself a part
//! of the payload, and if it changes, it indicates that something about the encoding may have
//! changed.
//!
//! # Encoding an Extrinsic
//!
//! At a high level, all extrinsics compatible with [`sp_runtime::generic::UncheckedExtrinsic`]
//! are formed from concatenating some details together, as in the following pseudo-code:
//!
//! ```text
//! extrinsic_bytes = concat(
//! compact_encoded_length,
//! version_and_extrinsic_type,
//! maybe_extension_data,
//! call_data
//! )
//! ```
//!
//! For clarity, the actual implementation in Substrate looks like this:
#![doc = docify::embed!("../../substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs", unchecked_extrinsic_encode_impl)]
//!
//! Let's look at how each of these details is constructed:
//!
//! ## compact_encoded_length
//!
//! This is a [SCALE compact encoded][frame::deps::codec::Compact] integer which is equal to the
//! length, in bytes, of the rest of the extrinsic details.
//!
//! To obtain this value, we must encode and concatenate together the rest of the extrinsic details
//! first, and then obtain the byte length of these. We can then compact encode that length, and
//! prepend it to the rest of the details.
//!
//! ## version_and_maybe_signature
//!
//! If the extrinsic is _unsigned_, then `version_and_maybe_signature` will be just one byte
//! denoting the _transaction protocol version_, which is 4 (or `0b0000_0100`).
//!
//! If the extrinsic is _signed_ (all extrinsics submitted from users must be signed), then
//! `version_and_maybe_signature` is obtained by concatenating some details together, ie:
//!
//! ```text
//! version_and_maybe_signature = concat(
//! version_and_signed,
//! from_address,
//! signature,
//! transaction_extensions_extra,
//! )
//! ```
//!
//! Each of the details to be concatenated together is explained below:
//!
//! ## version_and_extrinsic_type
//!
//! This byte has 2 components:
//! - the 2 most significant bits represent the extrinsic type:
//! - bare - `0b00`
//! - signed - `0b10`
//! - general - `0b01`
//! - the 6 least significant bits represent the extrinsic format version (currently 5)
//!
//! ### Bare extrinsics
//!
//! If the extrinsic is _bare_, then `version_and_extrinsic_type` will be just the _transaction
//! protocol version_, which is 5 (or `0b0000_0101`). Bare extrinsics do not carry any other
//! extension data, so `maybe_extension_data` would not be included in the payload and the
//! `version_and_extrinsic_type` would always be followed by the encoded call bytes.
//!
//! ### Signed extrinsics
//!
//! If the extrinsic is _signed_ (all extrinsics submitted from users used to be signed up until
//! version 4), then `version_and_extrinsic_type` is obtained by having a MSB of `1` on the
//! _transaction protocol version_ byte (which translates to `0b1000_0101`).
//!
//! Additionally, _signed_ extrinsics also carry with them address and signature information encoded
//! as follows:
//!
//! #### from_address
//!
//! This is the [SCALE encoded][frame::deps::codec] address of the sender of the extrinsic. The
//! address is the first generic parameter of [`sp_runtime::generic::UncheckedExtrinsic`], and so
//! can vary from chain to chain.
//!
//! The address type used on the Pezkuwi relay chain is [`sp_runtime::MultiAddress<AccountId32>`],
//! where `AccountId32` is defined [here][`sp_core::crypto::AccountId32`]. When constructing a
//! signed extrinsic to be submitted to a Pezkuwi node, you'll always use the
//! [`sp_runtime::MultiAddress::Id`] variant to wrap your `AccountId32`.
//!
//! #### signature
//!
//! This is the [SCALE encoded][frame::deps::codec] signature. The signature type is configured via
//! the third generic parameter of [`sp_runtime::generic::UncheckedExtrinsic`], which determines the
//! shape of the signature and signing algorithm that should be used.
//!
//! The signature is obtained by signing the _signed payload_ bytes (see below on how this is
//! constructed) using the private key associated with the address and correct algorithm.
//!
//! The signature type used on the Pezkuwi relay chain is [`sp_runtime::MultiSignature`]; the
//! variants there are the types of signature that can be provided.
//!
//! ### General extrinsics
//!
//! If the extrinsic is _general_ (it doesn't carry a signature in the payload, only extension
//! data), then `version_and_extrinsic_type` is obtained by logical OR between the general
//! transaction type bits and the _transaction protocol version_ byte (which translates to
//! `0b0100_0101`).
//!
//! ### transaction_extensions_extra
//!
//! This is the concatenation of the [SCALE encoded][frame::deps::codec] bytes representing first a
//! single byte describing the extension version (this is bumped whenever a change occurs in the
//! transaction extension pipeline) followed by the bytes of each of the [_transaction
//! extensions_][sp_runtime::traits::TransactionExtension], and are configured by the fourth generic
//! parameter of [`sp_runtime::generic::UncheckedExtrinsic`]. Learn more about transaction
//! extensions [here][crate::reference_docs::transaction_extensions].
//!
//! When it comes to constructing an extrinsic, each transaction extension has two things that we
//! are interested in here:
//!
//! - The actual SCALE encoding of the transaction extension type itself; this is what will form our
//! `transaction_extensions_extra` bytes.
//! - An `Implicit` type. This is SCALE encoded into the `transaction_extensions_implicit` data (see
//! below).
//!
//! Either (or both) of these can encode to zero bytes.
//!
//! Each chain configures the set of transaction extensions that it uses in its runtime
//! configuration. At the time of writing, Pezkuwi configures them
//! [here](https://github.com/polkadot-fellows/runtimes/blob/1dc04eb954eadf8aadb5d83990b89662dbb5a074/relay/polkadot/src/lib.rs#L1432C25-L1432C25).
//! Some of the common transaction extensions are defined
//! [here][frame::deps::frame_system#transaction-extensions].
//!
//! Information about exactly which transaction extensions are present on a chain and in what order
//! is also a part of the metadata for the chain. For V15 metadata, it can be [found
//! here][frame::deps::frame_support::__private::metadata::v15::ExtrinsicMetadata].
//!
//! ## call_data
//!
//! This is the main payload of the extrinsic, which is used to determine how the chain's state is
//! altered. This is defined by the second generic parameter of
//! [`sp_runtime::generic::UncheckedExtrinsic`].
//!
//! A call can be anything that implements [`Encode`][frame::deps::codec::Encode]. In FRAME-based
//! runtimes, a call is represented as an enum of enums, where the outer enum represents the FRAME
//! pallet being called, and the inner enum represents the call being made within that pallet, and
//! any arguments to it. Read more about the call enum
//! [here][crate::reference_docs::frame_runtime_types].
//!
//! FRAME `Call` enums are automatically generated, and end up looking something like this:
#![doc = docify::embed!("./src/reference_docs/extrinsic_encoding.rs", call_data)]
//!
//! In pseudo-code, this `Call` enum encodes equivalently to:
//!
//! ```text
//! call_data = concat(
//! pallet_index,
//! call_index,
//! call_args
//! )
//! ```
//!
//! - `pallet_index` is a single byte denoting the index of the pallet that we are calling into, and
//! is what the tag of the outermost enum will encode to.
//! - `call_index` is a single byte denoting the index of the call that we are making the pallet,
//! and is what the tag of the inner enum will encode to.
//! - `call_args` are the SCALE encoded bytes for each of the arguments that the call expects, and
//! are typically provided as values to the inner enum.
//!
//! Information about the pallets that exist for a chain (including their indexes), the calls
//! available in each pallet (including their indexes), and the arguments required for each call can
//! be found in the metadata for the chain. For V15 metadata, this information [is
//! here][frame::deps::frame_support::__private::metadata::v15::PalletMetadata].
//!
//! # The Signed Payload Format
//!
//! All _signed_ extrinsics submitted to a node from the outside world (also known as
//! _transactions_) need to be _signed_. The data that needs to be signed for some extrinsic is
//! called the _signed payload_, and its shape is described by the following pseudo-code:
//!
//! ```text
//! signed_payload = blake2_256(
//! concat(
//! call_data,
//! transaction_extensions_extra,
//! transaction_extensions_implicit,
//! )
//! )
//! ```
//!
//! The bytes representing `call_data` and `transaction_extensions_extra` can be obtained as
//! descibed above. `transaction_extensions_implicit` is constructed by SCALE encoding the
//! ["implicit" data][sp_runtime::traits::TransactionExtension::Implicit] for each transaction
//! extension that the chain is using, in order.
//!
//! Once we've concatenated those together, we hash the result using a Blake2 256bit hasher.
//!
//! The [`sp_runtime::generic::SignedPayload`] type takes care of assembling the correct payload for
//! us, given `call_data` and a tuple of transaction extensions.
//!
//! # The General Transaction Format
//!
//! A General transaction does not have a signature method hardcoded in the check logic of the
//! extrinsic, such as a traditionally signed transaction. Instead, general transactions should have
//! one or more extensions in the transaction extension pipeline that auhtorize origins in some way,
//! one of which could be the traditional signature check that happens for all signed transactions
//! in the [Checkable](sp_runtime::traits::Checkable) implementation of
//! [UncheckedExtrinsic](sp_runtime::generic::UncheckedExtrinsic). Therefore, it is up to each
//! extension to define the format of the payload it will try to check and authorize the right
//! origin type. For an example, look into the [authorization example pallet
//! extensions](pallet_example_authorization_tx_extension::extensions)
//!
//! # Example Encoding
//!
//! Using [`sp_runtime::generic::UncheckedExtrinsic`], we can construct and encode an extrinsic as
//! follows:
#![doc = docify::embed!("./src/reference_docs/extrinsic_encoding.rs", encoding_example)]
#[docify::export]
pub mod call_data {
use codec::{Decode, Encode};
use sp_runtime::{traits::Dispatchable, DispatchResultWithInfo};
// The outer enum composes calls within
// different pallets together. We have two
// pallets, "PalletA" and "PalletB".
#[derive(Encode, Decode, Clone)]
pub enum Call {
#[codec(index = 0)]
PalletA(PalletACall),
#[codec(index = 7)]
PalletB(PalletBCall),
}
// An inner enum represents the calls within
// a specific pallet. "PalletA" has one call,
// "Foo".
#[derive(Encode, Decode, Clone)]
pub enum PalletACall {
#[codec(index = 0)]
Foo(String),
}
#[derive(Encode, Decode, Clone)]
pub enum PalletBCall {
#[codec(index = 0)]
Bar(String),
}
impl Dispatchable for Call {
type RuntimeOrigin = ();
type Config = ();
type Info = ();
type PostInfo = ();
fn dispatch(self, _origin: Self::RuntimeOrigin) -> DispatchResultWithInfo<Self::PostInfo> {
Ok(())
}
}
}
#[docify::export]
pub mod encoding_example {
use super::call_data::{Call, PalletACall};
use crate::reference_docs::transaction_extensions::transaction_extensions_example;
use codec::Encode;
use sp_core::crypto::AccountId32;
use sp_keyring::sr25519::Keyring;
use sp_runtime::{
generic::{SignedPayload, UncheckedExtrinsic},
MultiAddress, MultiSignature,
};
// Define some transaction extensions to use. We'll use a couple of examples
// from the transaction extensions reference doc.
type TransactionExtensions = (
transaction_extensions_example::AddToPayload,
transaction_extensions_example::AddToSignaturePayload,
);
// We'll use `UncheckedExtrinsic` to encode our extrinsic for us. We set
// the address and signature type to those used on Pezkuwi, use our custom
// `Call` type, and use our custom set of `TransactionExtensions`.
type Extrinsic = UncheckedExtrinsic<
MultiAddress<AccountId32, ()>,
Call,
MultiSignature,
TransactionExtensions,
>;
pub fn encode_demo_extrinsic() -> Vec<u8> {
// The "from" address will be our Alice dev account.
let from_address = MultiAddress::<AccountId32, ()>::Id(Keyring::Alice.to_account_id());
// We provide some values for our expected transaction extensions.
let transaction_extensions = (
transaction_extensions_example::AddToPayload(1),
transaction_extensions_example::AddToSignaturePayload,
);
// Construct our call data:
let call_data = Call::PalletA(PalletACall::Foo("Hello".to_string()));
// The signed payload. This takes care of encoding the call_data,
// transaction_extensions_extra and transaction_extensions_implicit, and hashing
// the result if it's > 256 bytes:
let signed_payload = SignedPayload::new(call_data.clone(), transaction_extensions.clone());
// Sign the signed payload with our Alice dev account's private key,
// and wrap the signature into the expected type:
let signature = {
let sig = Keyring::Alice.sign(&signed_payload.encode());
MultiSignature::Sr25519(sig)
};
// Now, we can build and encode our extrinsic:
let ext = Extrinsic::new_signed(call_data, from_address, signature, transaction_extensions);
let encoded_ext = ext.encode();
encoded_ext
}
}
@@ -0,0 +1,13 @@
//! # Fee-Less Runtime
//!
//! 🚧 Work In Progress 🚧
//!
//! Notes:
//!
//! - An extension of [`runtime_vs_smart_contract`], showcasing the tools needed to build a safe
//! runtime that is fee-less.
//! - Would need to use unsigned origins, custom validate_unsigned, check the existence of some NFT
//! and some kind of rate limiting (eg. any account gets 5 free tx per day).
//! - The rule of thumb is that as long as the unsigned validate does one storage read, similar to
//! nonce, it is fine.
//! - This could possibly be a good guide/template, rather than a reference doc.
@@ -0,0 +1,212 @@
//! # FRAME Benchmarking and Weights.
//!
//! This reference doc explores the concept of weights within Pezkuwi-SDK runtimes, and more
//! specifically how FRAME-based runtimes handle it.
//!
//! ## Metering
//!
//! The existence of "weight" as a concept in Pezkuwi-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.
//!
//! In Pezkuwi-SDK, the WASM runtime is not assumed to be metered.
//!
//! ## 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 Pezkuwi-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 `pezkuwi-sdk`, or the pallets in our
//! [`crate::pezkuwi_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 teyrchain <> 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 Pezkuwi-SDK based nodes (such as the ones integrated in
//! [`templates`]) have 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 teyrchains. Please see:
//! [`crate::guides::enable_pov_reclaim`].
//!
//! ## Summary
//!
//! Pezkuwi-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 Pezkuwi 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
//! Pezkuwi-SDK, rendering them not needed anymore once PolkaVM is fully integrated into
//! Pezkuwi-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::pezkuwi_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<T>(_);
#[pallet::call]
impl<T: Config> Pallet<T> {
#[docify::export]
#[pallet::weight(10_000)]
pub fn simple_transfer(
origin: OriginFor<T>,
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<T>,
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<T>,
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://docs.pezkuwichain.io/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,
})
}
}
}
}
+155
View File
@@ -0,0 +1,155 @@
//! # FRAME Logging
//!
//! This reference docs briefly explores how to do logging and printing runtimes, mainly
//! FRAME-based.
//!
//! > Please make sure to read [the section below](#using-logging-in-production) on using logging in
//! > production.
//!
//! ## Using `println!`
//!
//! To recap, as with standard Rust, you can use `println!` _in your tests_, but it will only print
//! out if executed with `--nocapture`, or if the test panics.
//!
//! ```
//! fn it_print() {
//! println!("Hello, world!");
//! }
//! ```
//!
//! within the pallet, if you want to use the standard `println!`, it needs to be wrapped in
//! [`sp_std::if_std`]. Of course, this means that this print code is only available to you in the
//! `std` compiler flag, and never present in a wasm build.
//!
//! ```
//! // somewhere in your pallet. This is not a real pallet code.
//! mod pallet {
//! struct Pallet;
//! impl Pallet {
//! fn print() {
//! sp_std::if_std! {
//! println!("Hello, world!");
//! }
//! }
//! }
//! }
//! ```
//!
//! ## Using `log`
//!
//! First, ensure you are familiar with the [`log`] crate. In short, each log statement has:
//!
//! 1. `log-level`, signifying how important it is.
//! 2. `log-target`, signifying to which component it belongs.
//!
//! Add log statements to your pallet as such:
//!
//! You can add the log crate to the `Cargo.toml` of the pallet.
//!
//! ```text
//! #[dependencies]
//! log = { version = "x.y.z", default-features = false }
//!
//! #[features]
//! std = [
//! // snip -- other pallets
//! "log/std"
//! ]
//! ```
//!
//! More conveniently, the `frame` umbrella crate re-exports the log crate as [`frame::log`].
//!
//! Then, the pallet can use this crate to emit log statements. In this statement, we use the info
//! level, and the target is `pallet-example`.
//!
//! ```
//! mod pallet {
//! struct Pallet;
//!
//! impl Pallet {
//! fn logs() {
//! frame::log::info!(target: "pallet-example", "Hello, world!");
//! }
//! }
//! }
//! ```
//!
//! This will in itself just emit the log messages, **but unless if captured by a logger, they will
//! not go anywhere**. [`sp_api`] provides a handy function to enable the runtime logging:
//!
//! ```
//! // in your test
//! fn it_also_prints() {
//! sp_api::init_runtime_logger();
//! // call into your pallet, and now it will print `log` statements.
//! }
//! ```
//!
//! Alternatively, you can use [`sp_tracing::try_init_simple`].
//!
//! `info`, `error` and `warn` logs are printed by default, but if you want lower level logs to also
//! be printed, you must to add the following compiler flag:
//!
//! ```text
//! RUST_LOG=pallet-example=trace cargo test
//! ```
//!
//! ## Enabling Logs in Production
//!
//! All logs from the runtime are emitted by default, but there is a feature flag in [`sp_api`],
//! called `disable-logging`, that can be used to disable all logs in the runtime. This is useful
//! for production chains to reduce the size and overhead of the wasm runtime.
#![doc = docify::embed!("../../substrate/primitives/api/src/lib.rs", init_runtime_logger)]
//!
//! Similar to the above, the proper `RUST_LOG` must also be passed to your compiler flag when
//! compiling the runtime.
//!
//! ## Log Target Prefixing
//!
//! Many [`crate::pezkuwi_sdk::frame_runtime`] pallets emit logs with log target `runtime::<name of
//! pallet>`, for example `runtime::system`. This then allows one to run a node with a wasm blob
//! compiled with `LOG_TARGET=runtime=debug`, which enables the log target of all pallets who's log
//! target starts with `runtime`.
//!
//! ## Low Level Primitives
//!
//! Under the hood, logging is another instance of host functions under the hood (as defined in
//! [`crate::reference_docs::wasm_meta_protocol`]). The runtime uses a set of host functions under
//! [`sp_io::logging`] and [`sp_io::misc`] to emit all logs and prints. You typically do not need to
//! use these APIs directly.
//!
//! ## Using Logging in Production
//!
//! Note that within FRAME, reading storage values __only for the purpose of logging__ is dangerous,
//! and can lead to consensus issues. This is because with the introduction of
//! [`crate::guides::enable_pov_reclaim`], the node side code will track the storage changes, and
//! tries to update the onchain record of the `proof_size` weight used (stored in
//! [`frame_system::BlockWeight`]) after the block is executed.
//!
//! If one node has a different log level enabled than the rest of the network, and the extra logs
//! impose additional storage reads, then the amount of `proof_size` weight reclaimed into
//! [`frame_system::BlockWeight`] will be different, causing a state root mismatch, which is
//! typically a fatal error emitted from [`frame_executive`].
//!
//! This also can also happen in a teyrchain context, and cause discrepancies between the relay
//! chain and the teyrchain, when execution the Teyrchain Validation Function (PVF) on the relay
//! chain.
//!
//! **In summary, you should only used storage values in logging (especially for levels lower than
//! `info` which is typically enabled by all parties) that are already read from storage, and will
//! be part of the storage proof of execution in any case**.
//!
//! A typical faulty code would look like this:
//!
//! ```ignore
//! /// This function will have a different storage footprint depending on the log level
//! fn faulty_logging() {
//! log::debug!(
//! "what I am about to print is only read when `RUST_LOG=debug` {:?}",
//! StorageValue::<T>::get()
//! );
//! }
//! ```
//!
//! Please read [this issue](https://github.com/pezkuwichain/pezkuwi-sdk/issues/155) for one
//! instance of the consensus issues caused by this mistake.
@@ -0,0 +1,114 @@
//! # Offchain Workers
//!
//! This reference document explains how offchain workers work in Substrate and FRAME. The main
//! focus is upon FRAME's implementation of this functionality. Nonetheless, offchain workers are a
//! Substrate-provided feature and can be used with possible alternatives to [`frame`] as well.
//!
//! Offchain workers are a commonly misunderstood topic, therefore we explain them bottom-up,
//! starting at the fundamentals and then describing the developer interface.
//!
//! ## Context
//!
//! Recall from [`crate::reference_docs::wasm_meta_protocol`] that the node and the runtime
//! communicate with one another via host functions and runtime APIs. Many of these interactions
//! contribute to the actual state transition of the blockchain. For example [`sp_api::Core`] is the
//! main runtime API that is called to execute new blocks.
//!
//! Offchain workers are in principle not different in any way: It is a runtime API exposed by the
//! wasm blob ([`sp_offchain::OffchainWorkerApi`]), and the node software calls into it when it
//! deems fit. But, crucially, this API call is different in that:
//!
//! 1. It can have no impact on the state ie. it is _OFF (the) CHAIN_. If any state is altered
//! during the execution of this API call, it is discarded.
//! 2. It has access to an extended set of host functions that allow the wasm blob to do more. For
//! example, call into HTTP requests.
//!
//! > The main way through which an offchain worker can interact with the state is by submitting an
//! > extrinsic to the chain. This is the ONLY way to alter the state from an offchain worker.
//! > [`pallet_example_offchain_worker`] provides an example of this.
//!
//!
//! Given the "Off Chain" nature of this API, it is important to remember that calling this API is
//! entirely optional. Some nodes might call into it, some might not, and it would have no impact on
//! the execution of your blockchain because no state is altered no matter the execution of the
//! offchain worker API.
//!
//! Substrate's CLI allows some degree of configuration about this, allowing node operators to
//! specify when they want to run the offchain worker API. See
//! [`sc_cli::RunCmd::offchain_worker_params`].
//!
//! ## Nondeterministic Execution
//!
//! Needless to say, given the above description, the code in your offchain worker API can be
//! nondeterministic, as it is not part of the blockchain's STF, so it can be executed at unknown
//! times, by unknown nodes, and has no impact on the state. This is why an HTTP
//! ([`sp_runtime::offchain::http`]) API is readily provided to the offchain worker APIs. Because
//! there is no need for determinism in this context.
//!
//! > A common mistake here is for novice developers to see this HTTP API, and imagine that
//! > `pezkuwi-sdk` somehow magically solved the determinism in blockchains, and now a blockchain
//! > can make HTTP calls and it will all work. This is absolutely NOT the case. An HTTP call made
//! > by the offchain worker is non-deterministic by design. Blockchains can't and always won't be
//! > able to perform non-deterministic operations such as making HTTP calls to a foreign server.
//!
//! ## FRAME's API
//!
//! [`frame`] provides a simple API through which pallets can define offchain worker functions. This
//! is part of [`frame::traits::Hooks`], which is implemented as a part of
//! [`frame::pallet_macros::hooks`].
//!
//! ```
//! #[frame::pallet]
//! pub mod pallet {
//! use frame::prelude::*;
//!
//! #[pallet::config]
//! pub trait Config: frame_system::Config {}
//!
//! #[pallet::pallet]
//! pub struct Pallet<T>(_);
//!
//! #[pallet::hooks]
//! impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
//! fn offchain_worker(block_number: BlockNumberFor<T>) {
//! // ...
//! }
//! }
//! }
//! ```
//!
//! Additionally, [`sp_runtime::offchain`] provides a set of utilities that can be used to moderate
//! the execution of offchain workers.
//!
//! ## Think Twice: Why Use Substrate's Offchain Workers?
//!
//! Consider the fact that in principle, an offchain worker code written using the above API is no
//! different than an equivalent written with an _actual offchain interaction library_, such as
//! [Pezkuwi-JS](https://polkadot.js.org/docs/), or any of the other ones listed [here](https://github.com/substrate-developer-hub/awesome-substrate?tab=readme-ov-file#client-libraries).
//!
//! They can both read from the state, and have no means of updating the state, other than the route
//! of submitting an extrinsic to the chain. Therefore, it is worth thinking twice before embedding
//! a logic as a part of Substrate's offchain worker API. Does it have to be there? Can it not be a
//! simple, actual offchain application that lives outside of the chain's WASM blob?
//!
//! Some of the reasons why you might want to do the opposite, and actually embed an offchain worker
//! API into the WASM blob are:
//!
//! * Accessing the state is easier within the `offchain_worker` function, as it is already a part
//! of the runtime, and [`frame::pallet_macros::storage`] provides all the tools needed to read
//! the state. Other client libraries might provide varying degrees of capability here.
//! * It will be updated in synchrony with the runtime. A Substrate's offchain application is part
//! of the same WASM blob, and is therefore guaranteed to be up to date.
//!
//! For example, imagine you have modified a storage item to have a new type. This will possibly
//! require a [`crate::reference_docs::frame_runtime_upgrades_and_migrations`], and any offchain
//! code, such as a Pezkuwi-JS application, will have to be updated to reflect this change. Whereas
//! the WASM offchain worker code is guaranteed to already be updated, or else the runtime code will
//! not even compile.
//!
//!
//! ## Further References
//!
//! - <https://forum.polkadot.network/t/offchain-workers-design-assumptions-vulnerabilities/2548>
//! - <https://exchange.pezkuwichain.app/questions/11058/how-can-i-create-ocw-that-wont-activates-every-block-but-will-activates-only-w/11060#11060>
//! - [Offchain worker example](https://github.com/pezkuwichain/pezkuwi-sdk/tree/master/substrate/frame/examples/offchain-worker)
+270
View File
@@ -0,0 +1,270 @@
//! # FRAME Origin
//!
//! Let's start by clarifying a common wrong assumption about Origin:
//!
//! **ORIGIN IS NOT AN ACCOUNT ID**.
//!
//! FRAME's origin abstractions allow you to convey meanings far beyond just an account-id being the
//! caller of an extrinsic. Nonetheless, an account-id having signed an extrinsic is one of the
//! meanings that an origin can convey. This is the commonly used [`frame_system::ensure_signed`],
//! where the return value happens to be an account-id.
//!
//! Instead, let's establish the following as the correct definition of an origin:
//!
//! > The origin type represents the privilege level of the caller of an extrinsic.
//!
//! That is, an extrinsic, through checking the origin, can *express what privilege level it wishes
//! to impose on the caller of the extrinsic*. One of those checks can be as simple as "*any account
//! that has signed a statement can pass*".
//!
//! But the origin system can also express more abstract and complicated privilege levels. For
//! example:
//!
//! * If the majority of token holders agreed upon this. This is more or less what the
//! [`pallet_democracy`] does under the hood ([reference](https://github.com/pezkuwichain/pezkuwi-sdk/blob/edd95b3749754d2ed0c5738588e872c87be91624/substrate/frame/democracy/src/lib.rs#L1603-L1633)).
//! * If a specific ratio of an instance of [`pallet_collective`]/DAO agrees upon this.
//! * If another consensus system, for example a bridged network or a teyrchain, agrees upon this.
//! * If the majority of validator/authority set agrees upon this[^1].
//! * If caller holds a particular NFT.
//!
//! and many more.
//!
//! ## Context
//!
//! First, let's look at where the `origin` type is encountered in a typical pallet. The `origin:
//! OriginFor<T>` has to be the first argument of any given callable extrinsic in FRAME:
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", call_simple)]
//!
//! Typically, the code of an extrinsic starts with an origin check, such as
//! [`frame_system::ensure_signed`].
//!
//! Note that [`OriginFor`](frame_system::pallet_prelude::OriginFor) is merely a shorthand for
//! [`frame_system::Config::RuntimeOrigin`]. Given the name prefix `Runtime`, we can learn that
//! `RuntimeOrigin` is similar to `RuntimeCall` and others, a runtime composite enum that is
//! amalgamated at the runtime level. Read [`crate::reference_docs::frame_runtime_types`] to
//! familiarize yourself with these types.
//!
//! To understand this better, we will next create a pallet with a custom origin, which will add a
//! new variant to `RuntimeOrigin`.
//!
//! ## Adding Custom Pallet Origin to the Runtime
//!
//! For example, given a pallet that defines the following custom origin:
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin)]
//!
//! And a runtime with the following pallets:
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", runtime_exp)]
//!
//! The type [`crate::reference_docs::frame_origin::runtime_for_origin::RuntimeOrigin`] is expanded.
//! This `RuntimeOrigin` contains a variant for the [`frame_system::RawOrigin`] and the custom
//! origin of the pallet.
//!
//! > Notice how the [`frame_system::ensure_signed`] is nothing more than a `match` statement. If
//! > you want to know where the actual origin of an extrinsic is set (and the signature
//! > verification happens, if any), see
//! > [`sp_runtime::generic::CheckedExtrinsic#trait-implementations`], specifically
//! > [`sp_runtime::traits::Applyable`]'s implementation.
//!
//! ## Asserting on a Custom Internal Origin
//!
//! In order to assert on a custom origin that is defined within your pallet, we need a way to first
//! convert the `<T as frame_system::Config>::RuntimeOrigin` into the local `enum Origin` of the
//! current pallet. This is a common process that is explained in
//! [`crate::reference_docs::frame_runtime_types#
//! adding-further-constraints-to-runtime-composite-enums`].
//!
//! We use the same process here to express that `RuntimeOrigin` has a number of additional bounds,
//! as follows.
//!
//! 1. Defining a custom `RuntimeOrigin` with further bounds in the pallet.
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin_bound)]
//!
//! 2. Using it in the pallet.
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin_usage)]
//!
//! ## Asserting on a Custom External Origin
//!
//! Very often, a pallet wants to have a parameterized origin that is **NOT** defined within the
//! pallet. In other words, a pallet wants to delegate an origin check to something that is
//! specified later at the runtime level. Like many other parameterizations in FRAME, this implies
//! adding a new associated type to `trait Config`.
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_def)]
//!
//! Then, within the pallet, we can simply use this "unknown" origin check type:
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_usage)]
//!
//! Finally, at the runtime, any implementation of [`frame::traits::EnsureOrigin`] can be passed.
#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_provide)]
//!
//! Indeed, some of these implementations of [`frame::traits::EnsureOrigin`] are similar to the ones
//! that we know about: [`frame::runtime::prelude::EnsureSigned`],
//! [`frame::runtime::prelude::EnsureSignedBy`], [`frame::runtime::prelude::EnsureRoot`],
//! [`frame::runtime::prelude::EnsureNone`], etc. But, there are also many more that are not known
//! to us, and are defined in other pallets.
//!
//! For example, [`pallet_collective`] defines [`pallet_collective::EnsureMember`] and
//! [`pallet_collective::EnsureProportionMoreThan`] and many more, which is exactly what we alluded
//! to earlier in this document.
//!
//! Make sure to check the full list of [implementors of
//! `EnsureOrigin`](frame::traits::EnsureOrigin#implementors) for more inspiration.
//!
//! ## Obtaining Abstract Origins
//!
//! So far we have learned that FRAME pallets can assert on custom and abstract origin types,
//! whether they are defined within the pallet or not. But how can we obtain these abstract origins?
//!
//! > All extrinsics that come from the outer world can generally only be obtained as either
//! > `signed` or `none` origin.
//!
//! Generally, these abstract origins are only obtained within the runtime, when a call is
//! dispatched within the runtime.
//!
//! ## Further References
//!
//! - [Gavin Wood's speech about FRAME features at Protocol Berg 2023.](https://youtu.be/j7b8Upipmeg?si=83_XUgYuJxMwWX4g&t=195)
//! - [A related StackExchange question.](https://exchange.pezkuwichain.app/questions/10992/how-do-you-find-the-public-key-for-the-medium-spender-track-origin)
//!
//! [^1]: Inherents are essentially unsigned extrinsics that need an [`frame_system::ensure_none`]
//! origin check, and through the virtue of being an inherent, are agreed upon by all validators.
use frame::prelude::*;
#[frame::pallet(dev_mode)]
pub mod pallet_for_origin {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[docify::export(call_simple)]
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn do_something(_origin: OriginFor<T>) -> DispatchResult {
// ^^^^^^^^^^^^^^^^^^^^^
todo!();
}
}
}
#[frame::pallet(dev_mode)]
pub mod pallet_with_custom_origin {
use super::*;
#[docify::export(custom_origin_bound)]
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
+ Into<Result<Origin, <Self as Config>::RuntimeOrigin>>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[docify::export(custom_origin)]
/// A dummy custom origin.
#[pallet::origin]
#[derive(
PartialEq,
Eq,
Clone,
RuntimeDebug,
Encode,
Decode,
DecodeWithMemTracking,
TypeInfo,
MaxEncodedLen,
)]
pub enum Origin {
/// If all holders of a particular NFT have agreed upon this.
AllNftHolders,
/// If all validators have agreed upon this.
ValidatorSet,
}
#[docify::export(custom_origin_usage)]
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn only_validators(origin: OriginFor<T>) -> DispatchResult {
// first, we convert from `<T as frame_system::Config>::RuntimeOrigin` to `<T as
// Config>::RuntimeOrigin`
let local_runtime_origin = <<T as Config>::RuntimeOrigin as From<
<T as frame_system::Config>::RuntimeOrigin,
>>::from(origin);
// then we convert to `origin`, if possible
let local_origin =
local_runtime_origin.into().map_err(|_| "invalid origin type provided")?;
ensure!(matches!(local_origin, Origin::ValidatorSet), "Not authorized");
todo!();
}
}
}
pub mod runtime_for_origin {
use super::pallet_with_custom_origin;
use frame::{runtime::prelude::*, testing_prelude::*};
#[docify::export(runtime_exp)]
construct_runtime!(
pub struct Runtime {
System: frame_system,
PalletWithCustomOrigin: pallet_with_custom_origin,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
}
impl pallet_with_custom_origin::Config for Runtime {
type RuntimeOrigin = RuntimeOrigin;
}
}
#[frame::pallet(dev_mode)]
pub mod pallet_with_external_origin {
use super::*;
#[docify::export(external_origin_def)]
#[pallet::config]
pub trait Config: frame_system::Config {
type ExternalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[docify::export(external_origin_usage)]
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn externally_checked_ext(origin: OriginFor<T>) -> DispatchResult {
T::ExternalOrigin::ensure_origin(origin)?;
todo!();
}
}
}
pub mod runtime_for_external_origin {
use super::*;
use frame::{runtime::prelude::*, testing_prelude::*};
construct_runtime!(
pub struct Runtime {
System: frame_system,
PalletWithExternalOrigin: pallet_with_external_origin,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
}
#[docify::export(external_origin_provide)]
impl pallet_with_external_origin::Config for Runtime {
type ExternalOrigin = EnsureSigned<<Self as frame_system::Config>::AccountId>;
}
}
@@ -0,0 +1,296 @@
//! # FRAME Pallet Coupling
//!
//! This reference document explains how FRAME pallets can be combined to interact together.
//!
//! It is suggested to re-read [`crate::pezkuwi_sdk::frame_runtime`], notably the information
//! around [`frame::pallet_macros::config`]. Recall that:
//!
//! > Configuration trait of a pallet: It allows a pallet to receive types at a later
//! > point from the runtime that wishes to contain it. It allows the pallet to be parameterized
//! > over both types and values.
//!
//! ## Context, Background
//!
//! FRAME pallets, as per described in [`crate::pezkuwi_sdk::frame_runtime`] are:
//!
//! > A pallet is a unit of encapsulated logic. It has a clearly defined responsibility and can be
//! linked to other pallets.
//!
//! That is to say:
//!
//! * *encapsulated*: Ideally, a FRAME pallet contains encapsulated logic which has clear
//! boundaries. It is generally a bad idea to build a single monolithic pallet that does multiple
//! things, such as handling currencies, identities and staking all at the same time.
//! * *linked to other pallets*: But, adhering extensively to the above also hinders the ability to
//! write useful applications. Pallets often need to work with each other, communicate and use
//! each other's functionalities.
//!
//! The broad principle that allows pallets to be linked together is the same way through which a
//! pallet uses its `Config` trait to receive types and values from the runtime that contains it.
//!
//! There are generally two ways to achieve this:
//!
//! 1. Tight coupling pallets.
//! 2. Loose coupling pallets.
//!
//! To explain the difference between the two, consider two pallets, `A` and `B`. In both cases, `A`
//! wants to use some functionality exposed by `B`.
//!
//! When tightly coupling pallets, `A` can only exist in a runtime if `B` is also present in the
//! same runtime. That is, `A` is expressing that can only work if `B` is present.
//!
//! This translates to the following Rust code:
//!
//! ```
//! trait Pallet_B_Config {}
//! trait Pallet_A_Config: Pallet_B_Config {}
//! ```
//!
//! Contrary, when pallets are loosely coupled, `A` expresses that some functionality, expressed via
//! a trait `F`, needs to be fulfilled. This trait is then implemented by `B`, and the two pallets
//! are linked together at the runtime level. This means that `A` only relies on the implementation
//! of `F`, which may be `B`, or another implementation of `F`.
//!
//! This translates to the following Rust code:
//!
//! ```
//! trait F {}
//! trait Pallet_A_Config {
//! type F: F;
//! }
//! // Pallet_B will implement and fulfill `F`.
//! ```
//!
//! ## Example
//!
//! Consider the following example, in which `pallet-foo` needs another pallet to provide the block
//! author to it, and `pallet-author` which has access to this information.
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_foo)]
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author)]
//!
//! ### Tight Coupling Pallets
//!
//! To tightly couple `pallet-foo` and `pallet-author`, we use Rust's supertrait system. When a
//! pallet makes its own `trait Config` be bounded by another pallet's `trait Config`, it is
//! expressing two things:
//!
//! 1. That it can only exist in a runtime if the other pallet is also present.
//! 2. That it can use the other pallet's functionality.
//!
//! `pallet-foo`'s `Config` would then look like:
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_config)]
//!
//! And `pallet-foo` can use the method exposed by `pallet_author::Pallet` directly:
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_usage)]
//!
//!
//! ### Loosely Coupling Pallets
//!
//! If `pallet-foo` wants to *not* rely on `pallet-author` directly, it can leverage its
//! `Config`'s associated types. First, we need a trait to express the functionality that
//! `pallet-foo` wants to obtain:
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", AuthorProvider)]
//!
//! > We sometimes refer to such traits that help two pallets interact as "glue traits".
//!
//! Next, `pallet-foo` states that it needs this trait to be provided to it, at the runtime level,
//! via an associated type:
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_config)]
//!
//! Then, `pallet-foo` can use this trait to obtain the block author, without knowing where it comes
//! from:
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_usage)]
//!
//! Then, if `pallet-author` implements this glue-trait:
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author_provider)]
//!
//! And upon the creation of the runtime, the two pallets are linked together as such:
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", runtime_author_provider)]
//!
//! Crucially, when using loose coupling, we gain the flexibility of providing different
//! implementations of `AuthorProvider`, such that different users of a `pallet-foo` can use
//! different ones, without any code change being needed. For example, in the code snippets of this
//! module, you can find [`OtherAuthorProvider`], which is an alternative implementation of
//! [`AuthorProvider`].
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", other_author_provider)]
//!
//! A common pattern in pezkuwi-sdk is to provide an implementation of such glu traits for the unit
//! type as a "default/test behavior".
#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", unit_author_provider)]
//!
//! ## Frame System
//!
//! With the above information in context, we can conclude that **`frame_system` is a special pallet
//! that is tightly coupled with every other pallet**. This is because it provides the fundamental
//! system functionality that every pallet needs, such as some types like
//! [`frame::prelude::frame_system::Config::AccountId`],
//! [`frame::prelude::frame_system::Config::Hash`], and some functionality such as block number,
//! etc.
//!
//! ## Recap
//!
//! To recap, consider the following rules of thumb:
//!
//! * In all cases, try and break down big pallets apart with clear boundaries of responsibility. In
//! general, it is easier to argue about multiple pallet if they only communicate together via a
//! known trait, rather than having access to all of each others public items, such as storage and
//! dispatchables.
//! * If a group of pallets is meant to work together, but is not foreseen to be generalized, or
//! used by others, consider tightly coupling pallets, *if it simplifies the development*.
//! * If a pallet needs a functionality provided by another pallet, but multiple implementations can
//! be foreseen, consider loosely coupling pallets.
//!
//! For example, all pallets in `pezkuwi-sdk` that needed to work with currencies could have been
//! tightly coupled with [`pallet_balances`]. But, `pezkuwi-sdk` also provides [`pallet_assets`]
//! (and more implementations by the community), therefore all pallets use traits to loosely couple
//! with balances or assets pallet. More on this in [`crate::reference_docs::frame_tokens`].
//!
//! ## Further References
//!
//! - <https://www.youtube.com/watch?v=0eNGZpNkJk4>
//! - <https://exchange.pezkuwichain.app/questions/922/pallet-loose-couplingtight-coupling-and-missing-traits>
//!
//! [`AuthorProvider`]: crate::reference_docs::frame_pallet_coupling::AuthorProvider
//! [`OtherAuthorProvider`]: crate::reference_docs::frame_pallet_coupling::OtherAuthorProvider
#![allow(unused)]
use frame::prelude::*;
#[docify::export]
#[frame::pallet]
pub mod pallet_foo {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
impl<T: Config> Pallet<T> {
fn do_stuff_with_author() {
// needs block author here
}
}
}
#[docify::export]
#[frame::pallet]
pub mod pallet_author {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
impl<T: Config> Pallet<T> {
pub fn author() -> T::AccountId {
todo!("somehow has access to the block author and can return it here")
}
}
}
#[frame::pallet]
pub mod pallet_foo_tight {
use super::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[docify::export(tight_config)]
/// This pallet can only live in a runtime that has both `frame_system` and `pallet_author`.
#[pallet::config]
pub trait Config: frame_system::Config + pallet_author::Config {}
#[docify::export(tight_usage)]
impl<T: Config> Pallet<T> {
// anywhere in `pallet-foo`, we can call into `pallet-author` directly, namely because
// `T: pallet_author::Config`
fn do_stuff_with_author() {
let _ = pallet_author::Pallet::<T>::author();
}
}
}
#[docify::export]
/// Abstraction over "something that can provide the block author".
pub trait AuthorProvider<AccountId> {
fn author() -> AccountId;
}
#[frame::pallet]
pub mod pallet_foo_loose {
use super::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[docify::export(loose_config)]
#[pallet::config]
pub trait Config: frame_system::Config {
/// This pallet relies on the existence of something that implements [`AuthorProvider`],
/// which may or may not be `pallet-author`.
type AuthorProvider: AuthorProvider<Self::AccountId>;
}
#[docify::export(loose_usage)]
impl<T: Config> Pallet<T> {
fn do_stuff_with_author() {
let _ = T::AuthorProvider::author();
}
}
}
#[docify::export(pallet_author_provider)]
impl<T: pallet_author::Config> AuthorProvider<T::AccountId> for pallet_author::Pallet<T> {
fn author() -> T::AccountId {
pallet_author::Pallet::<T>::author()
}
}
pub struct OtherAuthorProvider;
#[docify::export(other_author_provider)]
impl<AccountId> AuthorProvider<AccountId> for OtherAuthorProvider {
fn author() -> AccountId {
todo!("somehow get the block author here")
}
}
#[docify::export(unit_author_provider)]
impl<AccountId> AuthorProvider<AccountId> for () {
fn author() -> AccountId {
todo!("somehow get the block author here")
}
}
pub mod runtime {
use super::*;
use cumulus_pallet_aura_ext::pallet;
use frame::{runtime::prelude::*, testing_prelude::*};
construct_runtime!(
pub struct Runtime {
System: frame_system,
PalletFoo: pallet_foo_loose,
PalletAuthor: pallet_author,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
}
impl pallet_author::Config for Runtime {}
#[docify::export(runtime_author_provider)]
impl pallet_foo_loose::Config for Runtime {
type AuthorProvider = pallet_author::Pallet<Runtime>;
// which is also equivalent to
// type AuthorProvider = PalletAuthor;
}
}
@@ -0,0 +1,320 @@
//! # FRAME Runtime Types
//!
//! This reference document briefly explores the idea around types generated at the runtime level by
//! the FRAME macros.
//!
//! > As of now, many of these important types are generated within the internals of
//! > [`construct_runtime`], and there is no easy way for you to visually know they exist.
//! > [#pezkuwi-sdk#1378](https://github.com/paritytech/polkadot-sdk/pull/1378) is meant to
//! > significantly improve this. Exploring the rust-docs of a runtime, such as [`runtime`] which is
//! > defined in this module is as of now the best way to learn about these types.
//!
//! ## Composite Enums
//!
//! Many types within a FRAME runtime follow the following structure:
//!
//! * Each individual pallet defines a type, for example `Foo`.
//! * At the runtime level, these types are amalgamated into a single type, for example
//! `RuntimeFoo`.
//!
//! As the names suggest, all composite enums in a FRAME runtime start their name with `Runtime`.
//! For example, `RuntimeCall` is a representation of the most high level `Call`-able type in the
//! runtime.
//!
//! Composite enums are generally convertible to their individual parts as such:
#![doc = simple_mermaid::mermaid!("../../../mermaid/outer_runtime_types.mmd")]
//!
//! In that one can always convert from the inner type into the outer type, but not vice versa. This
//! is usually expressed by implementing `From`, `TryFrom`, `From<Result<_>>` and similar traits.
//!
//! ### Example
//!
//! We provide the following two pallets: [`pallet_foo`] and [`pallet_bar`]. Each define a
//! dispatchable, and `Foo` also defines a custom origin. Lastly, `Bar` defines an additional
//! `GenesisConfig`.
#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", pallet_foo)]
#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", pallet_bar)]
//!
//! Let's explore how each of these affect the [`RuntimeCall`], [`RuntimeOrigin`] and
//! [`RuntimeGenesisConfig`] generated in [`runtime`] respectively.
//!
//! As observed, [`RuntimeCall`] has 3 variants, one for each pallet and one for `frame_system`. If
//! you explore further, you will soon realize that each variant is merely a pointer to the `Call`
//! type in each pallet, for example [`pallet_foo::Call`].
//!
//! [`RuntimeOrigin`]'s [`OriginCaller`] has two variants, one for system, and one for `pallet_foo`
//! which utilized [`frame::pallet_macros::origin`].
//!
//! Finally, [`RuntimeGenesisConfig`] is composed of `frame_system` and a variant for `pallet_bar`'s
//! [`pallet_bar::GenesisConfig`].
//!
//! You can find other composite enums by scanning [`runtime`] for other types who's name starts
//! with `Runtime`. Some of the more noteworthy ones are:
//!
//! - [`RuntimeEvent`]
//! - [`RuntimeError`]
//! - [`RuntimeHoldReason`]
//!
//! ### Adding Further Constraints to Runtime Composite Enums
//!
//! This section explores a common scenario where a pallet has access to one of these runtime
//! composite enums, but it wishes to further specify it by adding more trait bounds to it.
//!
//! Let's take the example of `RuntimeCall`. This is an associated type in
//! [`frame_system::Config::RuntimeCall`], and all pallets have access to this type, because they
//! have access to [`frame_system::Config`]. Finally, this type is meant to be set to outer call of
//! the entire runtime.
//!
//! But, let's not forget that this is information that *we know*, and the Rust compiler does not.
//! All that the rust compiler knows about this type is *ONLY* what the trait bounds of
//! [`frame_system::Config::RuntimeCall`] are specifying:
#![doc = docify::embed!("../../substrate/frame/system/src/lib.rs", system_runtime_call)]
//!
//! So, when at a given pallet, one accesses `<T as frame_system::Config>::RuntimeCall`, the type is
//! extremely opaque from the perspective of the Rust compiler.
//!
//! How can a pallet access the `RuntimeCall` type with further constraints? For example, each
//! pallet has its own `enum Call`, and knows that its local `Call` is a part of `RuntimeCall`,
//! therefore there should be a `impl From<Call<_>> for RuntimeCall`.
//!
//! The only way to express this using Rust's associated types is for the pallet to **define its own
//! associated type `RuntimeCall`, and further specify what it thinks `RuntimeCall` should be**.
//!
//! In this case, we will want to assert the existence of [`frame::traits::IsSubType`], which is
//! very similar to [`TryFrom`].
#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", custom_runtime_call)]
//!
//! And indeed, at the runtime level, this associated type would be the same `RuntimeCall` that is
//! passed to `frame_system`.
#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", pallet_with_specific_runtime_call_impl)]
//!
//! > In other words, the degree of specificity that [`frame_system::Config::RuntimeCall`] has is
//! > not enough for the pallet to work with. Therefore, the pallet has to define its own associated
//! > type representing `RuntimeCall`.
//!
//! Another way to look at this is:
//!
//! `pallet_with_specific_runtime_call::Config::RuntimeCall` and `frame_system::Config::RuntimeCall`
//! are two different representations of the same concrete type that is only known when the runtime
//! is being constructed.
//!
//! Now, within this pallet, this new `RuntimeCall` can be used, and it can use its new trait
//! bounds, such as being [`frame::traits::IsSubType`]:
#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", custom_runtime_call_usages)]
//!
//! > Once Rust's "_Associated Type Bounds RFC_" is usable, this syntax can be used to
//! > simplify the above scenario. See [this](https://github.com/pezkuwichain/pezkuwi-sdk/issues/133)
//! > issue for more information.
//!
//! ### Asserting Equality of Multiple Runtime Composite Enums
//!
//! Recall that in the above example, `<T as Config>::RuntimeCall` and `<T as
//! frame_system::Config>::RuntimeCall` are expected to be equal types, but at the compile-time we
//! have to represent them with two different associated types with different bounds. Would it not
//! be cool if we had a test to make sure they actually resolve to the same concrete type once the
//! runtime is constructed? The following snippet exactly does that:
#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", assert_equality)]
//!
//! We leave it to the reader to further explore what [`frame::traits::Hooks::integrity_test`] is,
//! and what [`core::any::TypeId`] is. Another way to assert this is using
//! [`frame::traits::IsType`].
//!
//! ## Type Aliases
//!
//! A number of type aliases are generated by the `construct_runtime` which are also noteworthy:
//!
//! * [`runtime::PalletFoo`] is an alias to [`pallet_foo::Pallet`]. Same for `PalletBar`, and
//! `System`
//! * [`runtime::AllPalletsWithSystem`] is an alias for a tuple of all of the above. This type is
//! important to FRAME internals such as `executive`, as it implements traits such as
//! [`frame::traits::Hooks`].
//!
//! ## Further Details
//!
//! * [`crate::reference_docs::frame_origin`] explores further details about the usage of
//! `RuntimeOrigin`.
//! * [`RuntimeCall`] is a particularly interesting composite enum as it dictates the encoding of an
//! extrinsic. See [`crate::reference_docs::transaction_extensions`] for more information.
//! * See the documentation of [`construct_runtime`].
//! * See the corresponding lecture in the [PBA Lectures](https://www.youtube.com/watch?v=OCBC1pMYPoc&list=PL-w_i5kwVqbni1Ch2j_RwTIXiB-bwnYqq&index=11).
//!
//!
//! [`construct_runtime`]: frame::runtime::prelude::construct_runtime
//! [`runtime::PalletFoo`]: crate::reference_docs::frame_runtime_types::runtime::PalletFoo
//! [`runtime::AllPalletsWithSystem`]: crate::reference_docs::frame_runtime_types::runtime::AllPalletsWithSystem
//! [`runtime`]: crate::reference_docs::frame_runtime_types::runtime
//! [`pallet_foo`]: crate::reference_docs::frame_runtime_types::pallet_foo
//! [`pallet_foo::Call`]: crate::reference_docs::frame_runtime_types::pallet_foo::Call
//! [`pallet_foo::Pallet`]: crate::reference_docs::frame_runtime_types::pallet_foo::Pallet
//! [`pallet_bar`]: crate::reference_docs::frame_runtime_types::pallet_bar
//! [`pallet_bar::GenesisConfig`]: crate::reference_docs::frame_runtime_types::pallet_bar::GenesisConfig
//! [`RuntimeEvent`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeEvent
//! [`RuntimeGenesisConfig`]:
//! crate::reference_docs::frame_runtime_types::runtime::RuntimeGenesisConfig
//! [`RuntimeOrigin`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeOrigin
//! [`OriginCaller`]: crate::reference_docs::frame_runtime_types::runtime::OriginCaller
//! [`RuntimeError`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeError
//! [`RuntimeCall`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeCall
//! [`RuntimeHoldReason`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeHoldReason
use frame::prelude::*;
#[docify::export]
#[frame::pallet(dev_mode)]
pub mod pallet_foo {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::origin]
#[derive(
PartialEq,
Eq,
Clone,
RuntimeDebug,
Encode,
Decode,
DecodeWithMemTracking,
TypeInfo,
MaxEncodedLen,
)]
pub enum Origin {
A,
B,
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn foo(_origin: OriginFor<T>) -> DispatchResult {
todo!();
}
pub fn other(_origin: OriginFor<T>) -> DispatchResult {
todo!();
}
}
}
#[docify::export]
#[frame::pallet(dev_mode)]
pub mod pallet_bar {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub initial_account: Option<T::AccountId>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn bar(_origin: OriginFor<T>) -> DispatchResult {
todo!();
}
}
}
pub mod runtime {
use super::{pallet_bar, pallet_foo};
use frame::{runtime::prelude::*, testing_prelude::*};
#[docify::export(runtime_exp)]
construct_runtime!(
pub struct Runtime {
System: frame_system,
PalletFoo: pallet_foo,
PalletBar: pallet_bar,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
}
impl pallet_foo::Config for Runtime {}
impl pallet_bar::Config for Runtime {}
}
#[frame::pallet(dev_mode)]
pub mod pallet_with_specific_runtime_call {
use super::*;
use frame::traits::IsSubType;
#[docify::export(custom_runtime_call)]
/// A pallet that wants to further narrow down what `RuntimeCall` is.
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeCall: IsSubType<Call<Self>>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
// note that this pallet needs some `call` to have a `enum Call`.
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn foo(_origin: OriginFor<T>) -> DispatchResult {
todo!();
}
}
#[docify::export(custom_runtime_call_usages)]
impl<T: Config> Pallet<T> {
fn _do_something_useful_with_runtime_call(call: <T as Config>::RuntimeCall) {
// check if the runtime call given is of this pallet's variant.
let _maybe_my_call: Option<&Call<T>> = call.is_sub_type();
todo!();
}
}
#[docify::export(assert_equality)]
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn integrity_test() {
use core::any::TypeId;
assert_eq!(
TypeId::of::<<T as Config>::RuntimeCall>(),
TypeId::of::<<T as frame_system::Config>::RuntimeCall>()
);
}
}
}
pub mod runtime_with_specific_runtime_call {
use super::pallet_with_specific_runtime_call;
use frame::{runtime::prelude::*, testing_prelude::*};
construct_runtime!(
pub struct Runtime {
System: frame_system,
PalletWithSpecificRuntimeCall: pallet_with_specific_runtime_call,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Self>;
}
#[docify::export(pallet_with_specific_runtime_call_impl)]
impl pallet_with_specific_runtime_call::Config for Runtime {
// an implementation of `IsSubType` is provided by `construct_runtime`.
type RuntimeCall = RuntimeCall;
}
}
@@ -0,0 +1,134 @@
//! # Runtime Upgrades
//!
//! At their core, blockchain logic consists of
//!
//! 1. on-chain state,
//! 2. a state transition function.
//!
//! In Substrate-based blockchains, state transition functions are referred to as
//! [runtimes](https://docs.pezkuwichain.io/sdk/master/polkadot_sdk_docs/reference_docs/blockchain_state_machines/index.html).
//!
//! Traditionally, before Substrate, upgrading state transition functions required node
//! operators to download new software and restart their nodes in a process called
//! [forking](https://en.wikipedia.org/wiki/Fork_(blockchain)).
//!
//! Substrate-based blockchains do not require forking, and instead upgrade runtimes
//! in a process called "Runtime Upgrades".
//!
//! Forkless runtime upgrades are a defining feature of the Substrate framework. Updating the
//! runtime logic without forking the code base enables your blockchain to seamlessly evolve
//! over time in a deterministic, rules-based manner. It also removes ambiguity for node operators
//! and other participants in the network about what is the canonical runtime.
//!
//! This capability is possible due to the runtime of a blockchain existing in on-chain storage.
//!
//! ## Performing a Runtime Upgrade
//!
//! To upgrade a runtime, an [`Origin`](frame_system::RawOrigin) with the necessary permissions
//! (usually via governance) changes the `:code` storage. Usually, this is performed via a call to
//! [`set_code`] (or [`set_code_without_checks`]) with the desired new runtime blob, scheduled
//! using [`pallet_scheduler`].
//!
//! Prior to building the new runtime, don't forget to update the
//! [`RuntimeVersion`](sp_version::RuntimeVersion).
//!
//! # Migrations
//!
//! It is often desirable to define logic to execute immediately after runtime upgrades (see
//! [this diagram](frame::traits::Hooks)).
//!
//! Self-contained pieces of logic that execute after a runtime upgrade are called "Migrations".
//!
//! The typical use case of a migration is to 'migrate' pallet storage from one layout to another,
//! for example when the encoding of a storage item is changed. However, they can also execute
//! arbitrary logic such as:
//!
//! - Calling arbitrary pallet methods.
//! - Mutating arbitrary on-chain state.
//! - Cleaning up some old storage items that are no longer needed.
//!
//! ## Single Block Migrations
//!
//! - Execute immediately and entirely at the beginning of the block following
//! a runtime upgrade.
//! - Are suitable for migrations which are guaranteed to not exceed the block weight.
//! - Are simply implementations of [`OnRuntimeUpgrade`].
//!
//! To learn best practices for writing single block pallet storage migrations, see the
//! [Single Block Migration Example Pallet](pallet_example_single_block_migrations).
//!
//! ### Scheduling the Single Block Migrations to Run Next Runtime Upgrade
//!
//! Schedule migrations to run next runtime upgrade passing them as a parameter to your
//! [`Config`](frame_system) pallet:
//!
//! ```ignore
//! /// Tuple of migrations (structs that implement `OnRuntimeUpgrade`)
//! type Migrations = (
//! pallet_example_storage_migration::migrations::v1::versioned::MigrateV0ToV1,
//! MyCustomMigration,
//! // ...more migrations here
//! );
//! impl frame_system::Config for Runtime {
//! type SingleBlockMigrations = Migrations;
//! }
//! ```
//!
//! ### Ensuring Single Block Migration Safety
//!
//! "My migration unit tests pass, so it should be safe to deploy right?"
//!
//! No! Unit tests execute the migration in a very simple test environment, and cannot account
//! for the complexities of a real runtime or real on-chain state.
//!
//! Prior to deploying migrations, it is critical to perform additional checks to ensure that when
//! run in our real runtime they will not brick the chain due to:
//! - Panicking.
//! - Touching too many storage keys and resulting in an excessively large PoV.
//! - Taking too long to execute.
//!
//! [`try-runtime-cli`](https://github.com/paritytech/try-runtime-cli) has a sub-command
//! [`on-runtime-upgrade`](https://paritytech.github.io/try-runtime-cli/try_runtime_core/commands/enum.Action.html#variant.OnRuntimeUpgrade)
//! which is designed to help with exactly this.
//!
//! Developers MUST run this command before deploying migrations to ensure they will not
//! inadvertently result in a bricked chain.
//!
//! It is recommended to run as part of your CI pipeline. See the
//! [pezkuwi-sdk check-runtime-migration job](https://github.com/pezkuwichain/pezkuwi-sdk/blob/4a293bc5a25be637c06ce950a34490706597615b/.gitlab/pipeline/check.yml#L103-L124)
//! for an example of how to configure this.
//!
//! ### Note on the Manipulability of PoV Size and Execution Time
//!
//! While [`try-runtime-cli`](https://github.com/paritytech/try-runtime-cli) can help ensure with
//! very high certainty that a migration will succeed given **existing** on-chain state, it cannot
//! prevent a malicious actor from manipulating state in a way that will cause the migration to take
//! longer or produce a PoV much larger than previously measured.
//!
//! Therefore, it is important to write migrations in such a way that the execution time or PoV size
//! it adds to the block cannot be easily manipulated. e.g., do not iterate over storage that can
//! quickly or cheaply be bloated.
//!
//! If writing your migration in such a way is not possible, a multi block migration should be used
//! instead.
//!
//! ### Other useful tools
//!
//! [`Chopsticks`](https://github.com/AcalaNetwork/chopsticks) is another tool in the Substrate
//! ecosystem which developers may find useful to use in addition to `try-runtime-cli` when testing
//! their single block migrations.
//!
//! ## Multi Block Migrations
//!
//! Safely and easily execute long-running migrations across multiple blocks.
//!
//! Suitable for migrations which could use arbitrary amounts of block weight.
//!
//! See the
//! [multi-block-migrations example](https://github.com/pezkuwichain/pezkuwi-sdk/tree/0d7d2177807ec6b3094f4491a45b0bc0d74d3c8b/substrate/frame/examples/multi-block-migrations)
//! for reference.
//!
//! [`OnRuntimeUpgrade`]: frame_support::traits::OnRuntimeUpgrade
//! [`StorageVersion`]: frame_support::traits::StorageVersion
//! [`set_code`]: frame_system::Call::set_code
//! [`set_code_without_checks`]: frame_system::Call::set_code_without_checks
@@ -0,0 +1,202 @@
//! # Frame storage derives
//!
//! > **Note:**
//! >
//! > In all examples, a few lines of boilerplate have been hidden from each snippet for
//! > conciseness.
//!
//! Let's begin by starting to store a `NewType` in a storage item:
//!
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! pub struct NewType(u32);
//
//! #[pallet::storage]
//! pub type Something<T> = StorageValue<_, NewType>;
//! }
//! ```
//!
//! This raises a number of compiler errors, like:
//! ```text
//! the trait `MaxEncodedLen` is not implemented for `NewType`, which is required by
//! `frame::prelude::StorageValue<_GeneratedPrefixForStorageSomething<T>, NewType>:
//! StorageInfoTrait`
//! ```
//!
//! This implies the following set of traits that need to be derived for a type to be stored in
//! `frame` storage:
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
//! pub struct NewType(u32);
//!
//! #[pallet::storage]
//! pub type Something<T> = StorageValue<_, NewType>;
//! }
//! ```
//!
//! Next, let's look at how this will differ if we are to store a type that is derived from `T` in
//! storage, such as [`frame::prelude::BlockNumberFor`]:
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
//! pub struct NewType<T: Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>>;
//! }
//! ```
//!
//! Surprisingly, this will also raise a number of errors, like:
//! ```text
//! the trait `TypeInfo` is not implemented for `T`, which is required
//! by`frame_support::pallet_prelude::StorageValue<pallet_2::_GeneratedPrefixForStorageSomething<T>,
//! pallet_2::NewType<T>>:StorageEntryMetadataBuilder
//! ```
//!
//! Why is that? The underlying reason is that the `TypeInfo` `derive` macro will only work for
//! `NewType` if all of `NewType`'s generics also implement `TypeInfo`. This is not the case for `T`
//! in the example above.
//!
//! If you expand an instance of the derive, you will find something along the lines of:
//! `impl<T> TypeInfo for NewType<T> where T: TypeInfo { ... }`. This is the reason why the
//! `TypeInfo` trait is required for `T`.
//!
//! To fix this, we need to add a `#[scale_info(skip_type_params(T))]`
//! attribute to `NewType`. This additional macro will instruct the `derive` to skip the bound on
//! `T`.
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
//! #[scale_info(skip_type_params(T))]
//! pub struct NewType<T: Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>>;
//! }
//! ```
//!
//! Next, let's say we wish to store `NewType` as [`frame::prelude::ValueQuery`], which means it
//! must also implement `Default`. This should be as simple as adding `derive(Default)` to it,
//! right?
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo, Default)]
//! #[scale_info(skip_type_params(T))]
//! pub struct NewType<T: Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>, ValueQuery>;
//! }
//! ```
//!
//! Under the hood, the expansion of the `derive(Default)` will suffer from the same restriction as
//! before: it will only work if `T: Default`, and `T` is not `Default`. Note that this is an
//! expected issue: `T` is merely a wrapper of many other types, such as `BlockNumberFor<T>`.
//! `BlockNumberFor<T>` should indeed implement `Default`, but `T` implementing `Default` is rather
//! meaningless.
//!
//! To fix this, frame provides a set of macros that are analogous to normal rust derive macros, but
//! work nicely on top of structs that are generic over `T: Config`. These macros are:
//!
//! - [`frame::prelude::DefaultNoBound`]
//! - [`frame::prelude::DebugNoBound`]
//! - [`frame::prelude::PartialEqNoBound`]
//! - [`frame::prelude::EqNoBound`]
//! - [`frame::prelude::CloneNoBound`]
//! - [`frame::prelude::PartialOrdNoBound`]
//! - [`frame::prelude::OrdNoBound`]
//!
//! The above traits are almost certainly needed for your tests - to print your type, assert equality
//! or clone it.
//!
//! We can fix the following example by using [`frame::prelude::DefaultNoBound`].
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! # use frame::prelude::*;
//! # #[pallet::config]
//! # pub trait Config: frame_system::Config {}
//! # #[pallet::pallet]
//! # pub struct Pallet<T>(_);
//! #[derive(
//! codec::Encode,
//! codec::Decode,
//! codec::MaxEncodedLen,
//! scale_info::TypeInfo,
//! DefaultNoBound
//! )]
//! #[scale_info(skip_type_params(T))]
//! pub struct NewType<T:Config>(BlockNumberFor<T>);
//!
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, NewType<T>, ValueQuery>;
//! }
//! ```
//!
//! Finally, if a custom type that is provided through `Config` is to be stored in the storage, it
//! is subject to the same trait requirements. The following does not work:
//! ```compile_fail
//! #[frame::pallet]
//! pub mod pallet {
//! use frame::prelude::*;
//! #[pallet::config]
//! pub trait Config: frame_system::Config {
//! type CustomType;
//! }
//! #[pallet::pallet]
//! pub struct Pallet<T>(_);
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, T::CustomType>;
//! }
//! ```
//!
//! But adding the right trait bounds will fix it.
//! ```rust
//! #[frame::pallet]
//! pub mod pallet {
//! use frame::prelude::*;
//! #[pallet::config]
//! pub trait Config: frame_system::Config {
//! type CustomType: codec::FullCodec
//! + codec::MaxEncodedLen
//! + scale_info::TypeInfo
//! + Debug
//! + Default;
//! }
//! #[pallet::pallet]
//! pub struct Pallet<T>(_);
//! #[pallet::storage]
//! pub type Something<T: Config> = StorageValue<_, T::CustomType>;
//! }
//! ```
@@ -0,0 +1,10 @@
//! # FRAME Accounts
//!
//! 🚧 Work In Progress 🚧
//!
//! How `frame_system` handles accountIds. Nonce. Consumers and Providers, reference counting.
// - poorly understood topics, needs one great article to rul them all.
// - https://github.com/paritytech/substrate/issues/14425
// - https://github.com/paritytech/substrate/pull/12951
// - https://exchange.pezkuwichain.app/questions/263/what-is-the-meaning-of-the-account-provider-sufficients-and-consumer
+129
View File
@@ -0,0 +1,129 @@
// This file is part of pezkuwi-sdk.
//
// 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.
//! # FRAME Tokens
//!
//! This reference doc serves as a high-level overview of the token-related logic in FRAME, and
//! how to properly apply it to your use case.
//!
//! On completion of reading this doc, you should have a good understanding of:
//! - The distinction between token traits and trait implementations in FRAME, and why this
//! distinction is helpful.
//! - Token-related traits available in FRAME.
//! - Token-related trait implementations in FRAME.
//! - How to choose the right trait or trait implementation for your use case.
//! - Where to go next.
//!
//! ## Getting Started
//!
//! The most ubiquitous way to add a token to a FRAME runtime is [`pallet_balances`]. Read
//! more about pallets [here](crate::pezkuwi_sdk::frame_runtime#pallets).
//!
//! You may then write custom pallets that interact with [`pallet_balances`]. The fastest way to
//! get started with that is by
//! [tightly coupling](crate::reference_docs::frame_pallet_coupling#tight-coupling-pallets) your
//! custom pallet to [`pallet_balances`].
//!
//! However, to keep pallets flexible and modular, it is often preferred to
//! [loosely couple](crate::reference_docs::frame_pallet_coupling#loosely--coupling-pallets).
//!
//! To achieve loose coupling,
//! we separate token logic into traits and trait implementations.
//!
//! ## Traits and Trait Implementations
//!
//! Broadly speaking, token logic in FRAME can be divided into two categories: traits and
//! trait implementations.
//!
//! **Traits** define common interfaces that types of tokens should implement. For example, the
//! [`fungible::Inspect`](`frame_support::traits::fungible::Inspect`) trait specifies an interface
//! for *inspecting* token state such as the total issuance of the token, the balance of individual
//! accounts, etc.
//!
//! **Trait implementations** are concrete implementations of these traits. For example, one of the
//! many traits [`pallet_balances`] implements is
//! [`fungible::Inspect`](`frame_support::traits::fungible::Inspect`)[^1]. It provides the concrete
//! way of inspecting the total issuance, balance of accounts, etc. There can be many
//! implementations of the same traits.
//!
//! [^1]: Rust Advanced Tip: The knowledge that [`pallet_balances`] implements
//! [`fungible::Inspect`](`frame_support::traits::fungible::Inspect`) is not some arcane knowledge
//! that you have to know by heart or memorize. One can simply look at the list of the implementors
//! of any trait in the Rust Doc to find all implementors (e.g.
//! [Mutate trait implementors](https://docs.pezkuwichain.io/sdk/master/frame_support/traits/tokens/fungible/trait.Mutate.html#implementors)),
//! or use the `rust-analyzer`'s `Implementations` action.
//!
//! The distinction between traits and trait implementations is helpful because it allows pallets
//! and other logic to be generic over their dependencies, avoiding tight coupling.
//!
//! To illustrate this with an example let's consider [`pallet_preimage`]. This pallet takes a
//! deposit in exchange for storing a preimage for later use. A naive implementation of the
//! pallet may use [`pallet_balances`] in a tightly coupled manner, directly calling methods
//! on the pallet to reserve and unreserve deposits. This approach works well,
//! until someone has a use case requiring that an asset from a different pallet such as
//! [`pallet_assets`] is used for the deposit. Rather than tightly coupling [`pallet_preimage`] to
//! [`pallet_balances`], [`pallet_assets`], and every other token-handling pallet, a user
//! could possibly specify that [`pallet_preimage`] does not specify a concrete pallet as a
//! dependency, but instead accepts any dependency which implements the
//! [`currency::ReservableCurrency`](`frame_support::traits::tokens::currency::ReservableCurrency`)
//! trait, namely via its [`Config::Currency`](`pallet_preimage::pallet::Config::Currency`)
//! associated type. This allows [`pallet_preimage`] to support any arbitrary pallet implementing
//! this trait, without needing any knowledge of what those pallets may be or requiring changes to
//! support new pallets which may be written in the future.
//!
//! Read more about coupling, and the benefits of loose coupling
//! [here](crate::reference_docs::frame_pallet_coupling).
//!
//! ## Fungible Token Traits in FRAME
//!
//! The [`fungible`](`frame_support::traits::fungible`) crate contains the latest set of FRAME
//! fungible token traits, and is recommended to use for all new logic requiring a fungible token.
//! See the crate documentation for more info about these fungible traits.
//!
//! [`fungibles`](`frame_support::traits::fungibles`) provides very similar functionality to
//! [`fungible`](`frame_support::traits::fungible`), except it supports managing multiple tokens.
//!
//! You may notice the trait [`Currency`](`frame_support::traits::Currency`) with similar
//! functionality is also used in the codebase, however this trait is deprecated and existing logic
//! is in the process of being migrated to [`fungible`](`frame_support::traits::fungible`) ([tracking issue](https://github.com/pezkuwichain/pezkuwi-sdk/issues/102)).
//!
//! ## Fungible Token Trait Implementations in FRAME
//!
//! [`pallet_balances`] implements [`fungible`](`frame_support::traits::fungible`), and is the most
//! commonly used fungible implementation in FRAME. Most of the time, it's used for managing the
//! native token of the blockchain network it's used in.
//!
//! [`pallet_assets`] implements [`fungibles`](`frame_support::traits::fungibles`), and is another
//! popular fungible token implementation. It supports the creation and management of multiple
//! assets in a single crate, making it a good choice when a network requires more assets in
//! addition to its native token.
//!
//! ## Non-Fungible Tokens in FRAME
//!
//! [`pallet_nfts`] is recommended to use for all NFT use cases in FRAME.
//! See the crate documentation for more info about this pallet.
//!
//! [`pallet_uniques`] is deprecated and should not be used.
//!
//!
//! # What Next?
//!
//! - If you are interested in implementing a single fungible token, continue reading the
//! [`fungible`](`frame_support::traits::fungible`) and [`pallet_balances`] docs.
//! - If you are interested in implementing a set of fungible tokens, continue reading the
//! [`fungibles`](`frame_support::traits::fungibles`) trait and [`pallet_assets`] docs.
//! - If you are interested in implementing an NFT, continue reading the [`pallet_nfts`] docs.
+120
View File
@@ -0,0 +1,120 @@
//! # Glossary
//!
//! #### State
//!
//! The data around which the blockchain network wishes to come to consensus. Also
//! referred to as "onchain data", "onchain storage" or sometimes just "storage". In UTXO based
//! blockchains, is referred to as "ledger".
//!
//! **Synonyms**: Onchain data, Onchain storage, Storage, Ledger
//!
//! #### State Transition Function
//!
//! The WASM Blob that dictates how the blockchain should transition its state upon encountering new
//! blocks.
//!
//! #### Host
//!
//! The environment that hosts and executes the [state transition function's WASM
//! blob](#state-transition-function).
//!
//! #### Node
//!
//! The full software artifact that contains the [host](#host), but importantly also all the other
//! modules needed to be part of a blockchain network, such as peer-to-peer networking, database and
//! such.
//!
//! **Synonyms**: Client
//!
//! #### Light Node
//!
//! Same as [node](#nodes), but when capable of following the network only through listening to
//! block headers. Usually capable of running in more constrained environments, such as an embedded
//! device, phone, or a web browser.
//!
//! **Synonyms**: Light Client
//!
//! #### Offchain
//!
//! Refers to operations conducted outside the blockchain's consensus mechanism. They are essential
//! for enhancing scalability and efficiency, enabling activities like data fetching and computation
//! without bloating the blockchain state.
//!
//! #### Host Functions:
//!
//! Host functions are the node's API, these are functions provided by the runtime environment (the
//! [host](#host)) to the Wasm runtime. These functions allow the Wasm code to interact with and
//! perform operations on the [node](#node), like accessing the blockchain state.
//!
//! #### Runtime API:
//!
//! This is the API of the runtime, it acts as a communication bridge between the runtime and the
//! node, serving as the exposed interface that facilitates their interactions.
//!
//! #### Dispatchable:
//!
//! Dispatchables are [function objects](https://en.wikipedia.org/wiki/Function_object) that act as
//! the entry points in [FRAME](frame) pallets. They can be called by internal or external entities
//! to interact with the blockchain's state. They are a core aspect of the runtime logic, handling
//! transactions and other state-changing operations.
//!
//! **Synonyms**: Callable
//!
//! #### Extrinsic
//!
//! An extrinsic is a general term for a piece of data that is originated outside of the runtime,
//! included into a block and leads to some action. This includes user-initiated transactions as
//! well as inherents which are placed into the block by the block-builder.
//!
//! #### Pallet
//!
//! Similar to software modules in traditional programming, [FRAME](frame) pallets in Substrate are
//! modular components that encapsulate distinct functionalities or business logic. Just as
//! libraries or modules are used to build and extend the capabilities of a software application,
//! pallets are the foundational building blocks for constructing a blockchain's runtime with frame.
//! They enable the creation of customizable and upgradeable networks, offering a composable
//! framework for a Substrate-based blockchain. Each pallet can be thought of as a plug-and-play
//! module, enhancing the blockchain's functionality in a cohesive and integrated manner.
//!
//! #### Full Node
//!
//! It is a node that prunes historical states, keeping only recent finalized block states to reduce
//! storage needs. Full nodes provide current chain state access and allow direct submission and
//! validation of extrinsics, maintaining network decentralization.
//!
//! #### Archive Node
//!
//! An archive node is a specialized node that maintains a complete history of all block states and
//! transactions. Unlike a full node, it does not prune historical data, ensuring full access to the
//! entire blockchain history. This makes it essential for detailed blockchain analysis and
//! historical queries, but requires significantly more storage capacity.
//!
//! #### Validator
//!
//! A validator is a node that participates in the consensus mechanism of the network.
//! Its role includes block production, transaction validation, network integrity and security
//! maintenance.
//!
//! #### Collator
//!
//! A collator is a node that is responsible for producing candidate blocks for the validators.
//! Collators are similar to validators on any other blockchain but, they do not need to provide
//! security guarantees as the Relay Chain handles this.
//!
//! #### Teyrchain
//!
//! Short for "parallelized chain" a teyrchain is a specialized blockchain that runs in parallel to
//! the Relay Chain (Pezkuwi, Kusama, etc.), benefiting from the shared security and
//! interoperability features of it.
//!
//! **Synonyms**: AppChain
//!
//! #### PVF
//! The Teyrchain Validation Function (PVF) is the current runtime Wasm for a teyrchain that is
//! stored on the Relay chain. It is an essential component in the Pezkuwi ecosystem, encapsulating
//! the validation logic for each teyrchain. The PVF is executed by validators to verify the
//! correctness of teyrchain blocks. This is critical for ensuring that each block follows the logic
//! set by its respective teyrchain, thus maintaining the integrity and security of the entire
//! network.
//!
//! **Synonyms**: Teyrchain Validation Function
+25
View File
@@ -0,0 +1,25 @@
//! # Metadata
//!
//! The existence of metadata in pezkuwi-sdk goes back to the (forkless) upgrade-ability of all
//! Substrate-based blockchains, which is achieved through
//! [`crate::reference_docs::wasm_meta_protocol`]. You can learn more about the details of how to
//! deal with these upgrades in [`crate::reference_docs::frame_runtime_upgrades_and_migrations`].
//!
//! Another consequence of upgrade-ability is that as a UI, wallet, or generally an offchain entity,
//! it is hard to know the types internal to the runtime, specifically in light of the fact that
//! they can change at any point in time.
//!
//! This is why all Substrate-based runtimes must expose a [`sp_api::Metadata`] api, which mandates
//! the runtime to return a description of itself. The return type of this api is `Vec<u8>`, meaning
//! that it is up to the runtime developer to decide on the format of this.
//!
//! All [`crate::pezkuwi_sdk::frame_runtime`] based runtimes expose a specific metadata language,
//! maintained in <https://github.com/paritytech/frame-metadata> which is adopted in the Pezkuwi
//! ecosystem.
//!
//! ## Metadata Explorers:
//!
//! A few noteworthy tools that inspect the (FRAME-based) metadata of a chain:
//!
//! - <https://wiki.network.pezkuwichain.io/docs/metadata>
//! - <https://paritytech.github.io/subxt-explorer/>
+116
View File
@@ -0,0 +1,116 @@
//! # Pezkuwi SDK Reference Docs.
//!
//! This is the entry point for all reference documents that enhance one's learning experience in
//! the Pezkuwi SDK.
//!
//! Note that this module also contains the [glossary](crate::reference_docs::glossary).
//!
//! ## What is a "reference document"?
//!
//! First, see [why we use rust-docs for everything](crate::meta_contributing#why-rust-docs) and our
//! documentation [principles](crate::meta_contributing#principles). We acknowledge that as much of
//! the crucial information should be embedded in the low level rust-docs. Then, high level
//! scenarios should be covered in [`crate::guides`]. Finally, we acknowledge that there is a
//! category of information that is:
//!
//! 1. Crucial to know.
//! 2. Is too high level to be in the rust-doc of any one `type`, `trait` or `fn`.
//! 3. Is too low level to be encompassed in a [`crate::guides`].
//!
//! We call this class of documents "reference documents". Our goal should be to minimize the number
//! of "reference" docs, as they incur maintenance burden.
/// Learn how Substrate and FRAME use traits and associated types to make modules generic in a
/// type-safe manner.
pub mod trait_based_programming;
/// Learn about the way Substrate and FRAME view their blockchains as state machines.
pub mod blockchain_state_machines;
/// The glossary.
pub mod glossary;
/// Learn about the WASM meta-protocol of all Substrate-based chains.
pub mod wasm_meta_protocol;
/// Learn about the differences between smart contracts and a FRAME-based runtime. They are both
/// "code stored onchain", but how do they differ?
pub mod runtime_vs_smart_contract;
/// Learn about how extrinsics are encoded to be transmitted to a node and stored in blocks.
pub mod extrinsic_encoding;
/// Deprecated in favor of transaction extensions.
pub mod signed_extensions;
/// Learn about the transaction extensions that form a part of extrinsics.
pub mod transaction_extensions;
/// Learn about *Origins*, a topic in FRAME that enables complex account abstractions to be built.
pub mod frame_origin;
/// Learn about the details of what derives are needed for a type to be store-able in `frame`
/// storage.
pub mod frame_storage_derives;
/// Learn about how to write safe and defensive code in your FRAME runtime.
pub mod defensive_programming;
/// Learn about composite enums and other runtime level types, such as `RuntimeEvent` and
/// `RuntimeCall`.
pub mod frame_runtime_types;
/// Learn about how to make a pallet/runtime that is fee-less and instead uses another mechanism to
/// control usage and sybil attacks.
pub mod fee_less_runtime;
/// Learn about metadata, the main means through which an upgradeable runtime communicates its
/// properties to the outside world.
pub mod metadata;
/// Learn about how to add custom host functions to the node.
pub mod custom_host_functions;
/// Learn about how frame-system handles `account-ids`, nonces, consumers and providers.
pub mod frame_system_accounts;
/// Advice for configuring your development environment for Substrate development.
pub mod development_environment_advice;
/// Learn about benchmarking and weight.
pub mod frame_benchmarking_weight;
/// Learn about the token-related logic in FRAME and how to apply it to your use case.
pub mod frame_tokens;
/// Learn about chain specification file and the genesis state of the blockchain.
pub mod chain_spec_genesis;
/// Learn about Substrate's CLI, and how it can be extended.
pub mod cli;
/// Learn about Runtime Upgrades and best practices for writing Migrations.
pub mod frame_runtime_upgrades_and_migrations;
/// Learn about the offchain workers, how they function, and how to use them, as provided by the
/// [`frame`] APIs.
pub mod frame_offchain_workers;
/// Learn about the different ways through which multiple [`frame`] pallets can be combined to work
/// together.
pub mod frame_pallet_coupling;
/// Learn about how to do logging in FRAME-based runtimes.
pub mod frame_logging;
/// Learn about the Pezkuwi Umbrella crate that re-exports all other crates.
pub mod umbrella_crate;
/// Learn about how to create custom RPC endpoints and runtime APIs.
pub mod custom_runtime_api_rpc;
/// The [`pezkuwi-omni-node`](https://crates.io/crates/polkadot-omni-node) and its related binaries.
pub mod omni_node;
/// Learn about the state in Substrate.
pub mod state;
+201
View File
@@ -0,0 +1,201 @@
//! # (Omni) Node
//!
//! This reference doc elaborates on what a Pezkuwi-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
//! > [`pezkuwi_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/pezkuwichain/pezkuwi-sdk/issues/97) 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
//! [`pezkuwi-omni-node`] is an attempt at creating the former, and the [`pezkuwi_omni_node_lib`]
//! is the latter.
//!
//! > Note: The OmniNodes are mainly focused on the development needs of **Pezkuwi
//! > teyrchains 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.
//!
//! ### [`pezkuwi-omni-node`]
//!
//! [`pezkuwi-omni-node`] is a white-labeled binary, released as a part of Pezkuwi SDK that is
//! capable of meeting the needs of most Pezkuwi teyrchains.
//!
//! It can act as the collator of a teyrchain 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 teyrchain locally for testing and development.
//!
//! ### [`pezkuwi_omni_node_lib`]
//!
//! [`pezkuwi_omni_node_lib`] is the library version of the above, which can be used to create a
//! fresh teyrchain 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 Pezkuwi-SDK, wishing to
//! deploy a teyrchain into 3 categories:
//!
//! 1. **Use the [`pezkuwi-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 [`pezkuwi_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 [`teyrchain-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:
//! * `pezkuwi-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 `pezkuwi-sdk-teyrchain-template`.
//! * For production `pezkuwi-omni-node` can be used out of the box.
//! * For further customization [`pezkuwi_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<Block, AuraId> for Runtime {
//! fn slot_duration() -> sp_consensus_aura::SlotDuration {
//! ...
//! }
//! fn authorities() -> Vec<AuraId> {
//! ...
//! }
//! }
//! ```
//!
//! 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 pezkuwi-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, [`pezkuwi-omni-node`] only supports:
//!
//! * Teyrchain-based Aura consensus, with 6s async-backing block-time, and before full elastic
//! scaling). [`pezkuwi_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/pezkuwichain/pezkuwi-sdk/issues/143) future improvement to OmniNode
//! aims to make such checks automatic.
//!
//! ### Runtime conventions
//!
//! The Omni Node needs to make some assumptions about the runtime. During startup, the node fetches
//! the runtime metadata and asserts that the runtime represents a compatible teyrchain.
//! The checks are best effort and will generate warning level logs in the Omni Node log file on
//! failure.
//!
//! The list of checks may evolve in the future and for now only few rules are implemented:
//! * runtimes must define a type for [`cumulus-pallet-teyrchain-system`], which is recommended to
//! be named as `TeyrchainSystem`.
//! * runtimes must define a type for [`frame-system`] pallet, which is recommended to be named as
//! `System`. The configured [`block number`] here will be used by Omni Node to configure AURA
//! accordingly.
//!
//! [`templates`]: crate::pezkuwi_sdk::templates
//! [`teyrchain-template`]: https://github.com/pezkuwichain/pezkuwi-sdk-teyrchain-template
//! [`--dev-block-time`]: pezkuwi_omni_node_lib::cli::Cli::dev_block_time
//! [`pezkuwi-omni-node`]: https://crates.io/crates/polkadot-omni-node
//! [`chain-spec-builder`]: https://crates.io/crates/staging-chain-spec-builder
//! [`cumulus-pallet-teyrchain-system`]: https://docs.rs/cumulus-pallet-parachain-system/latest/cumulus_pallet_parachain_system/
//! [`frame-system`]: https://docs.rs/frame-system/latest/frame_system/
//! [`block number`]: https://docs.rs/frame-system/latest/frame_system/pallet/storage_types/struct.Number.html
@@ -0,0 +1,209 @@
//! # Runtime vs. Smart Contracts
//!
//! *TL;DR*: If you need to create a *Blockchain*, then write a runtime. If you need to create a
//! *DApp*, then write a Smart Contract.
//!
//! This is a comparative analysis of Substrate-based Runtimes and Smart Contracts, highlighting
//! their main differences. Our aim is to equip you with a clear understanding of how these two
//! methods of deploying on-chain logic diverge in their design, usage, and implications.
//!
//! Both Runtimes and Smart Contracts serve distinct purposes. Runtimes offer deep customization for
//! blockchain development, while Smart Contracts provide a more accessible approach for
//! decentralized applications. Understanding their differences is crucial in choosing the right
//! approach for a specific solution.
//!
//! ## Substrate
//! Substrate is a modular framework that enables the creation of purpose-specific blockchains. In
//! the Pezkuwi ecosystem you can find two distinct approaches for on-chain code execution:
//! [Runtime Development](#runtime-in-substrate) and [Smart Contracts](#smart-contracts).
//!
//! #### Smart Contracts in Substrate
//! Smart Contracts are autonomous, programmable constructs deployed on the blockchain.
//! In [FRAME](frame), Smart Contracts infrastructure is implemented by the
//! [`pallet_contracts`] for WASM-based contracts or the
//! [`pallet_evm`](https://github.com/polkadot-evm/frontier/tree/master/frame/evm) for EVM-compatible contracts. These pallets
//! enable Smart Contract developers to build applications and systems on top of a Substrate-based
//! blockchain.
//!
//! #### Runtime in Substrate
//! The Runtime is the state transition function of a Substrate-based blockchain. It defines the
//! rules for processing transactions and blocks, essentially governing the behavior and
//! capabilities of a blockchain.
//!
//! ## Comparative Table
//!
//! | Aspect | Runtime | Smart Contracts |
//! |-----------------------|-------------------------------------------------------------------------|----------------------------------------------------------------------|
//! | **Design Philosophy** | Core logic of a blockchain, allowing broad and deep customization. | Designed for DApps deployed on the blockchain runtime. |
//! | **Development Complexity** | Requires in-depth knowledge of Rust and Substrate. Suitable for complex blockchain architectures. | Easier to develop with knowledge of Smart Contract languages like Solidity or [ink!](https://use.ink/). |
//! | **Upgradeability and Flexibility** | Offers comprehensive upgradeability with migration logic and on-chain governance, allowing modifications to the entire blockchain logic without hard forks. | Less flexible in upgrade migrations but offers more straightforward deployment and iteration. |
//! | **Performance and Efficiency** | More efficient, optimized for specific needs of the blockchain. | Can be less efficient due to its generic nature (e.g. the overhead of a virtual machine). |
//! | **Security Considerations** | Security flaws can affect the entire blockchain. | Security risks usually localized to the individual contract. |
//! | **Weighing and Metering** | Operations can be weighed, allowing for precise benchmarking. | Execution is metered, allowing for measurement of resource consumption. |
//!
//! We will now explore these differences in more detail.
//!
//! ## Design Philosophy
//! Runtimes and Smart Contracts are designed for different purposes. Runtimes are the core logic
//! of a blockchain, while Smart Contracts are designed for DApps on top of the blockchain.
//! Runtimes can be more complex, but also more flexible and efficient, while Smart Contracts are
//! easier to develop and deploy.
//!
//! #### Runtime Design Philosophy
//! - **Core Blockchain Logic**: Runtimes are essentially the backbone of a blockchain. They define
//! the fundamental rules, operations, and state transitions of the blockchain network.
//! - **Broad and Deep Customization**: Runtimes allow for extensive customization and flexibility.
//! Developers can tailor the most fundamental aspects of the blockchain, like introducing an
//! efficient transaction fee model to eliminating transaction fees completely. This level of
//! control is essential for creating specialized or application-specific blockchains.
//!
//! #### Smart Contract Design Philosophy
//! - **DApps Development**: Smart contracts are designed primarily for developing DApps. They
//! operate on top of the blockchain's infrastructure.
//! - **Modularity and Isolation**: Smart contracts offer a more modular approach. Each contract is
//! an isolated piece of code, executing predefined operations when triggered. This isolation
//! simplifies development and enhances security, as flaws in one contract do not directly
//! compromise the entire network.
//!
//! ## Development Complexity
//! Runtimes and Smart Contracts differ in their development complexity, largely due to their
//! differing purposes and technical requirements.
//!
//! #### Runtime Development Complexity
//! - **In-depth Knowledge Requirements**: Developing a Runtime in Substrate requires a
//! comprehensive understanding of Rust, Substrate's framework, and blockchain principles.
//! - **Complex Blockchain Architectures**: Runtime development is suitable for creating complex
//! blockchain architectures. Developers must consider aspects like security, scalability, and
//! network efficiency.
//!
//! #### Smart Contract Development Complexity
//! - **Accessibility**: Smart Contract development is generally more accessible, especially for
//! those already familiar with programming concepts. Knowledge of smart contract-specific
//! languages like Solidity or ink! is required.
//! - **Focused on Application Logic**: The development here is focused on the application logic
//! only. This includes writing functions that execute when certain conditions are met, managing
//! state within the contract, and ensuring security against common Smart Contract
//! vulnerabilities.
//!
//! ## Upgradeability and Flexibility
//! Runtimes and Smart Contracts differ significantly in how they handle upgrades and flexibility,
//! each with its own advantages and constraints. Runtimes are more flexible, allowing for writing
//! migration logic for upgrades, while Smart Contracts are less flexible but offer easier
//! deployment and iteration.
//!
//! #### Runtime Upgradeability and Flexibility
//! - **Migration Logic**: One of the key strengths of runtime development is the ability to define
//! migration logic. This allows developers to implement changes in the state or structure of the
//! blockchain during an upgrade. Such migrations can adapt the existing state to fit new
//! requirements or features seamlessly.
//! - **On-Chain Governance**: Upgrades in a Runtime environment are typically governed on-chain,
//! involving validators or a governance mechanism. This allows for a democratic and transparent
//! process for making substantial changes to the blockchain.
//! - **Broad Impact of Changes**: Changes made in Runtime affect the entire blockchain. This gives
//! developers the power to introduce significant improvements or changes but also necessitates a
//! high level of responsibility and scrutiny, we will talk further about it in the [Security
//! Considerations](#security-considerations) section.
//!
//! #### Smart Contract Upgradeability and Flexibility
//! - **Deployment and Iteration**: Smart Contracts, by nature, are designed for more
//! straightforward deployment and iteration. Developers can quickly deploy contracts.
//! - **Contract Code Updates**: Once deployed, although typically immutable, Smart Contracts can be
//! upgraded, but lack of migration logic. The [`pallet_contracts`]
//! allows for contracts to be upgraded by exposing the `set_code` dispatchable. More details on this
//! can be found in [Ink! documentation on upgradeable contracts](https://use.ink/basics/upgradeable-contracts).
//! - **Isolated Impact**: Upgrades or changes to a smart contract generally impact only that
//! contract and its users, unlike Runtime upgrades that have a network-wide effect.
//! - **Simplicity and Rapid Development**: The development cycle for Smart Contracts is usually
//! faster and less complex than Runtime development, allowing for rapid prototyping and
//! deployment.
//!
//! ## Performance and Efficiency
//! Runtimes and Smart Contracts have distinct characteristics in terms of performance and
//! efficiency due to their inherent design and operational contexts. Runtimes are more efficient
//! and optimized for specific needs, while Smart Contracts are more generic and less efficient.
//!
//! #### Runtime Performance and Efficiency
//! - **Optimized for Specific Needs**: Runtime modules in Substrate are tailored to meet the
//! specific needs of the blockchain. They are integrated directly into the blockchain's core,
//! allowing them to operate with high efficiency and minimal overhead.
//! - **Direct Access to Blockchain State**: Runtime has direct access to the blockchain's state.
//! This direct access enables more efficient data processing and transaction handling, as there
//! is no additional layer between the runtime logic and the blockchain's core.
//! - **Resource Management**: Resource management is integral to runtime development to ensure that
//! the blockchain operates smoothly and efficiently.
//!
//! #### Smart Contract Performance and Efficiency
//! - **Generic Nature and Overhead**: Smart Contracts, particularly those running in virtual
//! machine environments, can be less efficient due to the generic nature of their execution
//! environment. The overhead of the virtual machine can lead to increased computational and
//! resource costs.
//! - **Isolation and Security Constraints**: Smart Contracts operate in an isolated environment to
//! ensure security and prevent unwanted interactions with the blockchain's state. This isolation,
//! while crucial for security, can introduce additional computational overhead.
//! - **Gas Mechanism and Metering**: The gas mechanism in Smart Contracts, used for metering
//! computational resources, ensures that contracts don't consume excessive resources. However,
//! this metering itself requires computational power, adding to the overall cost of contract
//! execution.
//!
//! ## Security Considerations
//! These two methodologies, while serving different purposes, come with their own unique security
//! considerations.
//!
//! #### Runtime Security Aspects
//! Runtimes, being at the core of blockchain functionality, have profound implications for the
//! security of the entire network:
//!
//! - **Broad Impact**: Security flaws in the runtime can compromise the entire blockchain,
//! affecting all network participants.
//! - **Governance and Upgradeability**: Runtime upgrades, while powerful, need rigorous governance
//! and testing to ensure security. Improperly executed upgrades can introduce vulnerabilities or
//! disrupt network operations.
//! - **Complexity and Expertise**: Developing and maintaining runtime requires a higher level of
//! expertise in blockchain architecture and security, as mistakes can be far-reaching.
//!
//! #### Smart Contract Security Aspects
//! Smart contracts, while more isolated, bring their own set of security challenges:
//!
//! - **Isolated Impact**: Security issues in a smart contract typically affect the contract itself
//! and its users, rather than the whole network.
//! - **Contract-specific Risks**: Common issues like reentrancy
//! attacks, improper handling of external calls, and gas limit vulnerabilities are specific to
//! smart contract development.
//! - **Permissionless Deployment**: Since anyone can deploy a smart contract,
//! the ecosystem is more open to potentially malicious or vulnerable code.
//!
//! ## Weighing and Metering
//! Weighing and metering are mechanisms designed to limit the resources used by external actors.
//! However, there are fundamental differences in how these resources are handled in FRAME-based
//! Runtimes and how they are handled in Smart Contracts, while Runtime operations are weighed,
//! Smart Contract executions must be metered.
//!
//! #### Weighing
//! In FRAME-based Runtimes, operations are *weighed*. This means that each operation in the Runtime
//! has a fixed upper cost, known in advance, determined through
//! [benchmarking](crate::reference_docs::frame_benchmarking_weight). Weighing is practical here
//! because:
//!
//! - *Predictability*: Runtime operations are part of the blockchain's core logic, which is static
//! until an upgrade occurs. This predictability allows for precise
//! [benchmarking](crate::reference_docs::frame_benchmarking_weight).
//! - *Prevention of Abuse*: By having a fixed upper cost that corresponds to the worst-case
//! complexity scenario of its execution (and a mechanism to refund unused weight), it becomes
//! infeasible for an attacker to create transactions that could unpredictably consume excessive
//! resources.
//!
//! #### Metering
//! For Smart Contracts resource consumption is metered. This is essential due to:
//!
//! - **Untrusted Nature**: Unlike Runtime operations, Smart Contracts can be deployed by any user,
//! and their behavior isnt known in advance. Metering dynamically measures resource consumption
//! as the contract executes.
//! - **Safety Against Infinite Loops**: Metering protects the blockchain from poorly designed
//! contracts that might run into infinite loops, consuming an indefinite amount of resources.
//!
//! #### Implications for Developers and Users
//! - **For Runtime Developers**: Understanding the cost of each operation is essential. Misjudging
//! the weight of operations can lead to network congestion or vulnerability exploitation.
//! - **For Smart Contract Developers**: Being mindful of the gas cost associated with contract
//! execution is crucial. Efficiently written contracts save costs and are less likely to hit gas
//! limits, ensuring smoother execution on the blockchain.
@@ -0,0 +1,2 @@
//! `SignedExtension`s are deprecated in favor of
//! [`TransactionExtension`s](crate::reference_docs::transaction_extensions).
+12
View File
@@ -0,0 +1,12 @@
//! # State
//!
//! The state is abstracted as a key-value like database. Every item that
//! needs to be persisted by the [State Transition
//! Function](crate::reference_docs::blockchain_state_machines) is written to the state.
//!
//! ## Special keys
//!
//! The key-value pairs in the state are represented as byte sequences. The node
//! doesn't know how to interpret most the key-value pairs. However, there exist some
//! special keys and its values that are known to the node, the so-called
//! [`well-known-keys`](sp_storage::well_known_keys).
@@ -0,0 +1,229 @@
//! # Trait-based Programming
//!
//! This document walks you over a peculiar way of using Rust's `trait` items. This pattern is
//! abundantly used within [`frame`] and is therefore paramount important for a smooth transition
//! into it.
//!
//! The rest of this document assumes familiarity with the
//! [Rust book's Advanced Traits](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html)
//! section.
//! Moreover, we use the [`frame::traits::Get`].
//!
//! First, imagine we are writing a FRAME pallet. We represent this pallet with a `struct Pallet`,
//! and this pallet wants to implement the functionalities of that pallet, for example a simple
//! `transfer` function. For the sake of education, we are interested in having a `MinTransfer`
//! amount, expressed as a [`frame::traits::Get`], which will dictate what is the minimum amount
//! that can be transferred.
//!
//! We can foremost write this as simple as the following snippet:
#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", basic)]
//!
//!
//! In this example, we use arbitrary choices for `AccountId`, `Balance` and the `MinTransfer` type.
//! This works great for **one team's purposes** but we have to remember that Substrate and FRAME
//! are written as generic frameworks, intended to be highly configurable.
//!
//! In a broad sense, there are two avenues in exposing configurability:
//!
//! 1. For *values* that need to be generic, for example `MinTransfer`, we attach them to the
//! `Pallet` struct as fields:
//!
//! ```
//! struct Pallet {
//! min_transfer: u128,
//! }
//! ```
//!
//! 2. For *types* that need to be generic, we would have to use generic or associated types, such
//! as:
//!
//! ```
//! struct Pallet<AccountId> {
//! min_transfer: u128,
//! _marker: std::marker::PhantomData<AccountId>,
//! }
//! ```
//!
//! Substrate and FRAME, for various reasons (performance, correctness, type safety) has opted to
//! use *types* to declare both *values* and *types* as generic. This is the essence of why the
//! `Get` trait exists.
//!
//! This would bring us to the second iteration of the pallet, which would look like:
#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", generic)]
//!
//! In this example, we managed to make all 3 of our types generic. Taking the example of the
//! `AccountId`, one should read the above as following:
//!
//! > The `Pallet` does not know what type `AccountId` concretely is, but it knows that it is
//! > something that adheres to being `From<[u8; 32]>`.
//!
//! This method would work, but it suffers from two downsides:
//!
//! 1. It is verbose, each `impl` block would have to reiterate all of the trait bounds.
//! 2. It cannot easily share/inherit generic types. Imagine multiple pallets wanting to be generic
//! over a single `AccountId`. There is no easy way to express that in this model.
//!
//! Finally, this brings us to using traits and associated types on traits to express the above.
//! Trait associated types have the benefit of:
//!
//! 1. Being less verbose, as in effect they can *group multiple `type`s together*.
//! 2. Can inherit from one another by declaring
//! [supertraits](https://doc.rust-lang.org/rust-by-example/trait/supertraits.html).
//!
//! > Interestingly, one downside of associated types is that declaring defaults on them is not
//! > stable yet. In the meantime, we have built our own custom mechanics around declaring defaults
//! for associated types, see [`pallet_default_config_example`].
//!
//! The last iteration of our code would look like this:
#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", trait_based)]
//!
//! Notice how instead of having multiple generics, everything is generic over a single `<T:
//! Config>`, and all types are fetched through `T`, for example `T::AccountId`, `T::MinTransfer`.
//!
//! Finally, imagine all pallets wanting to be generic over `AccountId`. This can be achieved by
//! having individual `trait Configs` declare a shared `trait SystemConfig` as their
//! [supertrait](https://doc.rust-lang.org/rust-by-example/trait/supertraits.html).
#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", with_system)]
//! In FRAME, this shared supertrait is [`frame::prelude::frame_system`].
//!
//! Notice how this made no difference in the syntax of the rest of the code. `T::AccountId` is
//! still a valid type, since `T` implements `Config` and `Config` implies `SystemConfig`, which
//! has a `type AccountId`.
//!
//! Note, in some instances one would need to use what is known as the fully-qualified-syntax to
//! access a type to help the Rust compiler disambiguate.
#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", fully_qualified)]
//!
//! This syntax can sometimes become more complicated when you are dealing with nested traits.
//! Consider the following example, in which we fetch the `type Balance` from another trait
//! `CurrencyTrait`.
#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", fully_qualified_complicated)]
//!
//! Notice the final `type BalanceOf` and how it is defined. Using such aliases to shorten the
//! length of fully qualified syntax is a common pattern in FRAME.
//!
//! The above example is almost identical to the well-known (and somewhat notorious) `type
//! BalanceOf` that is often used in the context of [`frame::traits::fungible`].
#![doc = docify::embed!("../../substrate/frame/fast-unstake/src/types.rs", BalanceOf)]
//!
//! ## Additional Resources
//!
//! - <https://github.com/paritytech/substrate/issues/13836>
//! - [Substrate Seminar - Traits and Generic Types](https://www.youtube.com/watch?v=6cp10jVWNl4)
//! - <https://exchange.pezkuwichain.app/questions/2228/type-casting-to-trait-t-as-config>
#![allow(unused)]
use frame::traits::Get;
#[docify::export]
mod basic {
struct Pallet;
type AccountId = frame::deps::sp_runtime::AccountId32;
type Balance = u128;
type MinTransfer = frame::traits::ConstU128<10>;
impl Pallet {
fn transfer(_from: AccountId, _to: AccountId, _amount: Balance) {
todo!()
}
}
}
#[docify::export]
mod generic {
use super::*;
struct Pallet<AccountId, Balance, MinTransfer> {
_marker: std::marker::PhantomData<(AccountId, Balance, MinTransfer)>,
}
impl<AccountId, Balance, MinTransfer> Pallet<AccountId, Balance, MinTransfer>
where
Balance: frame::traits::AtLeast32BitUnsigned,
MinTransfer: frame::traits::Get<Balance>,
AccountId: From<[u8; 32]>,
{
fn transfer(_from: AccountId, _to: AccountId, amount: Balance) {
assert!(amount >= MinTransfer::get());
unimplemented!();
}
}
}
#[docify::export]
mod trait_based {
use super::*;
trait Config {
type AccountId: From<[u8; 32]>;
type Balance: frame::traits::AtLeast32BitUnsigned;
type MinTransfer: frame::traits::Get<Self::Balance>;
}
struct Pallet<T: Config>(std::marker::PhantomData<T>);
impl<T: Config> Pallet<T> {
fn transfer(_from: T::AccountId, _to: T::AccountId, amount: T::Balance) {
assert!(amount >= T::MinTransfer::get());
unimplemented!();
}
}
}
#[docify::export]
mod with_system {
use super::*;
pub trait SystemConfig {
type AccountId: From<[u8; 32]>;
}
pub trait Config: SystemConfig {
type Balance: frame::traits::AtLeast32BitUnsigned;
type MinTransfer: frame::traits::Get<Self::Balance>;
}
pub struct Pallet<T: Config>(std::marker::PhantomData<T>);
impl<T: Config> Pallet<T> {
fn transfer(_from: T::AccountId, _to: T::AccountId, amount: T::Balance) {
assert!(amount >= T::MinTransfer::get());
unimplemented!();
}
}
}
#[docify::export]
mod fully_qualified {
use super::with_system::*;
// Example of using fully qualified syntax.
type AccountIdOf<T> = <T as SystemConfig>::AccountId;
}
#[docify::export]
mod fully_qualified_complicated {
use super::with_system::*;
trait CurrencyTrait {
type Balance: frame::traits::AtLeast32BitUnsigned;
fn more_stuff() {}
}
trait Config: SystemConfig {
type Currency: CurrencyTrait;
}
struct Pallet<T: Config>(std::marker::PhantomData<T>);
impl<T: Config> Pallet<T> {
fn transfer(
_from: T::AccountId,
_to: T::AccountId,
_amount: <<T as Config>::Currency as CurrencyTrait>::Balance,
) {
unimplemented!();
}
}
/// A common pattern in FRAME.
type BalanceOf<T> = <<T as Config>::Currency as CurrencyTrait>::Balance;
}
@@ -0,0 +1,105 @@
//! Transaction extensions are, briefly, a means for different chains to extend the "basic"
//! extrinsic format with custom data that can be checked by the runtime.
//!
//! # FRAME provided transaction extensions
//!
//! FRAME by default already provides the following transaction extensions:
//!
//! - [`CheckGenesis`](frame_system::CheckGenesis): Ensures that a transaction was sent for the same
//! network. Determined based on genesis.
//!
//! - [`CheckMortality`](frame_system::CheckMortality): Extends a transaction with a configurable
//! mortality.
//!
//! - [`CheckNonZeroSender`](frame_system::CheckNonZeroSender): Ensures that the sender of a
//! transaction is not the *all zero account* (all bytes of the accountid are zero).
//!
//! - [`CheckNonce`](frame_system::CheckNonce): Extends a transaction with a nonce to prevent replay
//! of transactions and to provide ordering of transactions.
//!
//! - [`CheckSpecVersion`](frame_system::CheckSpecVersion): Ensures that a transaction was built for
//! the currently active runtime.
//!
//! - [`CheckTxVersion`](frame_system::CheckTxVersion): Ensures that the transaction signer used the
//! correct encoding of the call.
//!
//! - [`CheckWeight`](frame_system::CheckWeight): Ensures that the transaction fits into the block
//! before dispatching it.
//!
//! - [`ChargeTransactionPayment`](pallet_transaction_payment::ChargeTransactionPayment): Charges
//! transaction fees from the signer based on the weight of the call using the native token.
//!
//! - [`ChargeAssetTxPayment`](pallet_asset_tx_payment::ChargeAssetTxPayment): Charges transaction
//! fees from the signer based on the weight of the call using any supported asset (including the
//! native token).
//!
//! - [`ChargeAssetTxPayment`(using
//! conversion)](pallet_asset_conversion_tx_payment::ChargeAssetTxPayment): Charges transaction
//! fees from the signer based on the weight of the call using any supported asset (including the
//! native token). The asset is converted to the native token using a pool.
//!
//! - [`SkipCheckIfFeeless`](pallet_skip_feeless_payment::SkipCheckIfFeeless): Allows transactions
//! to be processed without paying any fee. This requires that the `call` that should be
//! dispatched is augmented with the [`feeless_if`](frame_support::pallet_macros::feeless_if)
//! attribute.
//!
//! - [`CheckMetadataHash`](frame_metadata_hash_extension::CheckMetadataHash): Extends transactions
//! to include the so-called metadata hash. This is required by chains to support the generic
//! Ledger application and other similar offline wallets.
//!
//! - [`WeightReclaim`](frame_system::WeightReclaim): A transaction extension for the relay chain
//! that reclaims unused weight after executing a transaction.
//!
//! - [`StorageWeightReclaim`](cumulus_pallet_weight_reclaim::StorageWeightReclaim): A transaction
//! extension for teyrchains that reclaims unused storage weight after executing a transaction.
//!
//! For more information about these extensions, follow the link to the type documentation.
//!
//! # Building a custom transaction extension
//!
//! Defining a couple of very simple transaction extensions looks like the following:
#![doc = docify::embed!("./src/reference_docs/transaction_extensions.rs", transaction_extensions_example)]
#[docify::export]
pub mod transaction_extensions_example {
use codec::{Decode, DecodeWithMemTracking, Encode};
use scale_info::TypeInfo;
use sp_runtime::{
impl_tx_ext_default,
traits::{Dispatchable, TransactionExtension},
transaction_validity::TransactionValidityError,
};
// This doesn't actually check anything, but simply allows
// some arbitrary `u32` to be added to the extrinsic payload
#[derive(Debug, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
pub struct AddToPayload(pub u32);
impl<Call: Dispatchable> TransactionExtension<Call> for AddToPayload {
const IDENTIFIER: &'static str = "AddToPayload";
type Implicit = ();
type Pre = ();
type Val = ();
impl_tx_ext_default!(Call; weight validate prepare);
}
// This is the opposite; nothing will be added to the extrinsic payload,
// but the Implicit type (`1234u32`) will be added to the
// payload to be signed.
#[derive(Debug, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
pub struct AddToSignaturePayload;
impl<Call: Dispatchable> TransactionExtension<Call> for AddToSignaturePayload {
const IDENTIFIER: &'static str = "AddToSignaturePayload";
type Implicit = u32;
fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
Ok(1234)
}
type Pre = ();
type Val = ();
impl_tx_ext_default!(Call; weight validate prepare);
}
}
+91
View File
@@ -0,0 +1,91 @@
//! # Umbrella Crate
//!
//! The Pezkuwi-SDK "umbrella" is a crate that re-exports all other published crates. This makes it
//! possible to have a very small `Cargo.toml` file that only has one dependency, the umbrella
//! 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.
//! There are two main features: `runtime` and `node`. The `runtime` feature enables all `no-std`
//! crates, while the `node` feature enables all `std` crates. It should be used like any other
//! crate in the repo, with `default-features = false`.
//!
//! For more fine-grained control, additionally, each crate can be enabled selectively. The umbrella
//! exposes one feature per dependency. For example, if you only want to use the `frame-support`
//! crate, you can enable the `frame-support` feature.
//!
//! The umbrella exposes a few more general features:
//! - `tuples-96`: Needs to be enabled for runtimes that have more than 64 pallets.
//! - `serde`: Specifically enable `serde` en/decoding support.
//! - `experimental`: Experimental enable experimental features - should not yet used in production.
//! - `with-tracing`: Enable tracing support.
//! - `try-runtime`, `runtime-benchmarks` and `std`: These follow the standard conventions.
//! - `runtime`: As described above, enable all `no-std` crates.
//! - `node`: As described above, enable all `std` crates.
//! - There does *not* exist a dedicated docs feature. To generate docs, enable the `runtime` and
//! `node` feature. For `docs.rs` the manifest contains specific configuration to make it show up
//! all re-exports.
//!
//! There is a specific [`zepter`](https://github.com/ggwpez/zepter) check in place to ensure that
//! the features of the umbrella are correctly configured. This check is run in CI and locally when
//! running `zepter`.
//!
//! ## Generation
//!
//! The umbrella crate needs to be updated every time when a new crate is added or removed from the
//! workspace. It is checked in CI by calling its generation script. The generation script is
//! located in `./scripts/generate-umbrella.py` and needs dependency `cargo_workspace`.
//!
//! Example: `python3 scripts/generate-umbrella.py --sdk . --version 1.9.0`
//!
//! ## Usage
//!
//! > Note: You can see a live example in the `staging-node-cli` and `kitchensink-runtime` crates.
//!
//! The umbrella crate can be added to your runtime crate like this:
//!
//! `pezkuwi-sdk = { path = "../../../../umbrella", features = ["runtime"], default-features =
//! false }`
//!
//! or for a node:
//!
//! `pezkuwi-sdk = { path = "../../../../umbrella", features = ["node"], default-features = false
//! }`
//!
//! In the code, it is then possible to bring all dependencies into scope via:
//!
//! `use pezkuwi_sdk::*;`
//!
//! ### Known Issues
//!
//! The only known issue so far is the fact that the `use` statement brings the dependencies only
//! into the outer module scope - not the global crate scope. For example, the following code would
//! need to be adjusted:
//!
//! ```rust
//! use pezkuwi_sdk::*;
//!
//! mod foo {
//! // This does sadly not compile:
//! frame_support::parameter_types! { }
//!
//! // Instead, we need to do this (or add an equivalent `use` statement):
//! pezkuwi_sdk::frame_support::parameter_types! { }
//! }
//! ```
//!
//! Apart from this, no issues are known. There could be some bugs with how macros locate their own
//! re-exports. Please [report issues](https://github.com/pezkuwichain/pezkuwi-sdk/issues) that arise from using this crate.
//!
//! ## Dependencies
//!
//! The umbrella crate re-exports all published crates, with a few exceptions:
//! - Runtime crates like `pezkuwichain-runtime` etc are not exported. This otherwise leads to very
//! weird compile errors and should not be needed anyway.
//! - Example and fuzzing crates are not exported. This is currently detected by checking the name
//! of the crate for these magic words. In the future, it will utilize custom metadata, as it is
//! done in the `pezkuwichain-runtime` crate.
//! - The umbrella crate itself. Should be obvious :)
@@ -0,0 +1,158 @@
//! # WASM Meta Protocol
//!
//! All Substrate based chains adhere to a unique architectural design novel to the Pezkuwi
//! ecosystem. We refer to this design as the "**WASM Meta Protocol**".
//!
//! Consider the fact that a traditional blockchain software is usually a monolithic artifact.
//! **Upgrading any part of the system implies upgrading the entire system**. This has historically
//! led to cumbersome forkful upgrades to be the status quo in blockchain ecosystems. In other
//! words, the entire node software is the specification of the blockchain's [`state transition
//! function`](crate::reference_docs::blockchain_state_machines).
//!
//! Moreover, the idea of "storing code in the state" is explored in the context of smart contracts
//! platforms, but has not been expanded further.
//!
//! Substrate mixes these two ideas together, and takes the novel approach of storing the
//! blockchain's main "state transition function" in the main blockchain state, in the same fashion
//! that a smart contract platform stores the code of individual contracts in its state. As noted in
//! [`crate::reference_docs::blockchain_state_machines`], this state transition function is called
//! the **Runtime**, and WASM is chosen as the bytecode. The Runtime is stored under a special key
//! in the state (see [`sp_core::storage::well_known_keys`]) and can be updated as a part of the
//! state transition function's execution, just like a user's account balance can be updated.
//!
//! > Note that while we drew an analogy between smart contracts and runtimes in the above, there
//! > are fundamental differences between the two, explained in
//! > [`crate::reference_docs::runtime_vs_smart_contract`].
//!
//! The rest of the system that is NOT the state transition function is called the
//! [**Node**](crate::reference_docs::glossary#node), and is a normal binary that is compiled from
//! Rust to different hardware targets.
//!
//! This design enables all Substrate-based chains to be fork-less-ly upgradeable, because the
//! Runtime can be updated on the fly, within the execution of a block, and the node is (for the
//! most part) oblivious to the change that is happening.
//!
//! Therefore, the high-level architecture of a any Substrate-based chain can be demonstrated as
//! follows:
#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_simple.mmd")]
//!
//! The node and the runtime need to communicate. This is done through two concepts:
//!
//! 1. **Host functions**: a way for the (WASM) runtime to talk to the node. All host functions are
//! defined in [`sp_io`]. For example, [`sp_io::storage`] are the set of host functions that
//! allow the runtime to read and write data to the on-chain state.
//! 2. **Runtime APIs**: a way for the node to talk to the WASM runtime. Runtime APIs are defined
//! using macros and utilities in [`sp_api`]. For example, [`sp_api::Core`] is the most
//! fundamental runtime API that any blockchain must implement in order to be able to (re)
//! execute blocks.
#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_client_runtime.mmd")]
//!
//! A runtime must have a set of runtime APIs in order to have any meaningful blockchain
//! functionality, but it can also expose more APIs. See
//! [`crate::reference_docs::custom_runtime_api_rpc`] as an example of how to add custom runtime
//! APIs to your FRAME-based runtime.
//!
//! Similarly, for a runtime to be "compatible" with a node, the node must implement the full set of
//! host functions that the runtime at any point in time requires. Given the fact that a runtime can
//! evolve in time, and a blockchain node (typically) wishes to be capable of re-executing all the
//! previous blocks, this means that a node must always maintain support for the old host functions.
//! **This implies that adding a new host function is a big commitment and should be done with
//! care**. This is why, for example, adding a new host function to Pezkuwi always requires an RFC.
//! Learn how to add a new host function to your runtime in
//! [`crate::reference_docs::custom_host_functions`].
//!
//! ## Node vs. Runtime
//!
//! A common question is: which components of the system end up being part of the node, and which
//! ones of the runtime?
//!
//! Recall from [`crate::reference_docs::blockchain_state_machines`] that the runtime is the state
//! transition function. Anything that needs to influence how your blockchain's state is updated,
//! should be a part of the runtime. For example, the logic around currency, governance, identity or
//! any other application-specific logic that has to do with the state is part of the runtime.
//!
//! Anything that does not have to do with the state-transition function and will only
//! facilitate/enable it is part of the node. For example, the database, networking, and even
//! consensus algorithm are all node-side components.
//!
//! > The consensus is to your runtime what HTTP is to a web-application. It is the underlying
//! > engine that enables trustless execution of the runtime in a distributed manner whilst
//! > maintaining a canonical outcome of that execution.
#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_with_frame.mmd")]
//!
//! ## State
//!
//! From the previous sections, we know that the database component is part of the node, not the
//! runtime. We also hinted that a set of host functions ([`sp_io::storage`]) are how the runtime
//! issues commands to the node to read/write to the state. Let's dive deeper into this.
//!
//! The state of the blockchain, what we seek to come to consensus about, is indeed *kept* in the
//! node side. Nonetheless, the runtime is the only component that:
//!
//! 1. Can update the state.
//! 2. Can fully interpret the state.
//!
//! In fact, [`sp_core::storage::well_known_keys`] are the only state keys that the node side is
//! aware of. The rest of the state, including what logic the runtime has, what balance each user
//! has and such, are all only comprehensible to the runtime.
#![doc = simple_mermaid::mermaid!("../../../mermaid/state.mmd")]
//!
//! In the above diagram, all of the state keys and values are opaque bytes to the node. The node
//! does not know what they mean, and it does not know what is the type of the corresponding value
//! (e.g. if it is a number of a vector). Contrary, the runtime knows both the meaning of their
//! keys, and the type of the values.
//!
//! This opaque-ness is the fundamental reason why Substrate-based chains can fork-less-ly upgrade:
//! because the node side code is kept oblivious to all of the details of the state transition
//! function. Therefore, the state transition function can freely upgrade without the node needing
//! to know.
//!
//! ## Native Runtime
//!
//! Historically, the node software also kept a native copy of the runtime at the time of
//! compilation within it. This used to be called the "Native Runtime". The main purpose of the
//! native runtime used to be leveraging the faster execution time and better debugging
//! infrastructure of native code. However, neither of the two arguments strongly hold and the
//! native runtime is being fully removed from the node-sdk.
//!
//! See: <https://github.com/pezkuwichain/pezkuwi-sdk/issues/97>
//!
//! > Also, note that the flags [`sc_cli::ExecutionStrategy::Native`] is already a noop and all
//! > chains built with Substrate only use WASM execution.
//!
//! ### Runtime Versions
//!
//! An important detail of the native execution worth learning about is that the node software,
//! obviously, only uses the native runtime if it is the same code as with the wasm blob stored
//! onchain. Else, nodes who run the native runtime will come to a different state transition. How
//! do nodes determine if two runtimes are the same? Through the very important
//! [`sp_version::RuntimeVersion`]. All runtimes expose their version via a runtime api
//! ([`sp_api::Core::version`]) that returns this struct. The node software, or other applications,
//! inspect this struct to examine the identity of a runtime, and to determine if two runtimes are
//! the same. Namely, [`sp_version::RuntimeVersion::spec_version`] is the main key that implies two
//! runtimes are the same.
//!
//! Therefore, it is utmost important to make sure before any runtime upgrade, the spec version is
//! updated.
//!
//! ## Example: Block Execution.
//!
//! As a final example to recap, let's look at how Substrate-based nodes execute blocks. Blocks are
//! received in the node side software as opaque blobs and in the networking layer.
//!
//! At some point, based on the consensus algorithm's rules, the node decides to import (aka.
//! *validate*) a block.
//!
//! * First, the node will fetch the state of the parent hash of the block that wishes to be
//! imported.
//! * The runtime is fetched from this state, and placed into a WASM execution environment.
//! * The [`sp_api::Core::execute_block`] runtime API is called and the block is passed in as an
//! argument.
//! * The runtime will then execute the block, and update the state accordingly. Any state update is
//! issued via the [`sp_io::storage`] host functions.
//! * Both the runtime and node will check the state-root of the state after the block execution to
//! match the one claimed in the block header.
//!
//! > Example taken from [this
//! > lecture](https://www.youtube.com/watch?v=v0cKuddbF_Q&list=PL-w_i5kwVqbkRmfDn5nzeuU1S_FFW8dDg&index=4)
//! > of the Pezkuwi Blockchain Academy.