Substrate官方教程梳理与练习

总览

Substrate TutorialsGet startedjump开始接触Substrate基础内容Build a local blockchainjump前置条件1. 有良好的互联网连接并可以访问本地计算机上的 shell 终端。2. 您相当(generally)熟悉软件开发并使用命令行界面。3. 相当熟悉区块链和智能合约平台。4. 你已经安装了 Rust 并按照安装中的描述设置了你的开发环境。目标内容1. 下载链端模版和前端模版- substrate-node-template- substrate-front-end-template2. 启动节点、启动前端查看3. 在前端进行转账操作Simulate a networkjump本教程提供了有关如何使用一组认证私有验证器启动私有区块链网络的基本介绍。Substrate节点模板使用认证共识模型,将块生产限制为授权账户轮换列表。授权账户(认证机构)负责以循环方式创建区块。在本教程中将通过使用两个预定义的帐户作为认证机制,使节点能够生成块,以此来了解权限共识模型在实践中的工作原理。在这个模拟网络中,两个节点使用不同的帐户和密钥启动,但在单台计算机上运行。前置条件完成上一节课1. 安装Rust和Rust工具链,为Substrate 开发配置了环境。2. 已完成构建本地区块链并在本地安装Substrate节点模板。3. 熟悉软件开发和使用命令行界面。4. 熟悉区块链和智能合约平台。目标内容1. 使用预定义的帐户启动区块链节点。2. 了解用于启动节点的关键命令行选项。3. 确定节点是否正在运行并产生块。4. 将第二个节点连接到正在运行的网络。5. 验证对等计算机(peer computers)产生并最终确定块。Add trusted nodesjump前置条件完成上一节课目标内容1. 生成用作网络授权的密钥对。2. 创建自定义链规范文件。3. 启动一个私有的两节点区块链网络。Authorize specific nodesjump前置条件1. 完成上一节课2. 熟悉libp2plibp2p目标内容1. 检查并编译节点模板。2. 将节点授权托盘(pallet)添加到节点模板运行时。3. 启动多个节点并授权新节点加入。monitor node metricsjump前置条件完成Build a local blockchain完成Simulate a network目标内容1. 安装 Prometheus 和 Grafana。2. 配置 Prometheus 以捕获 Substrate 节点的时间序列。3. 配置 Grafana 以可视化使用 Prometheus 端点收集的节点指标。Upgrade a running networkjump前置条件1. Build a local blockchain2. 从 Add a pallet to the runtime 了解如何添加pallet目标内容1. 使用 Sudo 托盘(sudo pallet)模拟链升级的治理(governance)。2. 升级运行节点的运行时以包含新的托盘。3. 为运行时安排升级。Work with palletsjump通过示例介绍pallets的结构和相关使用Add a pallet to the runtimejump前置条件1. Build a local blockchain目标内容1. 了解如何更新运行时依赖项以包含新托盘。2. 了解如何配置特定于托盘(pallet-specific)的 Rust 特征(trait)。3. 通过使用前端模板与新托盘交互来查看运行时的更改。Configure the contracts palletjump前置条件1. Build a local blockchain目标内容Use macros in a custom palletjump前置条件1. Build a local blockchain2. Simulate a network3. 需要1~2h编译运行目标内容1. 了解定制托盘的基本结构。2. 查看 Rust 宏如何简化需要编写的代码的示例。3. 启动一个包含自定义托盘的区块链节点。4. 添加暴露存在证明托盘的前端代码。Develop smart contractsjumpPrepare your first contractjump前置条件目标内容1. 了解如何创建智能合约项目。2. 使用ink!智能合约语言构建和测试智能合约。3. 在本地 Substrate 节点上部署智能合约。4. 通过浏览器与智能合约交互。Develop a smart contractjump前置条件1. Prepare your first contract目标内容1. 了解如何使用智能合约模板。2. 使用智能合约存储简单值。3. 使用智能合约增加和检索存储的值。4. 向智能合约添加公共和私有功能。Use maps for storing valuesjump前置条件目标内容Buid a token contractjump前置条件1. Prepare your first contract2. Develop a smart contract目标内容1. 了解 ERC-20 标准中定义的基本属性和接口。2. 创建符合 ERC-20 标准的代币。3. 在合约之间转移代币。4. 处理涉及批准或第三方的转移活动的路由。5. 创建与令牌活动相关的事件。Troubleshoot smart contractsjumpConnect with other chainsjumpStart a local relay chainjump前置条件1. Build a local blockchain2. Add trusted nodes3. 了解波卡的架构4. 了解平行链Atchitecture of PolkadotParachains目标内容1. 确认软件需求。2. 设置平行链(para chain)构建环境。3. 准备中继链(relay chain)规格。4. 在本地启动中继链。Connect a local parachainjump前置条件1. Start a local relay chain2. 注意与1的波卡版本一致,比如: polkadot-v0.9.24polkadot-v0.9.24/substrate-parachain-template目标内容1. 在中继链上为你的平行链注册一个 ParaID。2. 在中继链上开始生产平行链。Connect to Rococo testnetjump前置条件1. 回顾Add trusted nodes:- 如何生成并修改链规范文件- 如何生成和存储keys2. Connect a local parachain目标内容Access EVM accountsjump前置条件一、完成课程1. Build a local blockchain2. Add a pallet to the runtime3. Use macros in a custom pallet二、熟悉操作1. 启动一个 Substrate 区块链节点。2. 在运行时添加、移除和配置托盘。3. 通过使用 Polkadot-JS 或其他前端连接到节点来提交交易。三、掌握概念1. 以太坊核心概念和术语2. 以太坊虚拟机 (EVM) 基础知识3. 去中心化应用程序和智能合约4. 托盘设计原则目标内容

Get Started

Build a local blockchain

Build a local blockchain返回Compile a Substrate node:git clone https://github.com/substrate-developer-hub/substrate-node-templatecd substrate-node-template && git checkout latestcargo build --releaseStart the local node:./target/release/node-template --devInstall the front-end template:node --versionyarn --versionnpm install -g yarngit clone https://github.com/substrate-developer-hub/substrate-front-end-templatecd substrate-front-end-templateyarn installStart the front-end template:yarn startOpen http://localhost:8000 in a browser to view the front-end template.Transfer funds from an account

这里主要使用官方提供的默认模版启动节点。

一定要注意文档是否更新

由于rust对crate的版本只能检查,无法解决冲突问题,需要手动进行,所以一定要注意文档是否有更新,尤其是里面的代码

New Substrate documentation released · Issue #1132 · substrate-developer-hub/substrate-docs

另外,substrate官方文档也一直处在更新状态中。

设置开发环境

使用rustup设置rust环境

# 1.安装预编译包
sudo apt update && sudo apt install -y git clang curl libssl-dev llvm libudev-dev

# 2.安装Rust编译环境
curl https://sh.rustup.rs -sSf | sh
source ~/.cargo/env
rustup default stable
rustup update
rustup update nightly
rustup target add wasm32-unknown-unknown --toolchain nightly

检查环境

rustc --version
rustup show

启动链节点

node-template

node-template实际上是官方提供的使用substrate开发的模板链,可以理解为substrate官方提供的样例,后续任何人想使用substrate可以在这个样例的基础上进行修改,这样开发链就更方便。

这就好比以前的好多山寨链,在btc的源码上改下创世区块的配置,就是一条新链。那么substrate其实也一样,提供了node-template这样一个模板,后续根据需求在这个上面改吧改吧,就能产生一条新链。

下载node-template

git clone https://github.com/substrate-developer-hub/substrate-node-template
cd substrate-node-template
git checkout latest

⚠️注意查看最新分支的编号

/home/substrate-node-template on  #polkadot-v0.9.24

node-templeate项目结构

node-template项目结构

tree -L 2                                                                                                                                                                                                                              ─╯
.
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── docker-compose.yml
├── docs
│   └── rust-setup.md
├── node
│   ├── Cargo.toml
│   ├── build.rs
│   └── src
├── pallets
│   └── template
├── runtime
│   ├── Cargo.toml
│   ├── build.rs
│   └── src
├── rustfmt.toml
├── scripts
│   ├── docker_run.sh
│   └── init.sh
├── shell.nix
└── target
    ├── CACHEDIR.TAG
    └── release

10 directories, 15 files

Cargo.toml

[workspace]
members = [
    "node",
    "pallets/template",
    "runtime",
]
[profile.release]
panic = "unwind"

可见node-template主要包含三部分:node、pallets/template、runtime

编译前的检查

cargo check -p node-template-runtime

IDEA将会在修改Cargo.toml之后自动执行指令进行检查

cargo metadata --verbose --format-version 1 --all-features

Output JSON to stdout containing information about the workspace members and resolved dependencies of the current package.

It is recommended to include the –format-version flag to future-proof your code to ensure the output is in the format you are expecting.

编译

cargo build --release

这个过程比较慢,会下载并编译上面三部分内的Cargo.toml列出的所有包

可能遇到的问题

  • 安装cmake
brew install cmake

本地运行节点

./target/release/node-template --dev

docker运行节点

前端访问

Build a local blockchain | Substrate_ Docs

使用前端模版

node --version
yarn --version
npm install -g yarn
git clone https://github.com/substrate-developer-hub/substrate-front-end-template
cd substrate-front-end-template
yarn install
yarn start

启动后访问本地:http://localhost:8000

使用polkadot-js访问节点

polkadot-js-app

在substrate官方的教程中,是使用了substrate的前端模板来访问刚才启动的节点。但是在实际的开发中,后端人员其实更多的使用polkadot-js-app来访问我们的节点,所以这里我们也使用它来访问我们的节点。

  • 在浏览器中输入https://polkadot.js.org/apps, 点击左上角会展开;

CleanShot 2022-07-01 at 20.08.02@2x

  • 在展开的菜单中点击DEVELOPMENT;

  • 点击Local Node;

  • 点击switch。

CleanShot 2022-07-01 at 20.17.59@2x

Substrate使用方式

使用substrate的方式主要有以下几种:

使用subtrate node

开发者可以运行已经设计好的substrate节点,并配置genesis区块,在此方式下只需要提供一个json文件就可以启动自己的区块链。其实我们上一节的substrate初体验,也可以看成是使用此种方式的一个例子。

使用substrate frame

frame其实是一组模块(pallet)和支持库。使用substrate frame可以轻松的创建自己的自定义运行时,因为frame是用来构建底层节点的。使用frame还可以配置数据类型,也可以从模块库中选择甚至是添加自己定义的模块。

使用substrate core

使用substrate code运行开发者完全从头开始设计运行时(runtime,问题:什么是runtime?),当然此种方式也是使用substrate自由度最大的方式。

几种方式的关系可以用图描述如下:技术自由 vs 开发便利

Technical freedom vs development ease

Simulate a network

返回顶部

Simulate a network返回Start the first alice blockchain node清理之前alice的链数据:./target/release/node-template purge-chain --base-path /tmp/alice --chain localAre you sure to remove "/tmp/alice/chains/local_testnet/db"? [y/N]:启动alice节点./target/release/node-template--base-path /tmp/alice--chain local--alice--port 30333--ws-port 9945--rpc-port 9933--node-key 0000000000000000000000000000000000000000000000000000000000000001--telemetry-url "wss://telemetry.polkadot.io/submit/ 0"--validatorReview the command-line optionsFor more details:./target/release/node-template --help > the-command-line-options.txt:Review the node messages displayed🔨 Initializing Genesis block/state🏷 Local node identity isAdd a second node to the blockchain network现在开始使用 alice 帐户密钥的节点正在运行,接着可以使用 bob 帐户将另一个节点添加到网络中。因为要加入一个已经在运行的网络,所以可以使用正在运行的节点来识别新节点要加入的网络。这些命令与之前使用的命令相似,但有一些重要区别。1. --base-path2. --port3. --ws-port4. --rpc-port5. --bootnodes: 指定一个单独启动节点,这个节点来自于alias总共有4步,重点介绍后面两步1. 重新打开一个终端2. 进入substrate-node-template清除之前的链数据./target/release/node-template purge-chain--base-path /tmp/bob--chain local-y通过在命令中添加-y,可以在不提示确认操作的情况下删除链数据。启动第二个节点,用bob的账号./target/release/node-template--base-path /tmp/bob--chain local--bob--port 30334--ws-port 9946--rpc-port 9934--telemetry-url "wss://telemetry.polkadot.io/submit/ 0"--validator--bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp--base-path--bob--port--ws-port--rpc-port--bootnodesip4 表示节点的 IP 地址使用 IPv4 格式127.0.0.1 指定运行节点的 IP 地址,在这种情况下,本地主机的地址。tcp 将 TCP 指定为用于对等通信的协议。30333 指定用于点对点通信的端口号,在这种情况下,TCP 流量的端口号。12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp标识该网络要与之通信的运行节点。在这种情况下,节点的标识符开始使用 alice 帐户。Verify blocks are produced and finalized为了方便识别,还在每行前面加了标记:✌️  version 4.0.0-dev-9c89be106cb❤️  by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-2022📋 Chain specification: Local Testnet🏷  Node name: Bob👤 Role: AUTHORITY💾 Database: RocksDb at /tmp/bob/chains/local_testnet/db/full⛓  Native runtime: node-template-100 (node-template-1.tx1.au1)🔨 Initializing Genesis block/state (state: 0x0336…17a1, header-hash: 0x387f…a9b7)👴 Loading GRANDPA authority set from genesis on what appears to be first startup.Using default protocol ID "sup" because none is configured in the chain specs🏷  Local node identity is: 12D3KooWCPbSKhf9WggmGev8RBwzB5WKDNi9BjA8gjwsA4uDSkxN💻 Operating system: macos💻 CPU architecture: x86_64📦 Highest known block at #0🏷  Local node identity is: <encrypted account name>🔍 Discovered new external address for our nodeThe first node was started by alice:💤 Idle (1 peers), best... finalized...The node has a one peer (1 peers).The nodes have produced some blocks (best: #4 (0x2b8a…fdc4)).The blocks are being finalized (finalized #2 (0x8b6a…dce6)).🙌 Starting consensus session🎁 Prepared block for proposing at ...🔖 Pre-sealed block for proposal at ...✨ Imported #85 (0x5f7a…9b10)需要关注的标记有两个:🔍和💤🔍 Discovered new external address for our nodeThe first node was started by alice:注意:新版本已经更新,没有这个标记,改成如下:discovered: 12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp /ip4/172.16.0.79/tcp/30333这也和第一个alice节点启动的身份码一致💤 Idle (1 peers), best... finalized...The node has a one peer (1 peers).The nodes have produced some blocks (best: #4 (0x2b8a…fdc4)).

