通过本文,你已经了解到了如何修改 node-template 发布一个可用的公开测试网节点程序,chain spec 的组成和配置方法,最后我们模拟了如何渐进地启动一个公开测试网络。

通过本文,你会了解到:

  • 如何修改node template,使它能够提供完整的权益证明(Proof of Stake, PoS)功能;
  • Chain Specification的内容,以及如何生成它;
  • 配置和启动公开测试网络等。

添加 PoS 功能

Substrate node template 是一个快速开发Substrate应用链的节点程序,它内置的是权威证明(Proof of Authority, PoA)共识算法,出块算法是 Aura,也就是出块的节点和顺序是固定不变的。一个线上的公开

上面的例子中,为了易读性缺省了一些必要的信息,如system的code、其他的初始账户和验证人信息等。

那么,如何生成 Chain Spec 呢?

  1. 在节点的chain_spec.rs文件中添加自定义的GenesisConfig信息,如初始runtime 的 wasm 代码,初始区块存在的账户和余额,初始验证人的信息,治理团体的信息等:
fn staging_testnet_genesis() -> GenesisConfig {     // subkey inspect "$SECRET"     let endowed_accounts = vec![        // 5FemZuvaJ7wVy4S49X7Y9mj7FyTR4caQD5mZo2rL7MXQoXMi        hex!["9eaf896d76b55e04616ff1e1dce7fc5e4a417967c17264728b3fd8fee3b12f3c"].into(),        ... ...     ];      // for i in 1 2 3 4; do for j in stash controller; do subkey inspect "$SECRET//$i//$j"; done; done     // for i in 1 2 3 4; do for j in babe; do subkey --sr25519 inspect "$SECRET//$i//$j"; done; done     // for i in 1 2 3 4; do for j in grandpa; do subkey --ed25519 inspect "$SECRET//$i//$j"; done; done     // for i in 1 2 3 4; do for j in im_online; do subkey --sr25519 inspect "$SECRET//$i//$j"; done; done     let initial_authorities: Vec<(      AccountId,      AccountId,      BabeId,      GrandpaId,      ImOnlineId,     )> = vec![(      // 5Grpw9i5vNyF6pbbvw7vA8pC5Vo8GMUbG8zraLMmAn32kTNH      hex!["d41e0bf1d76de368bdb91896b0d02d758950969ea795b1e7154343ee210de649"].into(),      // 5DLMZF33f61KvPDbJU5c2dPNQZ3jJyptsacpvsDhwNS1wUuU      hex!["382bd29103cf3af5f7c032bbedccfb3144fe672ca2c606147974bc2984ca2b14"].into(),      // 5Dhd2QbrSE4dyNn3YUg8j5TY3fG7ZAWZMoRRF9KUc7VPVGmC      hex!["48640c12bc1b351cf4b051ac1cf7b5740765d02e34989d0a9dd935ce054ebb21"].unchecked_into(),      // 5C6rkxAZB437B5Bf1yS4B4qjW4HZPeBp8Kzx2Se9FLKhfyHY      hex!["01a474a93a0cf830fb40b1d17fd1fc7c6b4a95fa11f90345558574a72da0d4b1"].unchecked_into(),      // 5DscuovXyY1o7DxYroYjYgipn87eqYLyQA3HJ21Utb7TqAai      hex!["50041e469c63c994374a2829b0b0829213abd53be5113e751043318a9d7c0757"].unchecked_into(),     ),     ... ...     ];      const ENDOWMENT: u128 = 1_000_000 * DOLLARS;     const STASH: u128 = 100 * DOLLARS;     let num_endowed_accounts = endowed_accounts.len();      GenesisConfig {      system: Some(SystemConfig {        code: WASM_BINARY.to_vec(),        changes_trie_config: Default::default(),      }),      balances: Some(BalancesConfig {        balances: endowed_accounts.iter()          .map(|k: &AccountId| (k.clone(), ENDOWMENT))          .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))          .collect(),      }),      babe: Some(BabeConfig {        authorities: vec![],      }),      grandpa: Some(GrandpaConfig {        authorities: vec![],      }),      sudo: Some(SudoConfig {        key: endowed_accounts[0].clone(),      }),      session: Some(SessionConfig {        keys: initial_authorities.iter().map(|x| {          (            x.0.clone(),            x.0.clone(),            session_keys(x.2.clone(), x.3.clone(), x.4.clone())          )        }).collect::<Vec<_>>(),      }),      staking: Some(StakingConfig {        validator_count: initial_authorities.len() as u32 * 2,        minimum_validator_count: initial_authorities.len() as u32,        stakers: initial_authorities          .iter()          .map(|x| (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator))          .collect(),        invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(),        force_era: Forcing::ForceNone,        slash_reward_fraction: Perbill::from_percent(10),        .. Default::default()      }),      im_online: Some(ImOnlineConfig {        keys: vec![],      }),      democracy: Some(DemocracyConfig::default()),      elections_phragmen: Some(ElectionsConfig {        members: endowed_accounts.iter()              .take((num_endowed_accounts + 1) / 2)              .cloned()              .map(|member| (member, STASH))              .collect(),      }),      collective_Instance1: Some(CouncilConfig::default()),      collective_Instance2: Some(TechnicalCommitteeConfig {        members: endowed_accounts.iter()              .take((num_endowed_accounts + 1) / 2)              .cloned()              .collect(),        phantom: Default::default(),      }),      membership_Instance1: Some(Default::default()),      treasury: Some(Default::default()),     } } 

初始区块的账户可以是网络的管理员、发起者,或者其他社区成员,初始余额可以根据对网络的贡献进行分配。我们可以使用 subkey 工具生成所需的初始验证人信息,如stash和controller 账户,以及由Babe、GRANDPA、im-online组成的Session Keys,其中GRANDPA使用的是ed25519加密算法,其它则使用sr25519加密算法。subkey的使用方法可以参考官方文档 The subkey Tool,这里用到的命令在代码的注释中已经给出。

接着,就可以使用 ChainSpec::from_genesis 将通用的配置信息和 GenesisConfig 组合成Chain Spec,这里bootnodes的信息可以为空,在我们启动bootnode并获取到它的PeerId(打印在命令行输出)之后,修改Chain Spec 的 JSON 文件即可:

pub fn tao_staging_testnet_config() -> ChainSpec {     let boot_nodes = vec![];      ChainSpec::from_genesis(       "My Staging Testnet",       "my_staging",       ChainType::Live,       staging_testnet_genesis,       boot_nodes,       Some(         TelemetryEndpoints::new(vec![("wss://telemetry.polkadot.io/submit/".to_string(), 0)])           .expect("Westend Staging telemetry url is valid; qed")       ),       Some("my_staging"),       None,       Default::default(),     ) } 

2. 修改 command.rs 里的 load_spec,添加公开测试网络的解析方式:

fn load_spec(&self, id: &str) -> Result<Box<dyn sc_service::ChainSpec>, String> {     Ok(match id {          "dev" => Box::new(chain_spec::development_config()),          "" | "local" => Box::new(chain_spec::local_testnet_config()),          "my-staging" => Box::new(chain_spec::staging_testnet_config()),          path => Box::new(chain_spec::ChainSpec::from_json_file(            std::path::PathBuf::from(path),          )?),     }) } 

3. 使用build-spec子命令生成Chain Spec 配置文件,命令为./target/release/node-template build-spec --chain my-staging > my-staging.json

4. 如果要发布公开网络,需要使用编码后的Chain Spec文件,从而确保网络中的各个节点使用相同的初始状态即genesis hash相同,命令为:./target/release/node-template build-spec --chain=my-staging.json --raw > my-staging-raw.json,将此文件分发即可,也可以修改 staging_testnet_config,添加以配置文件加载网络的解析方式,

/// Staging testnet generator pub fn staging_testnet_config() -> Result<ChainSpec, String> {      ChainSpec::from_json_bytes(&include_bytes!("../res/my-staging-raw.json")[..]) } 

5. 配置bootnodes,启动某个bootnode的命令为:

./target/release/node-template         --node-key c12b6d18942f5ee8528c8e2baf4e147b5c5c18710926ea492d09cbd9f6c9f82a         --base-path /tmp/bootnode1         --chain my-staging-raw.json         --name bootnode1

node-key 可使用 subkey ed25519的方式生成;如果默认的chain就是my-staging-raw.json所指定的,--chain也可以缺省。

记录打印的PeerId(即这里的12D3KooWMmsXriTjqeiw4Us9LLgzRGiUmq8f5frBvyJgaYAwNcrU),结合bootnode的IP地址或者域名更新 Chain Spec 文件并重新分发。最好有多个bootnodes,防止出现单点故障。

{      "name": "My Staging Testnet",      "id": "my_staging",      "chainType": "Live",      "bootNodes": [        "/ip4/your-ip-address/tcp/30333/p2p/12D3KooWMmsXriTjqeiw4Us9LLgzRGiUmq8f5frBvyJgaYAwNcrU"      ],      ... ... }

启动公开测试网络

为了确保公开网络的发布过程顺利,不会出现大范围的故障,这里我们借鉴 Kusama/Polkadot 正式网络的发布流程,具体可以分为下面几个步骤:

Substrate 部署公开测试网络

Substrate 部署公开测试网络

在部署和升级的过程中,可以借助一些监控和辅助工具如:

  • Prometheus,用来监控节点的状态,参考文档 Visualizing Node Metrics;
  • srtool,编译 runtime 的 wasm 代码,用来解决不同的硬件、操作系统导致同样的代码编译结果不同,无法验证的问题;
  • 其它工具。

启动网络的 PoA 模式

在这一阶段,要确保:

  • Staking模块的初始配置即 StakingConfig 的 force_era 设置为 ForceNone,这样的话,网络的验证人不会变化;

  • 使用 system 模块提供的 BaseCallFilter 过滤掉当前阶段非必须的功能模块,从而不会因为网络不稳定导致无效交易的发生,示例代码参考这里;

  • 启动初始验证人,命令为:

./target/release/node-template        --base-path  /tmp/validator1        --chain   my-staging-raw.json        --bootnodes  /ip4/your-ip/tcp/30333/p2p/12D3KooWBmAwcd4PJNJvfV89HwE48nwkRmAgo8Vy3uQEyNNHBox2        --name  validator1        --validator 

--chain--bootnodes如果已经配置在默认启动模式下,可缺省;需要将初始验证人集合所指定的验证人都启动,并且不少于3个。

启动之后,给每个验证人设置 Session Keys,这里以curl发送http请求进行添加为例,

// 添加Babe的密钥 curl http://localhost:9933  -H "Content-Type:application/json;charset=utf-8" -d "@babe1"  // babe1文件的内容 {     "jsonrpc":"2.0",     "id":1,     "method":"author_insertKey",     "params": [         "babe",         "own word vocal dog decline set bitter example forget excite gesture water//1//babe",         "0x48640c12bc1b351cf4b051ac1cf7b5740765d02e34989d0a9dd935ce054ebb21"     ] }

GRANDPA,im-online 密钥的设置方式类似,更多内容可以参考文档 Creating Your Private Network。注意:GRANDPA密钥设置之后,需要重启节点

  • 允许提名和验证意向,等待拥有充足的提名和验证人之后,就可以考虑切换为 PoS 模式。

切换网络为 PoS 模式

网络在PoA阶段稳定并且没有可见的故障后,可以使用 sudo 权限调用 staking 模块的 force_new_era,开启验证人的选举,一段时间之后,选举得出的新验证人将会开始生产和验证区块,在这一阶段:

  • 可以根据网络情况,调节验证人数量,既要保证一定程度的去中心化,也要确保网络的时延、区块生产间隔稳定、可靠;
  • 使用 sudo 权限取消针对验证人的不必要惩罚。

启用治理功能

通过修改BaseCallFilter的逻辑,可以启用所需的治理功能,包括:

  • 投票选举议会成员;
  • 议会成员通过提案维护网络;
  • 网络用户通过提案申请国库资金,不过需要等待转账功能开启才可以真正转账;
  • 通过公投提案升级网络等等。

删除 sudo 权限

在网络的早期,合理地使用 sudo 权限,可以高效地管理网络的运转;但是在一个去中心化的网络中,这样的一个超级管理员,不符合去中心的价值观,在合适的时间点要进行删除,通常是在开启治理的一段时间之后。

删除 sudo 需要使用链上升级的方式,也就要求对此升级进行全民公投,由公投结果来决定是否删除 sudo 模块。

开启转账和其它功能模块

接着就可以一步步地引入和开启更多的功能模块,如转账、链上身份注册等等。

总结

通过本文,你已经了解到了如何修改 node-template 发布一个可用的公开测试网节点程序,chain spec 的组成和配置方法,最后我们模拟了如何渐进地启动一个公开测试网络。注意,本文所列代码未经过审计,不可直接应用于生产环境。

更多

Substrate官方文档:

Official Substrate Documentation for Blockchain Developers · Substrate Developer Hub​substrate.dev

Parity介绍:

https://www.parity.io/​www.parity.io

Substrate源码:

https://github.com/paritytech/substrate​github.com

Polkadot源码:

https://github.com/paritytech/polkadot​github.com

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注