Add trusted nodes

(重点内容)Add trusted nodes返回1. Sr25519: 用于使用 aura 为一个节点生成块。2. Ed25519: 用于使用 grapdpa 为一个节点生成块。2. SS58: 对应公钥步骤:1. 使用Sr25519 -> 一个助记词和对应SS58公钥 -> aura2. 使用Ed25519+前面的助记词 -> Ed25519 公钥 -> grandpaAbout Substrate Consensus:The Substrate node template uses a proof of authority consensus modelalso referred to as authority round or Aura consensus .The Aura consensus protocol limits block production toa rotating list of authorized accounts.The authorized accounts—authorities—create blocks ina round robin fashion and are generally considered to betrusted participants in the network.This consensus model provides a simple approach tostarting a solo blockchain for a limited number of participants.In this tutorial, you'll see how to generate the keysrequired to authorize a node to participate in the network,how to configure and share information about the networkwith other authorized accounts, and how to launch the networkwith an approved set of validators.Generate your account and keysKey generation options:1. a node-template subcommand2. the standalone subkey command-line program3. the Polkadot-JS application4. third-party key generation utilities.Generate local keys using the node template:使用Sr25519 -> 一个助记词和对应SS58公钥 -> aura# Generate a random secret phrase and keys./target/release/node-template keygenerate--scheme Sr25519--password-interactiveKey password: <123456>Secret phrase:       answer cotton spike caution blouse only radio artefact middle guilt bleak originalNetwork ID:        substrateSecret seed:       0xfcd01919178fa73e7223bdeea134b1ef809b75d3fabd672a52dcc69b964813b6Public key (hex):  0x5413998d3c189f62288daaf2bd2ca3da5e78b00be9172a36ae063aae4cc7f607Account ID:        0x5413998d3c189f62288daaf2bd2ca3da5e78b00be9172a36ae063aae4cc7f607Public key (SS58): 5DxwhfEDto6kGkHz1SZQWE1hDGM8E2LFQNujQdJ3vHNWrnc3SS58 Address:      5DxwhfEDto6kGkHz1SZQWE1hDGM8E2LFQNujQdJ3vHNWrnc3您现在拥有 Sr25519 密钥,用于使用 aura 为一个节点生成块。在此示例中,帐户的 Sr25519 公钥是 5DxwhfEDto6kGkHz1SZQWE1hDGM8E2LFQNujQdJ3vHNWrnc3使用Ed25519+前面的助记词 -> Ed25519 公钥 -> grandpa./target/release/node-template keyinspect--scheme Ed25519--password-interactive"answer cotton spike caution blouse only radio artefact middle guilt bleak original"Key password: 123456Secret phrase:       answer cotton spike caution blouse only radio artefact middle guilt bleak originalNetwork ID:        substrateSecret seed:       0xfcd01919178fa73e7223bdeea134b1ef809b75d3fabd672a52dcc69b964813b6Public key (hex):  0xb293f948a04a5bac3b8321f99bb4c9532f6ffe2b8ff40926b23c68c9726ca40aAccount ID:        0xb293f948a04a5bac3b8321f99bb4c9532f6ffe2b8ff40926b23c68c9726ca40aPublic key (SS58): 5G6rLZNtZPyMrYTo2YXL9nzaatJ837hmKPnsgYqDURgAWBgXSS58 Address:      5G6rLZNtZPyMrYTo2YXL9nzaatJ837hmKPnsgYqDURgAWBgXGenerate a second set of keys此教程的专用网络仅包含两个节点,因此需要两组密钥。有几个选项可以继续本教程:1. 可以将密钥用于预定义帐户之一。2. 可以使用本地计算机上的不同身份重复上一节中的步骤,以生成第二个密钥对。3. 您可以派生一个子密钥对来模拟本地计算机上的第二个身份。4. 您可以招募其他参与者来生成加入您的私有网络所需的密钥。出于强化目的,这里再次重复前面的操作,./target/release/node-template key generate --scheme Sr25519 --password-interactiveKey password: 234567Secret phrase:       twelve genuine tree month sport thought more almost frown question suit lifeNetwork ID:        substrateSecret seed:       0x627c5e2ac10a94cc0150efb2e2d38e0de2477e6a53892ade5f8b3cd9862e541ePublic key (hex):  0x0069217a6b3a9a4d3fa248a69fb39cef88c27301b5a63aeff52ba59c6781173dAccount ID:        0x0069217a6b3a9a4d3fa248a69fb39cef88c27301b5a63aeff52ba59c6781173dPublic key (SS58): 5C5F62ga8UtigQK1YRTcuVk1sexcmuLHtVSBnJk5xQQ9P6udSS58 Address:      5C5F62ga8UtigQK1YRTcuVk1sexcmuLHtVSBnJk5xQQ9P6ud./target/release/node-template key inspect --password-interactive --scheme Ed25519 "<前面的助记词>"Key password: 234567Secret phrase:       twelve genuine tree month sport thought more almost frown question suit lifeNetwork ID:        substrateSecret seed:       0x627c5e2ac10a94cc0150efb2e2d38e0de2477e6a53892ade5f8b3cd9862e541ePublic key (hex):  0xcde9a701b5965bb5327f900c83c2f9753d1d124fa21228851d6e26659d8dff5fAccount ID:        0xcde9a701b5965bb5327f900c83c2f9753d1d124fa21228851d6e26659d8dff5fPublic key (SS58): 5Gih5kiPMdKBrz4HTuKWQrTedqr8TLYWLb2WW67VzUyRzgF1SS58 Address:      5Gih5kiPMdKBrz4HTuKWQrTedqr8TLYWLb2WW67VzUyRzgF1使用的第二组键是:Sr25519 对应 SS58:5C5F62ga8UtigQK1YRTcuVk1sexcmuLHtVSBnJk5xQQ9P6ud 用于aura。Ed25519 对应 SS58:5Gih5kiPMdKBrz4HTuKWQrTedqr8TLYWLb2WW67VzUyRzgF1 用于grapdpaCreate a custom chain specification生成用于区块链的密钥后,您就可以使用这些密钥对创建自定义链规范,然后与作为验证器(validators)的受信任网络参与者共享您的自定义链规范。为了使其他人能够参与您的区块链网络,请确保他们生成自己的密钥。收集完网络参与者的密钥后,可以创建自定义链规范以替换本地链规范。为简单起见,本教程中创建的自定义链规范是本地链规范的修改版本,用于说明如何创建双节点网络。如果您拥有所需的密钥,您可以按照相同的步骤将更多节点添加到网络中。--------> Modify the local chain specification这个操作很重要,后续课程还会用到。(Connect to Rococo testnet)Export the local chain specification to a file./target/release/node-template build-spec--disable-default-bootnode--chain local > customSpec.jsonhead customSpec.json# customSpec.json{"name": "Local Testnet","id": "local_testnet","chainType": "Local","bootNodes": [],"telemetryEndpoints": null,"protocolId": null,"properties": null,"consensusEngine": null,"codeSubstitutes": {},}tail -n 80 customSpec.json此命令显示 Wasm 二进制字段后面的最后部分,包括运行时使用的几个托盘的详细信息,例如 sudo 和 balances 托盘。Modify the name field to identify this chain specification as a custom chain specification."name": "My Custom Testnet",:Modify aura field to specify the nodes"aura": { "authorities": ["<aura的Sr25519密钥>", "<aura的Sr25519公钥对应地址(SS58)>"]},Modify the grandpa field to specify the nodes"grandpa": {"authorities": [["节点 1 的grandpa Ed25519 对应 SS58公钥(地址)",1],["节点 2 的grandpa Ed25519 对应 SS58公钥(地址)",1]]},请注意,grandpa 部分中的 authority 字段有两个数据值。1. 第一个值是地址键。2. 第二个值用于支持加权投票。在此示例中,每个验证者的权重为 1 票。Add validators如你所见:可以通过修改 aura 和 grandpa 部分来添加和更改链规范中的权限地址。可以使用此技术添加任意数量的验证器。添加验证器:1. 修改 aura 部分以包含 Sr25519 地址。2. 修改 grapdpa 部分以包括 Ed25519 地址和投票权重。确保为每个验证器使用唯一的密钥。如果两个验证器具有相同的密钥,它们会产生冲突的块。Convert the chain specification to raw format./target/release/node-templatebuild-spec--chain=customSpec.json--raw--disable-default-bootnode> customSpecRaw.jsonShare the chain specification with others如果你正在创建私有区块链网络以与其他参与者共享,请确保只有一个人创建链规范并与该规范中的所有其他验证器共享生成的该规范的原始版本(例如 customSpecRaw.json 文件)网络。因为 Rust 编译器生成的优化的 WebAssembly 二进制文件在确定性上无法重现,所以每个生成 Wasm 运行时的人都会生成稍微不同的 Wasm blob。为了确保确定性,区块链网络中的所有参与者必须使用完全相同的原始链规范文件。有关此问题的更多信息,请参阅<Hunting down a non-determinism-bug in our Rust Wasm build>。Hunting down a non-determinism-bug in our Rust Wasm buildPrepare to launch the private network将自定义链规范分发给所有网络参与者后,就可以启动自己的私有区块链了。这些步骤类似于在启动第一个区块链节点中执行的步骤。但是,如果按照本教程中的步骤操作,则可以将多台计算机添加到您的网络中。要继续,请验证以下内容:1. 已经为至少两个权限帐户生成或收集了帐户密钥。2. 已经更新自定义链规范,以包含用于块生产(aura)和块完成(grandpa)的密钥。3. 已将自定义链规范转换为原始格式,并将原始链规范分发给参与私有网络的节点。如果已完成这些步骤,您就可以启动私有区块链中的第一个节点了。Start the first node此命令还使用您自己的密钥而不是预定义的帐户来启动节点。由于没有使用具有已知密钥的预定义帐户,因此需要在单独的步骤中将密钥添加到密钥库。./target/release/node-template--base-path /tmp/node01--chain ./customSpecRaw.json--port 30333--ws-port 9945--rpc-port 9933--telemetry-url "wss://telemetry.polkadot.io/submit/ 0"--validator--rpc-methods Unsafe--name MyNode01--password-interactiveKeystore password: 2345672022-07-21 21:10:58 Substrate Node2022-07-21 21:10:58 ✌️  version 4.0.0-dev-9c89be106cb2022-07-21 21:10:58 ❤️  by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-20222022-07-21 21:10:58 📋 Chain specification: My Custom Testnet2022-07-21 21:10:58 🏷  Node name: MyNode012022-07-21 21:10:58 👤 Role: AUTHORITY2022-07-21 21:10:58 💾 Database: RocksDb at /tmp/node01/chains/local_testnet/db/full2022-07-21 21:10:58 ⛓  Native runtime: node-template-100 (node-template-1.tx1.au1)2022-07-21 21:10:59 🔨 Initializing Genesis block/state (state: 0xe114…e9a6, header-hash: 0xbe24…67a2)2022-07-21 21:10:59 👴 Loading GRANDPA authority set from genesis on what appears to be first startup.2022-07-21 21:10:59 Using default protocol ID "sup" because none is configured in the chain specs2022-07-21 21:10:59 🏷  Local node identity is: 12D3KooWA6tqKTpAQVV8uanr7X3sRaEuTAaeHw3V5RVEupMoaCDA2022-07-21 21:10:59 💻 Operating system: macos2022-07-21 21:10:59 💻 CPU architecture: x86_642022-07-21 21:10:59 📦 Highest known block at #02022-07-21 21:10:59 〽️ Prometheus exporter started at 127.0.0.1:9615--base-path /tmp/node01The --base-path command-line option specifies acustom location for the chain associated withthis first node.--chain ./customSpecRaw.jsonThe --chain command-line option specifies thecustom chain specification.--port 30333--ws-port 9945--rpc-port 9933--telemetry-url "wss://telemetry.polkadot.io/submit/ 0":--validatorThe --validator command-line option indicates thatthis node is an authority for the chain.--rpc-methods UnsafeThe --rpc-methods Unsafe command-line option allowsyou to continue the tutorial using an unsafe communicationmode because your blockchain is not being used in aproduction setting.--name MyNode01The --name command-line option enables you to giveyour node a human-readable name in the telemetry UI.--password-interactiveView information about node operations注意其中的这些信息--chain输出表明正在使用的链规范是您使用 --chain 命令行选项创建和指定的自定义链规范。--validator输出表明该节点是一个授权,因为您使用 --validator 命令行选项启动了该节点。state输出显示使用块哈希(状态:0x2bde…8f66,标头哈希:0x6c78…37de)初始化创世块。node identify输出指定您的节点的本地节点身份。在此示例中,节点身份为12d3koowlmrydlontytytytdyzlwde1paxzxtw5rgjmhlfzw96sx。IP address输出指定用于节点的 IP 地址是本地主机 127.0.0.1。Add keys to the keystore启动第一个节点后,尚未生成任何块。下一步是将两种类型的密钥添加到网络中每个节点的密钥库中。对于每个节点:1. 添加 aura 权限密钥以启用块生产。2. 添加g randpa 权限密钥以启用块完成。有几种方法可以将密钥插入密钥库。对于本教程,您可以使用 key 子命令插入本地生成的密钥。Insert the aura secret key:./target/release/node-templatekey insert --base-path /tmp/node01--chain customSpecRaw.json--scheme Sr25519--suri <your-secret-seed>--password-interactive--key-type auraReplace <your-secret-seed> with the secret phrase orsecret seed for the first key pair that you generatedin Generate local keys using node template.You can also insert a key from a specified file location../target/release/node-template key insert --helpInsert the grandpa secret key./target/release/node-template key insert--base-path /tmp/node01--chain customSpecRaw.json--scheme Ed25519--suri <your-secret-key>--password-interactive--key-type granVerify that your keys are in the keystore for node01ls /tmp/node01/chains/local_testnet/keystoreRestart the nodeAfter you have added your keys to the keystore forthe first node under /tmp/node01, you can restartthe node using the command you used previously in<Start the first node>.Enable other participants to jointip: You can now allow other validators to jointhe network using the --bootnodes and --validatorcommand-line options.To add a second validator to the private network,just start a second blockchain node./target/release/node-template--base-path /tmp/node02--chain ./customSpecRaw.json--port 30334--ws-port 9946--rpc-port 9934--telemetry-url "wss://telemetry.polkadot.io/submit/ 0"--validator--rpc-methods Unsafe--name MyNode02--bootnodes/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWLmrYDLoNTyTYtRdDyZLWDe1paxzxTw5RgjmHLfzW96SX--password-interactive

加密方式梳理

Sr25519

用于使用 aura 为一个节点生成块。

Ed25519

用于使用 grapdpa 为一个节点生成块。

SS58: 对应公钥/地址格式

步骤:

  1. 使用Sr25519 -> 一个助记词和对应SS58公钥 -> aura
  2. 使用Ed25519+前面的助记词 -> Ed25519 公钥 -> grandpa

actdiag


  
    
      
    
  
  blockdiag
  actdiag {
  first_sr25519 -> first_ed25519 -> add_validators
  second_sr25519 -> second_ed25519 -> add_validators
  export_customSpec -> add_validators
  add_validators -> first_start -> first_import_key -> first_restart
  add_validators -> second_start -> second_import_key -> second_restart
  first_restart -> peers
  second_restart -> peers

  lane node1 {
    label = "node1"
    first_sr25519 [label = "使用Sr25519 -> 一个助记词和对应SS58公钥 -> aura"];
    first_ed25519 [label = "使用Ed25519+前面的助记词 -> Ed25519 公钥 -> grandpa"];
    first_start [label = "使用链规范文件启动第一个节点"];
    first_import_key [label = "导入第一个节点的key"];
    first_restart [label = "使用链规范文件重启第一个节点"];
  }
  lane network {
    label = "本地链"
    export_customSpec [label = "导出链规范文件"];
    add_validators [label = "添加验证节点信息"];
    peers [label = "进入本地链,互为观察者(peers)"];
  }
  lane node2 {
    label = "node2"
    second_sr25519 [label = "使用Sr25519 -> 一个助记词和对应SS58公钥 -> aura"];
    second_ed25519 [label = "使用Ed25519+前面的助记词 -> Ed25519 公钥 -> grandpa"];
    second_start [label = "使用链规范文件启动第二个节点"];
    second_import_key [label = "导入第二个节点的key"];
    second_restart [label = "使用链规范文件重启第二个节点"];
  }
}
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  node1
  
  本地链
  
  node2
  
  
  
  
  
  使用Sr25519 -> 一个助
  记词和对应SS58公钥 -> 
  aura
  
  使用Ed25519+前面的助记
  词 -> Ed25519 公钥 ->
   grandpa
  
  使用Ed25519+前面的助记
  词 -> Ed25519 公钥 ->
   grandpa
  
  导出链规范文件
  
  添加验证节点信息
  
  使用Sr25519 -> 一个助
  记词和对应SS58公钥 -> 
  aura
  
  使用链规范文件启动第一
  个节点
  
  导入第一个节点的key
  
  使用链规范文件重启第一
  个节点
  
  使用链规范文件重启第二
  个节点
  
  使用链规范文件启动第二
  个节点
  
  导入第二个节点的key
  
  进入本地链,互为观察者(
  peers)
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

Authorize specific nodes

Authorize specific nodes 返回using the node authorization palletThe node-authorization pallet is a prebuilt FRAME palletthat enables you to manage a configurable set of nodesfor a network. Each node is identified by a PeerId.Each PeerId is owned by one and only one AccountIdthat claims the node.Why permissioned networkIn Add trusted nodes, you saw how to build a simplenetwork with a known set of validator nodes. That tutorialillustrated a simplified version of a permissioned network.In a permissioned network, only authorized nodes are allowedto perform specific network activities. For example, you mightgrant some nodes the permission to validate blocks and othernodes the permission to propagate transactions.A blockchain with nodes that are granted specific permissionsis different from a public or permissionless blockchain.In a permissionless blockchain, anyone can join the networkby running the node software on suitable hardware. In general,a permissionless blockchain offers greater decentralization ofthe network. However, there are use cases where creating apermissioned blockchain might be appropriate.For example,a permissioned blockchain would be suitable forthe following types of projects:1. For a private or consortium networksuch as a private enterprise or a non-profit organization.2. In highly-regulated data environmentssuch as healthcare, finance, or business-to-business ledgers.3. For testing of a pre-public blockchain network at scale.Node authorization and ownershipThere are two ways you can authorize a node to join the network:1. By adding the PeerId to the list of predefined nodes.You must be approved by the governance or sudo palletin the network to do this.2. By asking for a paired peer connection from a specific node.This node can either be a predefined node PeerId or a normal one.any user can claim to be the owner of a PeerIdTo protect against false claims, you should claimthe node before you start the node.After you start the node, its PeerID is visible tothe network and anyone could subsequently claim it.As the owner of a nodeyou can add and remove connections for your node.For example, you can manipulate the connectionbetween a predefined node and your node orbetween your node and other non-predefined nodes.You can't change the connections for predefined nodes.They are always allowed to connect with each other.offchain workerThe node-authorization pallet uses an offchain workerto configure its node connections.Make sure to enable the offchain worker when youstart the node because it is disabled by defaultfor non-authority nodes.Need to be familiar with peer-to-peer networking in Substrate编译一下项目,获取可执行文件cd substrate-node-templategit checkout latestcargo build --releaseAdd the node authorization palletCargo.tomlthe Cargo.toml file controls two important pieces of information:1. The pallets to be imported as dependencies for the runtime,including the location and version of the pallets to import.2. The features in each pallet that should be enabledwhen compiling the native Rust binary. By enabling the standard (std)feature set from each pallet , you can compile the runtime to includefunctions, types, and primitives that would otherwise be missingwhen you build the WebAssembly binary.cargo dependenciescargo featuresAdd note-authorization dependenciesruntime/Cargo.toml->[depencies][dependencies]pallet-node-authorization = {default-features = false,git = "https://github.com/paritytech/substrate.git",tag = "devhub/latest",version = "4.0.0-dev" }<code>runtime/Cargo.toml->[features][features]default = ['std']std = [..."pallet-node-authorization/std",    # add this line...]如果忘记更新 Cargo.toml 文件中的 features 部分,可能会在编译运行时二进制文件时看到找不到函数错误。Build process本节指定要为此运行时编译的默认功能集是 std 功能集。使用 std 功能集编译运行时的时候,将启用所有列为依赖项的托盘中的 std 功能。有关如何使用标准库将运行时编译为原生 Rust 二进制文件以及使用 no_std 属性编译为 WebAssembly 二进制文件的更多详细信息,请参阅构建运行时。check new dependenciescargo check -p node-template-runtimeAdd an administrative rule要在本教程中模拟治理(governance),可以将托盘配置为使用 EnsureRoot 特权功能,该功能可以使用 Sudo 托盘调用。 Sudo 托盘默认包含在节点模板中,使您能够通过根级管理帐户进行调用。在生产环境中,将使用更现实的基于治理的检查。runtime/src/lib.rsuse frame_system::EnsureRoot;Implement the Config trait for the palletAbout Pallet Config Trait每个托盘都有一个名为 Config 的 Rust 特征。Config trait 用于识别托盘所需的参数和类型。添加托盘所需的大多数特定于托盘的代码都是使用 Config 特征实现的。可以通过参考其 Rust 文档或托盘的源代码来查看您需要为任何托盘实现的内容。例如,要查看 node-authorization 托盘中的 Config trait需要实现什么,可以参考托盘节点授权::Config 的 Rust 文档。[[Traits: Defining Shared Behavior - The Rust Programming Language](https://doc.rust-lang.org/book/ch10-02-traits.html) Traits]]pallet_node_authorization::pallet::ConfigTo implement the node-authorization pallet in your runtimeruntime/src/lib.rsAdd the parameter_typesparameter_types! {pub const MaxWellKnownNodes: u32 = 8;pub const MaxPeerIdLength: u32 = 128;}Add the impl sectionimpl pallet_node_authorization::Config for Runtime {type Event = Event;type MaxWellKnownNodes = MaxWellKnownNodes;type MaxPeerIdLength = MaxPeerIdLength;type AddOrigin = EnsureRoot<AccountId>;type RemoveOrigin = EnsureRoot<AccountId>;type SwapOrigin = EnsureRoot<AccountId>; //type ResetOrigin = EnsureRoot<AccountId>; //type WeightInfo = (); //}Add the pallet to the construct_runtime macroconstruct_runtime!(pub enum Runtime whereBlock = Block,NodeBlock = opaque::Block,UncheckedExtrinsic = UncheckedExtrinsic{/* Add This Line */NodeAuthorization: pallet_node_authorization::{Pallet, Call, Storage, Event<T>, Config<T>},});Cargo checkcargo check -p node-template-runtimeAdd genesis storage for authorized nodesBefore you can launch the network to use node authorization,some additional configuration is needed to handle the peeridentifiers and account identifiers .For example, the PeerId is encoded in bs58 format,so you need to add a new dependency for the bs58 libraryin the node/Cargo.toml to decode the PeerId to get its bytes.To keep things simple, the authorized nodes are associatedwith predefined accounts.node/Cargo.toml[dependencies]bs58 = "0.4.0"node/src/chain_spec.rsAdd genesis storage for nodes// A struct wraps Vec<u8>, represents as our `PeerId`.use sp_core::OpaquePeerId;// The genesis config that serves for our pallet.use node_template_runtime::NodeAuthorizationConfig;Locate the testnet_genesis function/// Configure initial storage state for FRAME modules.fn testnet_genesis(wasm_binary: &[u8],initial_authorities: Vec<(AuraId, GrandpaId)>,root_key: AccountId,endowed_accounts: Vec<AccountId>,_enable_println: bool,) -> GenesisConfig {Within the GenesisConfig declarationnode_authorization: NodeAuthorizationConfig {nodes: vec![(OpaquePeerId(bs58::decode("12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2").into_vec().unwrap()),endowed_accounts[0].clone()),(OpaquePeerId(bs58::decode("12D3KooWQYV9dGMFoRzNStwpXztXaBUjtPqi6aU76ZgUriHhKust").into_vec().unwrap()),endowed_accounts[1].clone()),],},在这段代码中,NodeAuthorizationConfig 包含一个 nodes 属性,它是一个包含两个元素的元组的向量。1. 元组的第一个元素是 OpaquePeerId。 bs58::decode 操作将人类可读的 PeerId(例如 12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2)转换为字节。2. 元组的第二个元素是代表该节点所有者的 AccountId。此示例使用预定义的 Alice 和 Bob,此处标识为捐赠账户 [0] 和 [1]。预定义的keyVerify that the node compilescargo build --releaseLaunch the permissioned networkFor the purposes of this tutorial现在可以使用预定义帐户的节点密钥和对等标识符来启动许可网络并授权其他节点加入。出于本教程的目的,将启动四个节点:1. 其中三个节点与预定义的帐户相关联,并且所有这三个节点都被允许创作和验证区块。2. 第四个节点是一个子节点,只有在该节点所有者批准的情况下才被授权从选定节点读取数据。Obtain node keys and peerIDs现在已经在创世存储(Genesis storage)中配置了与 Alice 和 Bob 账户关联的节点。可以使用子密钥程序(subkey)检查与预定义帐户关联的密钥,并生成和检查您自己的密钥。但是,如果运行 subkey generate-node-key 命令,节点密钥和对等标识符是随机生成的,并且与教程中使用的密钥不匹配。因为本教程使用预定义的账户和众所周知的节点密钥,所以总结了每个账户的密钥。使用表中数据启动alice节点./target/release/node-template--chain=local--base-path /tmp/validator1--alice--node-key=c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a--port 30333--ws-port 9944使用表中数据启动bob节点./target/release/node-template--chain=local--base-path /tmp/validator2--bob--node-key=6ce3be907dbcabf20a9a5a60a712b4256a54196000a8ed4050d352bc113f8c58--port 30334--ws-port 9945两个节点都启动后,您应该能够在两个终端日志中看到创建和完成的新块。Add a third node to the list of well-known nodes您可以使用 --name charlie 命令启动第三个节点。节点授权托盘使用脱链工作者来配置节点连接。由于第三个节点不是知名节点,并且会将网络中的第四个节点配置为只读子节点,因此您必须包含命令行选项以启用脱链工作者(offchain worker)。./target/release/node-template--chain=local--base-path /tmp/validator3--name charlie--node-key=3a9d5b35b9fb4c42aafadeca046f6bf56107bd2579687f069b42646684b94d9e--port 30335--ws-port=9946--offchain-worker alwayscharlie节点没有连接的peers启动此节点后,您应该会看到该节点没有连接的对等方。因为这是一个许可网络,所以必须明确授权该节点进行连接。Alice 和 Bob 节点在 genesis chain_spec.rsfile 中配置。必须使用对 Sudo 托盘的调用手动添加所有其他节点。Authorize access for the third nodeThis tutorial uses the sudo pallet for governance.Therefore, yu can use the sudo pallet to call theadd_well_known_node function provided bynode-authorization pallet to add the third node.Add a sub-node该网络中的第四个节点不是众所周知的节点。1. 该节点归用户 dave 所有,是 charlie 节点的子节点。2. 子节点只能通过连接到 charlie 拥有的节点来访问网络。3. 父节点负责其授权连接的任何子节点,并在子节点需要删除或审计时控制访问。./target/release/node-template--chain=local--base-path /tmp/validator4--name dave--node-key=a99331ff4f0e0a0434a6263da0a5823ea3afcfffe590c9f3014e6cf620f2b19a--port 30336--ws-port 9947--offchain-worker always

Alice授权Charlie过程

  1. 使用polkadot-js-app打开并切换到本地网络,开发者>超级管理(sudo)>nodeAuthorization

image-20220723180351122

  1. 切换到nodeAuthorization

image-20220723180419349

  1. 切换addConnections(node, owner)

image-20220723180452122

  1. 选择CHARLIE节点进行授权

image-20220723180656562

  • 注意填写charlie的peerid

image-20220723182510593

  1. 签名并提交

image-20220723180850302

交易包含在区块中后,您应该看到 charlie 节点连接到 alice 和 bob 节点,并开始同步区块。这三个节点可以使用本地网络中默认启用的 mDNS 发现机制找到彼此。 如果您的节点不在同一个本地网络上,您应该使用命令行选项 –no-mdns 来禁用它。

Charlie连接Dave过程

  1. 切换Charlie账户,执行addConnections(node, connections)操作

image-20220723183824019

注意:第一个填Chalie的peerid in hex,第二个填Dave的peer id in hex

  1. 切换Dave账户,执行claimNode(node)操作

image-20220723183609177

  1. 提示,操作成功后右侧会出现弹窗

image-20220723183551671

您现在应该看到 Dave 正在捕获区块,并且只有一个属于 Charlie 的节点!重新启动 Dave 的节点,以防它没有立即与 Charlie 连接

流程图

sequenceDiagram
    actor terminal as 终端
    participant runtime as 运行时:添加pallet
    participant node as 节点:修改链规范
    participant pkjs as polkadot-js-app
    terminal->>terminal: git chekout latest & cargo build --release
    terminal->>+runtime: 开始修改运行时cargo文件,添加pallet依赖与feature
    rect rgb(200, 150, 255)
    runtime->>runtime: runtime/Cargo.toml:depencies添加pallet-node-authorization
    runtime->>runtime: runtime/Cargo.toml:features添加pallet-node-authorization/std
    end
    runtime->>-terminal: prepare to check
    terminal->>terminal: cargo check -p node-template-runtime
    terminal->>+runtime: 开始给节点node添加pallet用到的参数类型、实现块、构建运行时配置
    rect rgb(200, 150, 255)
    runtime->>runtime: runtime/src/runtime.rs:add parameter_types
    runtime->>runtime: runtime/src/runtime.rs:add impl section
    runtime->>runtime: runtime/src/runtime.rs:add the pallet to the construct_runtime macro
    end
    runtime->>-terminal: 开始检查
    terminal->>terminal: cargo check -p node-template-runtime
    terminal->>+node: 开始给授权节点添加创始区块存储功能
    node->>node: node/Cargo.toml:add bs58 dependency
    rect rgb(200, 150, 255)
    node->>+node: 添加创始区块存储功能
    node->>node: node/src/node.rs:add genesis storage for nodes
    node->>node: node/src/node.rs:locate the testnet_genesis function
    node->>node: node/src/node.rs:add GenesisConfig declaration
    end
    node->>-terminal: cargo check & start nodes
    terminal->>terminal: cargo check -p node-template-runtime
    rect rgb(200, 150, 255)
    terminal->>terminal: start alice node
    terminal->>terminal: start bob node
    terminal->>terminal: start Charlie node
    terminal->>terminal: start Dave node
    end
    terminal->>pkjs: 开始进行授权与建立连接操作
    rect rgb(200, 150, 255)
    pkjs->>pkjs: 使用alice账号给Charlie授权
    pkjs->>pkjs: 使用Charlie账号连接Dave节点
    pkjs->>pkjs: Dave对外claimNode
    end
`

总结

任何节点都可以发出影响其他节点行为的交易(extrinsics),只要它位于用于参考的链数据上,并且您在密钥库中拥有可用于所需来源的相关帐户的密钥。此演示中的所有节点都可以访问开发人员签名密钥,因此能够代表 Charlie 从网络上的任何连接节点发出影响 charlie 子节点的命令。

在现实世界的应用程序中,节点操作员只能访问他们的节点密钥,并且是唯一能够正确签署和提交外部信息的人,很可能来自他们自己的节点,他们可以控制密钥的安全性。

Monitor node metrics

Error rendering admonishment

Failed with: TOML parsing error: expected an equals, found an identifier at line 1 column 5

Original markdown input:

```admonish tip info title='承接关系:需要基于上一节课'
![image-20220724104945822](https://raw.githubusercontent.com/KuanHsiaoKuo/writing_materials/main/imgs/image-20220724104945822.png)
```
Monitor node metrics 返回Substrate exposes metrics about the operation of your network.For example, you can collect information about:1. how many peers your node is connected to2. how much memory your node is using.To visualize these metrics, you can use tools like Prometheus and Grafana.This tutorial demonstrates how to use Grafana and Prometheusto scrape and visualize these types of node metrics .A possible architecture+-----------+                     +-------------+                                                              +---------+| Substrate |                     | Prometheus  |                                                              | Grafana |+-----------+                     +-------------+                                                              +---------+|               -----------------\ |                                                                          ||               | Every 1 minute |-|                                                                          ||               |----------------| |                                                                          ||                                  |                                                                          ||        GET current metric values |                                                                          ||<---------------------------------|                                                                          ||                                  |                                                                          || `substrate_peers_count 5`        |                                                                          ||--------------------------------->|                                                                          ||                                  | --------------------------------------------------------------------\    ||                                  |-| Save metric value with corresponding time stamp in local database |    ||                                  | |-------------------------------------------------------------------|    ||                                  |                                         -------------------------------\ ||                                  |                                         | Every time user opens graphs |-||                                  |                                         |------------------------------| ||                                  |                                                                          ||                                  |       GET values of metric `substrate_peers_count` from time-X to time-Y ||                                  |<-------------------------------------------------------------------------||                                  |                                                                          ||                                  | `substrate_peers_count (1582023828, 5), (1582023847, 4) [...]`           ||                                  |------------------------------------------------------------------------->||                                  |                                                                          |Tutorial objectives1. Install Prometheus and Grafana.2. Configure Prometheus to capture a time series for your Substrate node.3. Configure Grafana to visualize the node metrics collected using the Prometheus endpoint.Install Prometheus and GrafanaStart a Substrate nodeConfigure Prometheus to scrape your Substrate node# prometheus.yml# --snip--# A scrape configuration containing exactly one endpoint to scrape:# Here it's Prometheus itself.scrape_configs:# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.- job_name: "substrate_node"# metrics_path defaults to '/metrics'# scheme defaults to 'http'.# Override the global default and scrape targets from this job every 5 seconds.# ** NOTE: you want to have this *LESS THAN* the block time in order to ensure# ** that you have a data point for every block!scrape_interval: 5sstatic_configs:- targets: ["localhost:9615"]# specify a custom config file instead if you made one here:./prometheus --config.file prometheus.ymlCheck all Prometheus metricscurl localhost:9615/metricsVisualizing Prometheus metrics with Grafana

本节大概的架构

prometheus和grafana配合流程

+-----------+                     +-------------+                                                              +---------+
| Substrate |                     | Prometheus  |                                                              | Grafana |
+-----------+                     +-------------+                                                              +---------+
      |               -----------------\ |                                                                          |
      |               | Every 1 minute |-|                                                                          |
      |               |----------------| |                                                                          |
      |                                  |                                                                          |
      |        GET current metric values |                                                                          |
      |<---------------------------------|                                                                          |
      |                                  |                                                                          |
      | `substrate_peers_count 5`        |                                                                          |
      |--------------------------------->|                                                                          |
      |                                  | --------------------------------------------------------------------\    |
      |                                  |-| Save metric value with corresponding time stamp in local database |    |
      |                                  | |-------------------------------------------------------------------|    |
      |                                  |                                         -------------------------------\ |
      |                                  |                                         | Every time user opens graphs |-|
      |                                  |                                         |------------------------------| |
      |                                  |                                                                          |
      |                                  |       GET values of metric `substrate_peers_count` from time-X to time-Y |
      |                                  |<-------------------------------------------------------------------------|
      |                                  |                                                                          |
      |                                  | `substrate_peers_count (1582023828, 5), (1582023847, 4) [...]`           |
      |                                  |------------------------------------------------------------------------->|
      |                                  |                                                                          |

安装Prometheus和grafana

gunzip prometheus-<version>.darwin-amd64.tar.gz && tar -xvf prometheus-2.35.0.darwin-amd64.tar
brew update && brew install grafana
==> Downloading https://ghcr.io/v2/homebrew/core/grafana/manifests/9.0.2
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/grafana/blobs/sha256:6022dd955d971d2d34d70f29e56335610108c84b75081020092e29f3ec641724
==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:6022dd955d971d2d34d70f29e56335610108c84b75081020092e29f3ec64
######################################################################## 100.0%
==> Pouring grafana--9.0.2.monterey.bottle.tar.gz
==> Caveats
To restart grafana after an upgrade:
  brew services restart grafana
Or, if you don't want/need a background service you can just run:
  /usr/local/opt/grafana/bin/grafana-server --config /usr/local/etc/grafana/grafana.ini --homepath /usr/local/opt/grafana/share/grafana --packaging=brew cfg:default.paths.logs=/usr/local/var/log/grafana cfg:default.paths.data=/usr/local/var/lib/grafana cfg:default.paths.plugins=/usr/local/var/lib/grafana/plugins
==> Summary
🍺  /usr/local/Cellar/grafana/9.0.2: 6,007 files, 247.3MB
==> Running `brew cleanup grafana`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

配置Prometheus.yml

# --snip--

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "substrate_node"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    # Override the global default and scrape targets from this job every 5 seconds.
    # ** NOTE: you want to have this *LESS THAN* the block time in order to ensure
    # ** that you have a data point for every block!
    scrape_interval: 5s

    static_configs:
      - targets: [ "localhost:9615" ]
# specify a custom config file instead if you made one here:
./prometheus --config.file substrate_prometheus.yml
curl localhost:9615/metrics

浏览器查看

也可以直接打开浏览器:localhost:9615/metrics

启动grafana

# 后台运行
brew services restart grafana
# 指定运行
/usr/local/opt/grafana/bin/grafana-server --config /usr/local/etc/grafana/grafana.ini --homepath /usr/local/opt/grafana/share/grafana --packaging=brew cfg:default.paths.logs=/usr/local/var/log/grafana cfg:default.paths.data=/usr/local/var/lib/grafana cfg:default.paths.plugins=/usr/local/var/lib/grafana/plugins
  • http://localhost:3000/

image-20220724110946857

image-20220724111058420

然后需要选择 Prometheus 数据源类型并指定 Grafana 需要查找它的位置。

Grafana 需要的 Prometheus 端口不是在 prometheus.yml 文件 (http://localhost:9615) 中为节点发布其数据的位置设置的端口。

在同时运行 Substrate 节点和 Prometheus 的情况下,配置 Grafana 以在其默认端口 http://localhost:9090 或配置的端口(如果自定义它)上查找 Prometheus。

配置数据源

CleanShot 2022-07-24 at 11.16.03

CleanShot 2022-07-24 at 11.16.59

CleanShot 2022-07-24 at 11.18.17

CleanShot 2022-07-24 at 11.34.47

导入看板模版

Export and import | Grafana documentation

Dashboards | Grafana Labs

Substrate Node Template Metrics dashboard for Grafana | Grafana Labs

image-20220724113036077

CleanShot 2022-07-24 at 11.31.10

image-20220724113401390

Upgrade a running network

Upgrade a running networkUpgrade a running network返回Forkless upgrade introUnlike many blockchains, the Substrate development framework supportsforkless upgrades to the runtime that is the core of the blockchain.Most blockchain projects require a hard fork of the code base tosupport ongoing development of new features or enhancementsto existing features.With Substrate , you can deploy enhanced runtime capabilities—includingbreaking changes—without a hard fork.Because the definition of the runtime is itself an elementin a Substrate chain's state, network participants can updatethis value by calling the set_code function in a transaction.Because updates to the runtime state are validates using theblockchain's consensus mechanisms and cryptographic guarantees,network participants can use the blockchain itself to distributeupdated or extended runtime logic without needing to fork thechain or release a new blockchain client.Hard ForkSubstrate set_code functionTutorial objectives1. Use the Sudo pallet to simulate governance for a chain upgrade.2. Upgrade the runtime for a running node to include a new pallet .3. Schedule an upgrade for a runtime.Authorize an upgrade using the Sudo palletIn FRAME, the Root origin identifies the runtime administrator.Only this administrator can update the runtime by callingthe set_code function. To invoke this function using the Rootorigin, you can use the the sudo function in the Sudo pallet tospecify the account that has superuser administrative permissions.By default, the chain specification file for the node templatespecifies that the alice development account is the owner ofthe Sudo administrative account. Therefore, this tutorial usesthe alice account to perform runtime upgrades.Resource accounting for runtime upgradesFunction calls that are dispatched to the Substrate runtime arealways associated with a weight to account for resource usage.The FRAME System module sets boundaries on the block length andblock weight that these transactions can use.However, the set_code function is intentionally designed toconsume the maximum weight that can fit in a block. Forcing aruntime upgrade to consume an entire block prevents transactionsin the same block from executing on different versions of a runtime.The weight annotation for the set_code function also specifies thatthe function is in the Operational class because it provides networkcapabilities. Functions calls that are identified as operational:1. Can consume the entire weight limit of a block.2. Are given maximum priority.3. Are exempt from paying the transaction fees.Managing resource accountingIn this tutorial, the sudo_unchecked_weight function is used toinvoke the set_code function for the runtime upgrade.The sudo_unchecked_weight function is the same as the sudo functionexcept that it supports an additional parameter to specify the weightto use for the call. This parameter enables you to work around resourceaccounting safeguards to specify a weight of zero for the call thatdispatches the set_code function. This setting allows for a block totake an indefinite time to compute to ensure that the runtime upgradedoes not fail, no matter how complex the operation is.It can take all the time it needs to succeed or fail.Upgrade the runtime to add the Scheduler palletThe node template doesn't include the Scheduler pallet in its runtime.To illustrate a runtime upgrade, let's add the Scheduler pallet to a running node.First Screen: Start the local node in development mode# Leave this node running.# You can edit and re-compile to upgrade the runtime# without stopping or restarting the running node.cargo run --release -- --devSecond Screen: Upgrade Operationsubstrate-node-template/runtime/Cargo.tomlAdd the Scheduler pallet as a dependency[dependencies]...pallet-scheduler = {version = "4.0.0-dev",default-features = false,git = "https://github.com/paritytech/substrate.git",branch = "polkadot-v0.9.24" }...Add the Scheduler pallet to the features list.[features]default = ["std"]std = [..."pallet-scheduler/std",...substrate-node-template/runtime/src/lib.rsAdd the types required by the Scheduler palletparameter_types! {pub MaximumSchedulerWeight: Weight = 10_000_000;pub const MaxScheduledPerBlock: u32 = 50;}Add the implementation for the Config trait for the Scheduler pallet .impl pallet_scheduler::Config for Runtime {type Event = Event;type Origin = Origin;type PalletsOrigin = OriginCaller;type Call = Call;type MaximumWeight = MaximumSchedulerWeight;type ScheduleOrigin = frame_system::EnsureRoot<AccountId>;type MaxScheduledPerBlock = MaxScheduledPerBlock;type WeightInfo = ();type OriginPrivilegeCmp = EqualPrivilegeOnly;type PreimageProvider = ();type NoPreimagePostponement = ();}Add the Scheduler pallet inside the construct_runtime! macro.construct_runtime!(pub enum Runtime whereBlock = Block,NodeBlock = opaque::Block,UncheckedExtrinsic = UncheckedExtrinsic{/* snip */Scheduler: pallet_scheduler,});Add the following trait dependency at the top of the file:pub use frame_support::traits::EqualPrivilegeOnly;Increment the spec_version in theRuntimeVersion structpub const VERSION: RuntimeVersion = RuntimeVersion {spec_name: create_runtime_str!("node-template"),impl_name: create_runtime_str!("node-template"),authoring_version: 1,spec_version: 101,  // *Increment* this value, the template uses 100 as a baseimpl_version: 1,apis: RUNTIME_API_VERSIONS,transaction_version: 1,};Review the components of the RuntimeVersion structspec_name specifies the name of the runtime.impl_name specifies the name of the client.authoring_version specifies the version for block authors.spec_version specifies the version of the runtime.impl_version specifies the version of the client.apis specifies the list of supported APIs.transaction_version specifies the version ofthe dispatchable function interface.authordispatchBuild the updated runtime in the second terminal# without stopping the running node.cargo build --release -p node-template-runtimeConnect to the local node to upgrade the runtime to use the new build artifact.Polkadot-JS applicationSchedule an UpgradeNow that the node template has been upgraded to include the Scheduler pallet ,the schedule function can be used to perform the next runtime upgrade.In the previous part, the sudo_unchecked_weight function was used to overridethe weight associated with the set_code function; in this section, the runtimeupgrade will be scheduled so that it can be processed as the only extrinsic in a block.the schedule functionextrinsicPrepare an Upgraded Runtime// runtime/src/lib.rspub const VERSION: RuntimeVersion = RuntimeVersion {spec_name: create_runtime_str!("node-template"),impl_name: create_runtime_str!("node-template"),authoring_version: 1,spec_version: 102,  // *Increment* this value.impl_version: 1,apis: RUNTIME_API_VERSIONS,transaction_version: 1,};/* snip */parameter_types! {pub const ExistentialDeposit: u128 = 1000;  // Update this value.pub const MaxLocks: u32 = 50;}/* snip */Build the upgraded runtimecargo build --release -p node-template-runtimeUpgrade the RuntimeUpgrade a running network

时序图

终端1直接运行节点终端1直接运行节点终端2用于更新终端2用于更新运行时运行时运行时运行时Polkadot-JS application波卡前端Polkadot-JS application波卡前端运行原先节点1.1运行原先节点cargo runrelease--dev更新paleltrefsubstrate-node-template/runtime/Cargo.toml2.1添加pallet-scheduler依赖.2.2feature添加pallet-scheduler/std.refsubstrate-node-template/runtime/src/lib.rs2.3Add the types required by theScheduler pallet2.4Add the implementation forthe Config trait for theScheduler pallet .2.5Add the Scheduler pallet insidethe construct_runtime! macro.2.6Add the following traitdependency2.7Increment the spec_version inthe RuntimeVersion struct注意下列参数的意思:spec_name,impl_name,authoring_version,spec_version,impl_version,apis,traction_version在第二个终端编译更新运行时,获取编译后的wasm文件3.1Build the updated runtime inthe second terminal window ortabwithout stopping the running node先检查:cargo check -p node-template-runtime再编译:cargo build --release -p node-template-runtim在波卡前端的extrinsics提交wasm文件升级runtime4.1Connect to the local node toupgrade the runtime to usethe new build artifact.4.2Select the Alice account tosubmit a callhttps:polkadot.js.org/apps/#/extrinsics?rpc=ws:127.0.0.1:9944using the selected account: Alicesubmit the following extrinsic: sudo > sudoUncheckedWeight(call, weight)call: system > setCode(code)then toggle file upload4.3编译生成的wasm文件用于上传target/release/wbuild/node-template-runtime/node_template_runtime.compact.compressed.wasm4.4Select file upload, then selector drag and drop theWebAssembly file that nyougenerated for the runtime.4.5Click Submit Transaction.现在runtime已经更新成功,节点模版包含Scheduler pallet。可以使用the schedule函数进行自动更新spec_version5.1update spec_versionruntime/src/lib.rspub const VERSION: RuntimeVersion = RuntimeVersion {spec_name: create_runtime_str!("node-template"),impl_name: create_runtime_str!("node-template"),authoring_version: 1,spec_version: 102, // *Increment* this value.impl_version: 1,apis: RUNTIME_API_VERSIONS,transaction_version: 1,}; /* snip*/ parameter_types! {pub const ExistentialDeposit: u128 = 1000; // Update this value.pub const MaxLocks: u32 = 50;} /* snip*/6.1再次编译运行时,获取wasm文件This will override any previous build artifacts!So if you want to have a copy on hand of yourlast runtime Wasm build files,be sure to copy them somewhere else.先检查:cargo check -p node-template-runtime再编译:cargo build --release -p node-template-runtim6.2提供wasm文件6.3打开对应功能上传文件https:polkadot.js.org/apps/#/sudo?rpc=ws:127.0.0.1:99446.4查看区块,等到达到指定条件,自动更新https:polkadot.js.org/apps/#/explorer?rpc=ws:127.0.0.1:9944

第一次更新运行时

  1. 使用alice账户上传wasm文件 CleanShot 2022-07-24 at 18.28.35
  2. node-template版本更新 image-20220724183043916
  3. 已经添加新的交易函数scheduler CleanShot 2022-07-24 at 18.41.44

第二次上传文件设置自动执行条件

  1. 使用scheduler函数 CleanShot 2022-07-24 at 18.47.40
  2. 达到条件自动触发 CleanShot 2022-07-24 at 18.49.14

Work with pallets

文档/代码更新问题

substrate文档更新带来的问题

由于目前substrate的源码和文档都在快速更新,所以可能出现一些未曾说过的问题。 比如链接找不到、目录里面不存在对应文章链接、编译时依赖包版本冲突。 这些都需要对文档的熟悉、对rust编程的熟悉才能轻松越过。

排地雷

由于官方文档和代码一直都在更新,可能会出现问题,这里就需要根据默认依赖的substrate分支进行更换

[dependencies]
sp-std = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.24" }

如上所示,对应的分支为:branch = “polkadot-v0.9.24”, 所以需要改成:

[dependencies.pallet-nicks]
default-features = false
git = 'https://github.com/paritytech/substrate.git'
#tag = 'monthly-2021-10'
#tag = 'monthly-2022-04'
branch = "polkadot-v0.9.24"
version = '4.0.0-dev'

详见: cargo 与 git

Pallet前置Rust知识

Pallet-Preset

Add a pallet to the runtime

设置昵称:添加第一个Pallet到Runtime

substrate node template提供了一个最小的可工作的运行时,但是为了保持精炼,它并不包括Frame中的大多数的Pallet

接下来接着使用前面的node template

runtime结构分析

tree -L 2 runtime                                                                                               ─╯
runtime
├── Cargo.toml
├── build.rs
└── src
    └── lib.rs

1 directory, 3 files

时序图

运行时运行时运行时运行时终端用于检查和编译终端用于检查和编译Polkadot-JS application波卡前端Polkadot-JS application波卡前端添加pallet-nicksrefsubstrate-node-template/runtime/Cargo.toml添加pallet依赖pallet-nicks添加pallet-nicks/std featurescargo checkrefsubstrate-node-template/runtime/src/lib.rsimpl-实现新增pallet对应的功能construct_runtime!宏添加新增palletcargo checkcargo build --releasestart the node切换连接到本地测试网络ref其实pallet的源码都是单独一个crate测试新增pallet的setName[package]name = "pallet-nicks"version = "4.0.0-dev"authors = ["Parity Technologies <admin@parity.io>"]edition = "2021"license = "Apache-2.0"homepage = "https://substrate.io"repository = "https://github.com/paritytech/substrate/"description = "FRAME pallet for nick management"readme = "README.md"测试新增pallet的nameof测试新增pallet的clear_name测试新增pallet的kill_nameadd pallet-nicks

Specify the origin for a call

此小节接着上节内容进行修改,主要是强化权限

为帐户设置昵称

  • 检查帐户选择列表以验证当前选择了 Alice 帐户。
  • 在 Pallet Interactor 组件中,确认选择了 Extrinsic。
  • 从可调用的托盘列表中选择nicks。
  • 选择 settName 作为要从 nicks palette 调用的函数。
  • 键入一个长于 MinNickLength(8 个字符)且不长于 MaxNickLength(32 个字符)的名称。
  • 单击Signed以执行该功能。

CleanShot 2022-07-03 at 10.51.26

CleanShot 2022-07-03 at 10.54.34

使用Nicks pallet查询账户信息

CleanShot 2022-07-03 at 11.00.08

  • 按图所示进行设置,查询,复制Alice的地址进行查询会返回一个元组,里面的两个值分别指:

    • Alice 帐户的十六进制编码昵称。
    • 为保护昵称而从 Alice 的账户中保留的金额。

如果使用Bob的地址,会返回None,因为没有给他设置昵称。

可能出现的问题

返回顶部

指定调用源头unsigned, signed or sudo

前面已经介绍用Alice的地址来设置并查询nickname(setName),里面还有其他函数(killName、forceName、clearName)这里将会进行调用验证

signed与sudo有不同权限。

点击Sudo按钮将会发出一个 Sudid 事件以通知节点参与者 Root 源发送了一个呼叫。 但是,内部调度会因 DispatchError 而失败(Sudo 按钮的 sudo 函数是“外部”调度)。

特别是,这是 DispatchError::Module 变体的一个实例,它会提供两个元数据:一个索引号和一个错误号。

  • 索引号与产生错误的pallet有关;它对应于construct_runtime!中pallet的索引(位置)!。
  • 错误编号与该托盘的错误枚举中相关变体的索引相对应。

使用这些数字查找托盘错误时,请记住索引是从零开始。

比如:

  • 索引为 9(第十个托盘),对应nicks,
  • 错误为 2(第三个错误), 对应substrate源码中定义的第三个错误
#![allow(unused)]
fn main() {
/// Error for the nicks pallet.
#[pallet::error]
pub enum Error<T> {
    /// A name is too short.    
    TooShort,
    /// A name is too long.
    TooLong,
    /// An account isn't named.
    Unnamed,
}
}
  • 取决于您的construct_runtime中尼克斯托盘的位置!宏,您可能会看到不同的索引编号。不管 index 的值如何,你应该看到错误值是 2,它对应于 Nick 的 Pallet 的 Error 枚举的第三个变体,Unnamed 变体。这应该不足为奇,因为 Bob 尚未保留昵称,因此无法清除!

Configure the contracts pallet

运行时runtime运行时runtime终端用于检查和编译终端用于检查和编译节点node节点nodePolkadot-JS application波卡前端Polkadot-JS application波卡前端添加pallet-contracts依赖refsubstrate-node-template/runtime/Cargo.toml添加pallet依赖pallet-contracts添加pallet依赖pallet-contracts-primitivesstd features:添加上述两个pallet的stdpallet-contracts/stdpallet-contracts-primitives/stdcargo check实现配置traitrefsubstrate-node-template/runtime/src/lib.rs更新pub use frame_support引入use pallet_contracts::DefaultContractAccessWeight; // Add this line添加pallet-contracts用到的参数/* After this block */// Time is measured by number of blocks.pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);pub const HOURS: BlockNumber = MINUTES * 60;pub const DAYS: BlockNumber = HOURS * 24; /* Add this block */// Contracts price units.pub const MILLICENTS: Balance = 1_000_000_000;pub const CENTS: Balance = 1_000 * MILLICENTS;pub const DOLLARS: Balance = 100 * CENTS; const fn deposit(items: u32, bytes: u32) -> Balance {items as Balance * 15 * CENTS + (bytes as Balance) * 6 * CENTS}const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10);/* End Added Block*/parameter_types!添加用到的参数类型impl-实现新增pallet对应的功能construct_runtime!宏添加新增palletcargo check对外暴露合约APIrefsubstrate-node-template/runtime/Cargo.toml添加依赖pallet-contracts-rpc-runtime-api添加std featurerefsubstrate-node-template/runtime/src/lib.rs添加常量const CONTRACTS_DEBUG_OUTPUT: bool = true;实现运行时api,impl_runtime_apis!/* Add this block*/impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>for Runtime{fn call(...) -> pallet_contracts_primitives::ContractExecResult<Balance> {} fn instantiate(...) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, Balance> {} fn upload_code(...) -> pallet_contracts_primitives::CodeUploadResult<Hash, Balance> {} fn get_storage(...) -> pallet_contracts_primitives::GetStorageResult {}}cargo check更新节点,添加对应RPC功能refsubstrate-node-template/node/Cargo.toml更新依赖pallet-contracts\pallet-contracts-rpcrefsubstrate-node-template/node/src/rpc.rs更新usenode_template_runtime::{...,Hash, BlockNumber}添加useuse pallet_contracts_rpc::{Contracts, ContractsApiServer};create_full函数中添加RPC扩展cargo check -p node-templatecargo build --releasestart the node切换连接到本地测试网络至此已经将合约pallet添加完成,要想进一步,还需要学习如何写合约add pallet-contracts

Use macros in a custom pallet

返回顶部

这一节干货较多

  1. 了解Substrate Runtime development: Runtime development | Substrate_ Docs
  2. 尤其要理解FRAME和pallets的关系。
  3. 掌握自定义pallet的步骤,其实已经准备好模版:substrate-node-template/pallets/template
  4. 更多详细内容:how-to-guides: pallet-design

时序图

palletpallets/template/srcpalletpallets/template/srclib.rspallets/template/src/lib.rslib.rspallets/template/src/lib.rsCargo.tomlpallets/template/Cargo.tomlCargo.tomlpallets/template/Cargo.toml终端用于检查和编译终端用于检查和编译本地前端不使用pkjs,因为要添加新的前端模块本地前端不使用pkjs,因为要添加新的前端模块一、准备全新模版移除其他文件benchmarking.rsmock.rstests.rsrefsubstrate-node-template/pallets/template/src/lib.rs删除所有内容添加std本地构建和wasm构建no_std所需要的宏1.![cfg_attr(not(feature = "std"), no_std)]把一个lib所需的基本结构复制进去// Re-export pallet items so that they can be accessed from the crate namespace.pub use pallet::*; /#[frame_support::pallet]pub mod pallet {use frame_support::pallet_prelude::*;use frame_system::pallet_prelude::*;use sp_std::vec::Vec; // Step 3.1 will include this in `Cargo.toml` #[pallet::config] // <-- Step 2. code block will replace this.#[pallet::event] // <-- Step 3. code block will replace this.#[pallet::error] // <-- Step 4. code block will replace this.#[pallet::pallet]#[pallet::generate_store(pub(super) trait Store)]pub struct Pallet<T>(_); #[pallet::storage] // <-- Step 5. code block will replace this.#[pallet::hooks]impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}#[pallet::call] // <-- Step 6. code block will replace this.}event configReplace the #[lib::config]/// Configure the pallet by specifying the parameters and types on which it depends.1.[pallet::config]pub trait Config: frame_system::Config {/// Because this pallet emits events, it depends on the runtime's definition of an event.type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;}event impl实现配置的事件// Pallets use events to inform users when important changes are made.// Event documentation should end with an array that provides descriptive names for parameters.1.[pallet::event]2.[pallet::generate_deposit(pub(super) fn deposit_event)]pub enum Event<T: Config> {/// Event emitted when a proof has been claimed. [who, claim]ClaimCreated(T::AccountId, Vec<u8>),/// Event emitted when a claim is revoked by the owner. [who, claim]ClaimRevoked(T::AccountId, Vec<u8>),}添加依赖[dependencies.sp-std]default-features = falsegit = 'https://github.com/paritytech/substrate.git'branch = 'polkadot-v0.9.26' # Must *match* the rest of your Substrate deps! [features]default = ['std']std = [#snip]errorReplace the #[pallet::error]1.[pallet::error]pub enum Error<T> {/// The proof has already been claimed.ProofAlreadyClaimed,/// The proof does not exist, so it cannot be revoked.NoSuchProof,/// The proof is claimed by another account, so caller can't revoke it.NotProofOwner,}storageReplace the #[pallet::storage]1.[pallet::storage]pub(super) type Proofs<T: Config> = StorageMap<_, Blake2_128Concat, Vec<u8>, (T::AccountId, T::BlockNumber), ValueQuery>;callReplace the #[pallet::call]// Dispatchable functions allow users to interact with the pallet and invoke state changes.// These functions materialize as "extrinsics", which are often compared to transactions.// Dispatchable functions must be annotated with a weight and must return a DispatchResult.1.[pallet::call]impl<T: Config> Pallet<T> {#[pallet::weight(1_000)]pub fn create_claim(origin: OriginFor<T>,proof: Vec<u8>,) -> DispatchResult {...} #[pallet::weight(10_000)]pub fn revoke_claim(origin: OriginFor<T>,proof: Vec<u8>,) -> DispatchResult {...}}cargo checkcargo buildstart nodeadd react componentsrc/TemplateModule.jsyarn startverify pallet functioncustom pallet

Publish Custom pallets

内置pallets

发布pallets

两种方法

  1. github发布
  2. crates.io发布

这两个方法其实就是rust的crate常见发布方式。

Develop smart contracts

Prepare your first contract

返回顶部

终端更新工具链,下载安装Substrate contract code终端更新工具链,下载安装Substrate contract code智能合约项目flipper/Cargo.toml智能合约项目flipper/Cargo.tomlcontracts-ui application智能合约web appcontracts-ui application智能合约web app更新工具链rustup component add rust-src --toolchain nightlyrustup target add wasm32-unknown-unknown --toolchain nightlycargo installcargo install contracts-node--git https://github.com/paritytech/substrate-contracts-node.gittag <latest-tag>force --locked 可以去这里查看标签号:substrate contract code安装相关包linux: sudo apt install binaryenmac: brew install binaryen cargo install dylint-linkcargo install cargo-contract --forcecargo contract --help新建智能合约项目cargo contract new flippercd flipper/ls -al-rwxr-xr-x 1 dev-doc staff 285 Mar 4 14:49 .gitignore-rwxr-xr-x 1 dev-doc staff 1023 Mar 4 14:49 Cargo.toml-rwxr-xr-x 1 dev-doc staff 2262 Mar 4 14:49 lib.rs修改scale和scale-infoscale = {package = "parity-scale-codec",version = "3",default-features = false,features = ["derive"] }scale-info = {version = "2",default-features = false,features = ["derive"],optional = true }Test the default contractcargo +nightly testBuild the contractcargo +nightly contract build此命令为 Flipper 项目构建:1. 一个 WebAssembly 二进制文件2. 一个包含合约应用程序二进制接口 (ABI) 的元数据文件3. 一个用于部署合约的 .contract 文件。 target/ink 目录中的 metadata.json 文件描述了你可以用来与这个合约交互的所有接口。该文件包含几个重要部分:1. 规范部分包括有关可以调用的函数(如构造函数和消息)2. 发出的事件以及可以显示的任何文档的信息。 本节还包括一个选择器字段,该字段包含函数名称的 4 字节散列,用于将合约调用路由到正确的函数。storage 部分定义了合约管理的所有存储项以及如何访问它们。类型部分提供了整个 JSON 其余部分中使用的自定义数据类型。Start the Substrate smart contracts nodesubstrate-contracts-node --devSelect Local NodeDeploy the contractAdd New Contract & UploadNew Contract CodeSelect an account, such asalice, and uplodatflipper.contractcreate an instance on theblockchainTry smart contracttest the get() functiontest the flip() function

Develop a smart contract

返回顶部

终端更新工具链,下载安装Substrate contract code终端更新工具链,下载安装Substrate contract codelib.rsincrementer/lib.rslib.rsincrementer/lib.rsCargo.tomlincrementer/Cargo.tomlCargo.tomlincrementer/Cargo.tomlcontracts-ui application智能合约web appcontracts-ui application智能合约web appcreate & cdcargo contract new incrementercd incrementer/updateincrementer sourcecodemodify scale and scale-infoscale = {package = "parity-scale-codec",version = "3",default-features = false,features = ["derive"]}scale-info = {version = "2",default-features = false,features = ["derive"],optional = true }test & buildcargo +nightly testcargo +nightly contract buildstore simple valuesupdate #[ink(storage)]1.[ink(storage)]pub struct MyContract {// Store a boolmy_bool: bool,// Store a numbermy_number: u32,}supported types &parity scalecodec// We are importing the default ink! typesuse ink_lang as ink; 1.[ink::contract]mod MyContract { // Our struct will use those default ink! types#[ink(storage)]pub struct MyContract {// Store some AccountIdmy_account: AccountId,// Store some Balancemy_balance: Balance,}/*snip*/}add contructorsuse ink_lang as ink; 1.[ink::contract]mod mycontract { #[ink(storage)]pub struct MyContract {number: u32,} impl MyContract {/// Constructor that initializes the `u32` value to the given `init_value`.#[ink(constructor)]pub fn new(init_value: u32) -> Self {Self {number: init_value,}} /// Constructor that initializes the `u32` value to the `u32` default.////// Constructors can delegate to other constructors.#[ink(constructor)]pub fn default() -> Self {Self {number: Default::default(),}}/*snip*/}}update smart contractreplace the StorageDeclaration1.[ink(storage)]pub struct Incrementer {value: i32,}modify the incrementerconstructorimpl Incrementer {#[ink(constructor)]pub fn new(init_value: i32) -> Self {Self {value: init_value,}}}add a second constructor1.[ink(constructor)]pub fn default() -> Self {Self {value: 0,}}cargo +nightly testadd a function to get a storage valueuse #[link(message)] to getmessage1.[ink(message)]pub fn get(&self) -> i32 {self.value}}add test codefn default_works() {let contract = Incrementer::default();assert_eq!(contract.get(), 0);}cargo +nightly testadd a function to modify the storage valueadd inc function in#[ink(message)]1.[ink(message)]pub fn inc(&mut self, by: i32) {self.value += by;}}add test code1.[ink::test]fn it_works() {let mut contract = Incrementer::new(42);assert_eq!(contract.get(), 42);contract.inc(5);assert_eq!(contract.get(), 47);contract.inc(-50);assert_eq!(contract.get(), -3);}cargo +nightly testbuild wasm filecargo +nightly contract builddeploy and test the smart contractsubstrate-contracts-node --devadd new contractOpen the Contracts UI and verify that it is connected to the local node.Click Add New Contract.Click Upload New Contract Code.Select the incrementer.contract file, then click Next.Click Upload and Instantiate.Explore and interact with the smart contract using the Contracts UI.

Use maps for storing values

返回顶部

lib.rsincrementer/lib.rslib.rsincrementer/lib.rs终端更新工具链,下载安装Substrate contract code终端更新工具链,下载安装Substrate contract codeinitialize a mapping1.![cfg_attr(not(feature = "std"), no_std)] use ink_lang as ink; 2.[ink::contract]mod mycontract {use ink_storage::traits::SpreadAllocate; #[ink(storage)]#[derive(SpreadAllocate)]pub struct MyContract {// Store a mapping from AccountIds to a u32map: ink_storage::Mapping<AccountId, u32>,} impl MyContract {#[ink(constructor)]pub fn new(count: u32) -> Self {// This call is required to correctly initialize the// Mapping of the contract.ink_lang::utils::initialize_contract(|contract: &mut Self| {let caller = Self::env().caller();contract.map.insert(&caller, &count);})} #[ink(constructor)]pub fn default() -> Self {ink_lang::utils::initialize_contract(|_| {})} // Get the number associated with the caller's AccountId, if it exists#[ink(message)]pub fn get(&self) -> u32 {let caller = Self::env().caller();self.map.get(&caller).unwrap_or_default()}}}identifying the contract callerusing the contract caller1.![cfg_attr(not(feature = "std"), no_std)] use ink_lang as ink; 2.[ink::contract]mod mycontract { #[ink(storage)]pub struct MyContract {// Store a contract ownerowner: AccountId,} impl MyContract {#[ink(constructor)]pub fn new() -> Self {Self {owner: Self::env().caller();}}/*snip*/}}add mapping to the smart contractimport1.[ink::contractmod incrementer {use ink_storage::traits::SpreadAllocate; #[ink(storage)]#[derive(SpreadAllocate)]add mapping keypub struct Incrementer {value: i32,my_value: ink_storage::Mapping<AccountId, i32>,}set an initial value in the newfunction1.[ink(constructor)]pub fn new(init_value: i32) -> Self {ink_lang::utils::initialize_contract(|contract: &mut Self| {contract.value = init_value;let caller = Self::env().caller();contract.my_value.insert(&caller, &0);})}set an initial value in thedefault function1.[ink(constructor)]pub fn default() -> Self {ink_lang::utils::initialize_contract(|contract: &mut Self| {contract.value = Default::default();})}add get function1.[ink(message)]pub fn get_mine(&self) -> i32 {self.my_value.get(&self.env().caller()).unwrap_or_default()}add test code1.[ink::test]fn my_value_works() {let contract = Incrementer::new(11);assert_eq!(contract.get(), 11);assert_eq!(contract.get_mine(), 0);}cargo +nightly testinsert, update, or remove valuesadd insert function1.[ink(message)]pub fn inc_mine(&mut self, by: i32) {let caller = self.env().caller();let my_value = self.get_mine();self.my_value.insert(caller, &(my_value + by));}add test code for insertfunction1.[ink::test]fn inc_mine_works() {let mut contract = Incrementer::new(11);assert_eq!(contract.get_mine(), 0);contract.inc_mine(5);assert_eq!(contract.get_mine(), 5);contract.inc_mine(5);assert_eq!(contract.get_mine(), 10);}add remove function1.[ink(message)]pub fn remove_mine(&self) {self.my_value.remove(&self.env().caller())}add test code for removefunction1.[ink::test]fn remove_mine_works() {let mut contract = Incrementer::new(11);assert_eq!(contract.get_mine(), 0);contract.inc_mine(5);assert_eq!(contract.get_mine(), 5);contract.remove_mine();assert_eq!(contract.get_mine(), 0);}cargo +nightly test

Buid a token contract

返回顶部

lib.rserc20/lib.rslib.rserc20/lib.rsCargo.tomlerc20/Cargo.tomlCargo.tomlerc20/Cargo.toml终端更新工具链,下载安装Substrate contract code终端更新工具链,下载安装Substrate contract codecontracts-ui application智能合约web appcontracts-ui application智能合约web appbuild an ERC-20 token smart contractcreate & cdcargo contract newnew erc20 codecd erc20/replace the default src withnewmodify [dependencies]scale = {package = "parity-scale-codec",version = "3",default-features = false,features = ["derive"] }scale-info = {version = "2",default-features = false,features = ["derive"],optional = true }cargo +nightly testcargo +nightly contract buildUpload an instantiate the contractconnect & upload contract fileand sequence operationsTransfer tokensadd Error declaration/// Specify ERC-20 error type.1.[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]2.[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]pub enum Error {/// Return if the balance cannot fulfill a request.InsufficientBalance,}add an Result return type/// Specify the ERC-20 result type.pub type Result<T> = core::result::Result<T, Error>;add the transfer()1.[ink(message)]pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> {let from = self.env().caller();self.transfer_from_to(&from, &to, value)}add transfer_from_to()fn transfer_from_to(&mut self,from: &AccountId,to: &AccountId,value: Balance,) -> Result<()> {let from_balance = self.balance_of_impl(from);if from_balance < value {return Err(Error::InsufficientBalance)} self.balances.insert(from, &(from_balance - value));let to_balance = self.balance_of_impl(to);self.balances.insert(to, &(to_balance + value));Ok(())}add balance_of_impl1.[inline]fn balance_of_impl(&self, owner: &AccountId) -> Balance {self.balances.get(owner).unwrap_or_default()}cargo + nightly testcreate eventsadd a transfer event1.[ink(event)]pub struct Transfer {#[ink(topic)]from: Option<AccountId>,#[ink(topic)]to: Option<AccountId>,value: Balance,}emit the event in new functionfn new_init(&mut self, initial_supply: Balance) {let caller = Self::env().caller();self.balances.insert(&caller, &initial_supply);self.total_supply = initial_supply;Self::env().emit_event(Transfer {from: None,to: Some(caller),value: initial_supply,});}emit the eventh intransfer_from_to functionself.balances.insert(from, &(from_balance - value));let to_balance = self.balance_of_impl(to);self.balances.insert(to, &(to_balance + value));self.env().emit_event(Transfer {from: Some(*from),to: Some(*to),value,});add test code for transferstokens1.[ink::test]fn transfer_works() {let mut erc20 = Erc20::new(100);assert_eq!(erc20.balance_of(AccountId::from([0x0; 32])), 0);assert_eq!(erc20.transfer((AccountId::from([0x0; 32])), 10), Ok(()));assert_eq!(erc20.balance_of(AccountId::from([0x0; 32])), 10);}cargo +nightly testenable third-party transfersdeclare the approval event1.[ink(event)]pub struct Approval {#[ink(topic)]owner: AccountId,#[ink(topic)]spender: AccountId,value: Balance,}add an Error declaration1.[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]2.[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]pub enum Error {InsufficientBalance,InsufficientAllowance,}add the storage mappingallowances: Mapping<(AccountId, AccountId), Balance>,add the approve()1.[ink(message)]pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> {let owner = self.env().caller();self.allowances.insert((&owner, &spender), &value);self.env().emit_event(Approval {owner,spender,value,});Ok(())}add the allowance()1.[ink(message)]pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {self.allowance_impl(&owner, &spender)}add the allowance_impl()1.[inline]fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance {self.allowances.get((owner, spender)).unwrap_or_default()}add test for the allowance()1.[ink::test]fn allowances_works() {let mut contract = Erc20::new(100);assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);contract.approve(AccountId::from([0x1; 32]), 200);assert_eq!(contract.allowance(AccountId::from([0x1; 32]), AccountId::from([0x1; 32])), 200); contract.transfer_from(AccountId::from([0x1; 32]), AccountId::from([0x0; 32]), 50);assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 50);assert_eq!(contract.allowance(AccountId::from([0x1; 32]), AccountId::from([0x1; 32])), 150); contract.transfer_from(AccountId::from([0x1; 32]), AccountId::from([0x0; 32]), 100);assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 50);assert_eq!(contract.allowance(AccountId::from([0x1; 32]), AccountId::from([0x1; 32])), 150);}add the transfer_from()/// Transfers tokens on the behalf of the `from` account to the `to account1.[ink(message)]pub fn transfer_from(&mut self,from: AccountId,to: AccountId,value: Balance,) -> Result<()> {let caller = self.env().caller();let allowance = self.allowance_impl(&from, &caller);if allowance < value {return Err(Error::InsufficientAllowance)}self.transfer_from_to(&from, &to, value)?;self.allowances.insert((&from, &caller), &(allowance - value));Ok(())}}add test code for thetransfer_from()1.[ink::test]fn transfer_from_works() {let mut contract = Erc20::new(100);assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);contract.approve(AccountId::from([0x1; 32]), 20);contract.transfer_from(AccountId::from([0x1; 32]), AccountId::from([0x0; 32]), 10);assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);}cargo +nightly testcargo +nightly contract build

Troubleshoot smart contracts

返回顶部

Connect with other chains

Start a local relay chain

返回顶部

波卡架构

parachains

注意版本匹配

您必须使用本教程中规定的确切版本。

平行链与它们连接的中继链代码库紧密耦合,因为它们共享许多共同的依赖关系。 在处理整个 Substrate 文档中的任何示例时,请务必将相应版本的 Polkadot 与任何其他软件一起使用。您必须与中继链升级保持同步,您的平行链才能继续成 功运行。如果你不跟上中继链升级,你的网络很可能会停止生产区块。

时序图

终端终端链规范文件Plain/Raw rococo-localrelay chain spec链规范文件Plain/Raw rococo-localrelay chain specBuild the relay chain nodegit clone & compile1.Clone the Polkadot Repository, with correct versiongit clonedepth 1branch release-v0.9.26 https://github.com/paritytech/polkadot.git 2.Switch into the Polkadot directorycd polkadot 3.Build the relay chain Node4.Compiling the node can take 15 to 60 minuets to complete.cargo b -r 5.Check if the help page prints to ensure the node is built correctly./target/release/polkadot --help准备链规范文件Plain rococo-local relay chain specRaw rococo-local relay chain spec注意:文件比较大,直接复制比较麻烦。可以git clone到本地后复制过来start the alice validatorstart the bob validator

Connect a local parachain

返回顶部

Common Good Parachains

Conver a solo chain

Parachain Slots Autcion

跨链用到的信息格式

时序图

终端终端链规范文件Plain/Raw rococo-localrelay chain spec链规范文件Plain/Raw rococo-localrelay chain specPolkadot-JS application波卡前端Polkadot-JS application波卡前端Polkadot-JS application 2波卡前端2, 验证平行链的实际使用Polkadot-JS application 2波卡前端2, 验证平行链的实际使用build the parachain templateclone & build1.Clone the parachain template with the correct Polkadot versiongit clone--depth 1--branchpolkadot-v0.9.26 https://github.com/substrate-developer-hub/substrate-parachain-template 2.Switch into the parachain template directorycd substrate-parachain-template 3.Build the parachain template collator4.Compiling the node can take 15 to 60 minuetscargo b -rreserve a paraIDgenerate a plain spec1.Assumes that `rococo-local` is in `node/chan_spec.rs` as the relay you registered with./target/release/parachain-collatorbuild-spec--disable-default-bootnode> rococo-local-parachain-plain.jsonset the correct reserved ParaID//snip"para_id": 2000, // <--- your already registered ID//snip"parachainInfo": {"parachainId": 2000 // <--- your already registered ID},//snipgenerate a raw chain spec./target/release/parachain-collatorbuild-spec--chain rococo-local-parachain-plain.json--raw--disable-default-bootnode> rococo-local-parachain-2000-raw.jsonSave and distribute your rawspecObtain Wasm runtimevalidation function./target/release/parachain-collatorexport-genesis-wasm--chainrococo-local-parachain-2000-raw.json> para-2000-wasmconvert the solo chain to aparachain此运行时和状态仅适用于平行链的创世块。您不能将具有任何先前状态的平行链连接到中继链。所有平行链必须从中继链上的区块 0 开始。之后可能推出迁移基于 Substrate 构建的单链的区块历史,但短期内不会计划此功能。generate a parachain genesisstate./target/release/parachain-collatorexport-genesis-state--chainrococo-local-parachain-2000-raw.json> para-2000-genesisstart a collator node./target/release/parachain-collator--alice--collator--force-authoring--chain rococo-local-parachain-2000-raw.json--base-path /tmp/parachain/alice--port 40333--ws-port 8844--execution wasm--chain <relay chain raw chain spec>--port 30343--ws-port 9977关于这个命令,首先要注意的是:在单独的 -- 之前传递了几个参数,在它之后传递了更多参数。平行链collator包含实际的收集人节点以及嵌入式中继链节点。1. -- 之前的参数是针对整理者的,2. -- 之后的参数是针对嵌入式中继链节点的。 如果为第二条平行链再次执行这些指令,请记住更改collator-specific的值。必须使用相同的中继链规范文件,但要绑定到的第二个平行链收集器.使用不同的 ParaID、基本路径和端口号。目前没有嵌入中继链节点的平行链节点无法运行,但预计最后也会实现非收集节点。 执行命令后应该看到收集人节点作为独立节点运行,并且其中继节点作为对等节点与已经运行的中继链验证节点连接。 请注意,如果没有看到嵌入式中继链与本地中继链节点对等,请尝试禁用防火墙或添加带有中继节点地址的 bootnodes 参数。 此时还没有开始授权平行链区块。当收集人实际在中继链上注册时,才会开始。Parachain Registration有几种方法可以将平行链注册到中继链。1. 对于测试,使用 sudo 是最常见的。我们已经启动了中继链,并且我们的平行链整理器已经准备就绪。现在我们必须在中继链上注册平行链。在生产网络中,这通常通过平行链拍卖(parachain auctions)来完成。但是对于本教程,我们将使用 sudo 调用来完成。 2. 注册交易交易可以通过 Polkadot-JS Apps UI 在中继链节点上提交。有多种选择可以解决这个问题,我们可以选择以下任何一种。请注意,此处的所有选项都取决于保留的 paraID - 因此请务必先执行此操作。Registration Transactionalt[option1]paraSudoWrapper.sudoScheduleParaInitialize此选项完全绕过插槽租赁机制,从下一个中继链会话开始为保留的 paraID加入平行链或平行线程。这是进行测试的最简单和最快的方法。 但请注意,平行链将这些注册平行链所需的文件包含在链规范中设置的详细信息,这些详细信息必须明确针对正确的中继链并使用正确的 ParaID.在这种情况下,是 rococo(而不是本教程中使用的 rococo-local) .[option2]slots.forceLease这是生产中使用的更正式的注册流程(除了使用 sudo 强制槽租用):使用保留它的同一帐户注册您的保留 paraID,或者使用 sudo 和 registrar.forceRegister 交易操作,如果你希望。block productionThe collator should start producing parachain blocks(aka collating) once the registration is successfuland the next relay chain epoch begin!see block finalization中继链跟踪每个平行链的最新块(头部)。 当一个中继链区块最终确定时,每个已完成验证过程的平行链区块也将最终确定。这就是 Polkadot 为其平行链实现池化、共享安全性的方式!Connecting with the Apps UI我们已经将 Apps UI 连接到中继链节点。现在我们还可以连接到平行链收集器。在新的浏览器窗口中打开另一个应用程序 UI 实例,并将其连接到适当的端点。 如果您到目前为止已经按照本教程进行操作,则可以连接到平行链节点Submit extrinsics 您可以进行一些简单的代币转账,以确保平行链正常运行。 您还可以通过转到外部页面,选择系统托盘并注释外部来进行一些链上注释。如果交易按预期进行,你就有了一个有效的平行链。链条清洗1.Purge the collator(s)./target/release/parachain-collatorpurge-chain--base-path <your collator DB path set above> 2.Purge the validator(s)polkadotpurge-chain--base-path <your relay chain DB path set above>收集者节点是平行链区块链数据的唯一归宿,因为整个平行链网络上只有一个节点。中继链仅存储平行链标头信息。 如果平行链数据丢失,您将无法恢复该链。但是,在测试中,您可能需要从头开始。

Connect to Rococo testnet

返回顶部

substrate预置账户和密钥

wallets

polkadot-js/extension

SS58地址格式

Rococo faucet martic channel

时序图

useruserPolkadot-JS Rococo app波卡洛可可前端Polkadot-JS Rococo app波卡洛可可前端浏览器浏览器做其他申请操作浏览器浏览器做其他申请操作终端用于检查和编译终端用于检查和编译Set up a walletconnect & have an accountbackup & make noteBack up your secret seed phrase. Make note of your accountID that isusing the default 42 SS58 prefix foruse with Rococo.acquire ROC inRococo faucet matrix channelAcquire a ParaIDNetwork >> ParathreadsReserve a unique ParaIDMake note of this请注意,在 Rococo 上,ParaID 编号 0-999 保留给系统平行链,而 1000-1999 保留给公用工具平行链. 只有尚未保留的 2000 及以上数字才能用于社区平行链。generate file & startgenerate parachain genesis and wasm files注册平行链所需的文件必须明确指定正确的中继链和正确的 ParaID。 在本教程中,中继链是 rococo,而不是 Connect a local parachain教程中使用的 rococo-local。 配置您的链规范以使用:1. 你的Rococo ParaID。2. 您的整理者节点的开发密钥和帐户的独特替代方案。虽然 Alice 和此类帐户可以使用,但您绝对不应该使用它们!3. 为洛可可生成适当的平行链创世状态。4. 为 Rococo 生成平行链运行时 Wasm blob。start your collator对于嵌入式中继链和平行链,您必须拥有可公开访问和发现的整理者对等端口。这样你就可以与洛可可验证节点对等,否则你将无法产生区块! 对等端口使用port <collator node>--port <relay node>这两个 CLI 标志设置,请务必分别对两个节点执行此操作 您很可能至少希望您的collator的 --ws-port <ws port>端口也打开,以允许您自己(和其他人)通过 Polkadot应用程序 UI 或 API 调用与其连接。register and requestRegister as a parathread申请开通你的平行链插槽永久插槽是分配给当前在 Polkadot 上拥有平行链插槽的团队(在成功的插槽租赁拍卖之后)的平行链插槽,因此需要不断测试他们的代码库以与现实世界中的最新前沿功能兼容(洛可可)。 只有有限数量的永久插槽可用临时插槽是平行链插槽,以连续的循环方式动态分配。 在每个租期开始时,一定数量的平行线程(最多为中继链配置中定义的最大值)会在一定时间内自动升级为平行链。在租约结束期间处于活动状态的平行链会自动降级为平行线程,以释放插槽供其他人在后续期间使用。 临时插槽旨在供尚未在 Polkadot 上拥有平行链插槽的团队使用,并计划在不久的将来使用。在平行链作为平行线程激活后,相关项目团队应在Rococo 上打开永久或临时平行链插槽的请求 Rococo 运行时需要 sudo 访问权限来分配槽(AssignSlotOrigin = EnsureRoot<Self::AccountId>;)。 Rococo sudo 密钥目前由 Parity Technologies 控制,因此要进行获取插槽所需的操作,请在完成上述操作并准备好连接后转到 Subport repo 并打开 Rococo Slot Request!激活您的插槽后,Parity 团队成员将做出回应。 最终,该过程旨在通过洛可可式治理框架由社区驱动。assign a temporary slot1. Open Polkadot-JS Apps for Rococo.2. Click Developer > Extrinsics.3. Select the account you want to use to submit the transaction.4. Select the assignedSlots pallet .5. Choose the assignTempParachainSlot function.6. Insert your reserved ParaID.Make sure you use the ParaID you previously reserved.7. Select Current for the LeasePeriodStart.If the current slot is full, you will be assigned the next available slot.8. Sign and submit the transaction.给定 1 天的租期,洛可可分配的平行链插槽的当前设置是(在撰写本文时):1. 永久插槽最短持续时间:1 年(365 天)2. 临时时段最短持续时间:3 天3. 永久插槽的最大数量:最多 25 个永久插槽4. 最大临时槽数:最多 20 个临时槽5. 每个租期分配的最大临时时段:每 3 天临时租期最多 5 个临时时段Congratulations!Parity 团队激活您的插槽后,您可以在 Rococo 测试网络上测试您的平行链。 请注意,当您的临时插槽租约结束时,平行链会自动降级为平行线程。注册和批准的插槽以循环方式自动循环,因此您可以不时作为平行链重新上线。

Access EVM accounts

返回顶部

Ethereum core concepts and terminology

Ethereum Virtual Machine (EVM) basics

Decentralized applications and smart contracts

Pallet design principles

Truffle

Truffle - Truffle Suite

Remix IDE

Remix - Ethereum IDE

时序图

终端终端Polkadot-JS application波卡前端Polkadot-JS application波卡前端本节背景本教程说明了如何使用Frontier项目中的 crate 来构建与以太坊兼容的区块链,该区块链可以访问基于以太坊的账户并执行基于 Solidity 的智能合约。 Frontier 项目的两个主要目标是使您能够执行以下操作:1. 使用本地 Substrate 节点运行未经修改的以太坊去中心化应用程序(Dapp)。2. 从以太坊主网络导入状态。 本教程使用预定义的节点模板来提供工作环境。该模板是使用Frontier发布指南中的说明生成的。 如果你想为自己生成一个独立的模板,你可以使用node-template-release.sh 模板生成脚本。 如果您使用frontier 分支或模板生成脚本构建自己的节点,请注意 Frontier 使用其自己的Substrate crates 版本,您可能需要更新 Cargo 文件中的依赖项以匹配项目中的依赖项。chain specification filefrontier-node-template 中的开发链规范定义了一个创世区块,该创世区块已预先配置了 alice 账户的 EVM 账户。 当您在开发模式下启动此节点时,alice 的 EVM 帐户将默认分配一定数量的eth。您将使用此帐户查看 EVM 帐户详细信息并调用以太坊智能合约。 启动节点后,您将能够使用 Polkadot-JS 应用程序查看 alice 的 EVM 帐户的详细信息。terminal -> terminal: clone -> compile -> startnote leftgit clone https://github.com/substrate-developer-hub/frontier-node-template.gitcd frontier-node-templatecargo build --release./target/release/frontier-template-node --devconnect local nodeSettings >> Developer >> Define to create an EVM Account{"Address": "MultiAddress","LookupSource": "MultiAddress","Account": {"nonce": "U256","balance": "U256"},"Transaction": {"nonce": "U256","action": "String","gas_price": "u64","gas_limit": "u64","value": "U256","input": "Vec<u8>","signature": "Signature"},"Signature": {"v": "u64","r": "H256","s": "H256"}}Developer >> RPC calls1. Verify that your node is still running andthe Polkadot-JS application is connected to the node.2. Click Developer, then select RPC calls.3. On the Submission tab, select eth as the endpoint to call.4. Select getBalance(address, number) from the list of functions to call.5. Specify the EVM account identifier for the alice account for the address. The predefined account address is:- 0xd43593c715fdd31c61141abd04a99fd6822c8558. The address for the account was calculated fromthe public keyfor the alice account using Substrate EVM utilities. 6. Click Submit RPC call.deploy a smart contract现在您已经了解了如何查询以太坊地址的余额,您可能想探索如何部署和调用以太坊智能合约并测试相关功能。 本教程使用定义 ERC-20 代币的 Truffle 示例合约。您还可以使用 Polkadot JS SDK 和 Typescript创建 ERC-20 代币合约。create the ERC-20 contractDeveloper >> Extrinsics >> ALICE >>evm >> create >> Configure >>Submit Transaction >> Sign and Submitsource0xd43593c715fdd31c61141abd04a99fd6822c8558initraw bytecode hex value from MyToken.jsonvalue0gasLimit4294967295maxFeePerGas100000000view the smart contract、view -> transfer -> verifyNetwork >> Explorer >> evm.Created eventDeveloper >> Extrinsics >> ALICE>> evm >> call transfer(address, uint256)sourcetargetinputvaluegasLimitmaxFeePerGasNetwork >> Explorer >> evm.Excuted

参考资源

substrate文档练习

  • 构建一条链的体验

    substrate官方教程里面的第一课名称叫做创建我们的第一条链, 实际上我觉得应该叫做启动substrate默认模板链的节点更贴切,因为这个教程里面实际上就是把一个用substrate已经开发好的模板链的代码拉下来,然后编译一下,然后再启动起来。 这个过程实际上和我们拉一个比特币的代码,然后编译下然后再启动 ,并没有太大的不同。

    • substrate 开发环境

      • 启动链的节点: > 这里要用到node-template的代码。node-template实际上是官方提供的使用substrate开发的模板链, > 可以理解为substrate官方提供的样例,后续任何人想使用substrate可以在这个样例的基础上进行修改,这样开发链就更方便。 > 这就好比以前的好多山寨链,在btc的源码上改下创世区块的配置,就是一条新链。 那么substrate其实也一样,提供了node-template这样一个模板,后续根据需求在这个上面改吧改吧,就能产生一条新链。
    • 使用polkadot-js访问节点: 在substrate官方的教程中,是使用了substrate的前端模板来访问刚才启动的节点。但是在实际的开发中,后端人员其实更多的使用polkadot-js-app来访问我们的节点,所以这里我们也使用它来访问我们的节点。

    • 文档资料

  • Substrate快速入门

    substrate采用模块化的方法进行开发,它定义了一组丰富的原语,给开发人员提供了强大的、熟悉的编程方法。

    • 使用方式介绍:

      1. 使用substrate node: 使用json文件启动

      2. 使用substrate frame: 业务逻辑自由

        frame其实是一组模块(pallet)和支持库。使用substrate frame可以轻松的创建自己的自定义运行时,因为frame是用来构建底层节点的。使用frame还可以配置数据类型,也可以从模块库中选择甚至是添加自己定义的模块。

      3. 使用substrate core: runtime自由

        使用substrate code运行开发者完全从头开始设计运行时(runtime,问题:什么是runtime?),当然此种方式也是使用substrate自由度最大的方式。

    • Substrate Client:

      substrate客户端是基于substrate实现的区块链的节点客户端(可以理解为全节点),它主要由以下几个组件组成(以下也就是告诉我们实现一条链由哪几部分组成):

      1. 存储: 用来维持区块链系统所呈现的状态演变。substrate提供了的存储方式是一种简单有效的key-value对存储机制的方式。
      2. Runtime: 这里就可以回答上面的问题,什么是runtime?runtime定义了区块的处理方式,主要是状态转换的逻辑。在substrate中,runtime code被编译成wasm作为区块链存储状态的一部分。
      3. p2p网络: 允许客户端和其它网络参与者进行通信。
      4. 共识: 提供了一种逻辑,能使网络参与者就区块链的状态达成一致。substrate支持提供自定义的共识引擎。
      5. RPC: 远程过程调用。
      6. telemetry: 通过嵌入式Prometheus服务器的方式对外展示(我理解应该是类似于区块链浏览器一样的东西,或者是提供信息给区块链浏览器展示)。
  • 构建一个PoE(Prove of Existence)去中心化的应用

    substrat官方手册的第三个例子,使用substrate来创建自定义的存在证明dapp。我们本节的主要内容分为以下三步:

    1. 基于node template启动一条substrate的链。
    2. 修改node template来添加我们自己定制的Poe pallet,并实现我们的PoE API。
    3. 修改前端模板以添加与PoE API交互的自定义用户界面。
    • 接口设计
    • 创建自定义pallet

      node template的运行时是基于FRAME来实现的。FRAME是一个代码库,允许我们使用一系列pallet来构建底层的运行时。pallet可以看出是定义区块链功能的单个逻辑模块。subtrate为我们提供了一些已经构建好的pallet,我们在定义运行时时可以直接使用。

  • 使用substrate构建私有网络

    本节内容:

    1. 基于模板启动substrate区块链网络;
    2. 生成ed25519和sr25519 密钥对用于网络授权;
    3. 创建和编辑chainspec json文件。
  • 构建kitties链条

    分两部分,一是介绍如何构建kitties pallet,包括创建与kittes交互的功能;另一部分是介绍开发前端UI,与我们第一部分的链进行交互。 目标:

    1. 学习构建和运行substrate节点的基本模式。
    2. 编写自定义框架pallet并集成到运行时。
    3. 了解如何创建和更新存储项。
    4. 编写pellet相关辅助函数。
    5. 使用PolkadotJs API将substrate节点连接到自定义前端。
    • kitties功能:
      1. 可以通过一些原始来源或者通过使用现有小猫进行繁殖创造。
      2. 以其所有者设定的价格出售。
      3. 从一个所有者转移到另一个所有者。
    • 参考资料
  • 构建授权网络

    无许可准入区块链网络我们比较常见,例如比特币、以太坊都是无准入的。那么授权准入网络在哪些地方可能出现呢?

    1. 专用网络或者是联盟链网络;
    2. 高度管控的环境;
    3. 大规模测试预公开网络。

    目标

    1. 修改node-template工程添加node-authorization pallet。
    2. 加载部分节点并授权新节点加入网络。

pallet基础

尝试添加pallet到runtime

  • 添加一个pallet到runtime

    substrate node template提供了一个最小的可工作的运行时,但是为了保持精炼,它并不包括Frame中的大多数的Pallet。本节我们将学习如何将Pallet添加到runtime中。

    1. 安装Node Template
    2. 导入Pallet
    3. 配置Pallet
    4. 将Nicks添加到construct_runtime!中

智能合约

初探ink!

  • 初探ink!智能合约开发
    • 发展两年的波卡智能合约语言ink!将会带来什么影响? - 知乎

      ink!是由Parity在这里开发智能合约语言Rust编写智能合同并编译成Wasm代码。 智能合同是在分散区块链网络上运行的计算机协议,可视为可自动执行的应用程序。 ink!它不同于其他更成熟的智能合约语言Parity的烙印。ink!最初是通过使用Rust宏系统生成自定义语法和风格,开发智能合约。 但是这种方法偏离了Rust在不牺牲易用性或开发性的前提下,开发人员熟悉和喜欢的语言诞生了ink!2.0.为开发人员提供最大的灵活性。简单来说,整合后现在ink!所有的结构和语法都是纯的Rust

    • 参考

深入ink!

  • 深入ink!智能合约

    学习智能合约的开发,主要包括:

    • ink!智能合约的结构;
    • 存储单个的值和hash map;
    • 安全的设置或获取这些值;
    • 编写public和private函数;
    • 配置Rust使用安全的数学库。

    总的来说,写ink!合约和直接用Rust编码没有太大的区别,只要能使用Rust都能很快的编写合约。

ERC20

连接其他链

中继链连接

学习启动一个relay chain, 通过cumus来创建自己的parachain,并且在在本地测试网络中将parachain连接到relaychain。

平行链连接

  • 连接parachain

    上一节,我们启动了relaychain的节点,本节将连接parachain到relaychain节点。 主要包括以下内容:

    • 启动parachain;
    • parachain注册;
    • 和parachain交互;
    • 连接到添加的parachain节点。

测试

编写测试

大部分用第一种

  • 第一种情况相对来说比较简单,这个也比较好找到例子。
    • 第二种情况则比较复杂,写起来也比较麻烦。
    • 不过在我们的开发中,大部分都是第一种情况。

benchmarking

升级

  • learn-substrate-easy/11升级runtime.md at main · KuanHsiaoKuo/learn-substrate-easy

  • learn-substrate-easy/12升级substrate版本.md at main · KuanHsiaoKuo/learn-substrate-easy

  • 无分叉runtime升级

    substrate框架的特性之一就是支持无分叉运行时升级。无分叉升级时以区块链自身能力支持和保护的方式增强区块链运行时的一种手段,区块链的运行时定义了区块链可以保持的状态,还定义了改变该装填的逻辑。 substrate可以在不分叉的情况下更新runtime,因为运行时的定义本身就是substrate链中的一个元素,网络参与者可以通过交易函数,特别是set_code函数来更新该值。 由于运行时状态的更新受到区块链共识机制和加密安全的约束,网络参与者可以在不分叉的情况下使用不受信任分发的更新或者扩展的运行时逻辑,甚至不需要发布新的区块链客户端。

    • 本节内容:
      1. 使用sudo调用将schelduler
      2. pallet包含到runtime中;
      3. 调用runtime升级。