上面的例子中,为了易读性缺省了一些必要的信息,如system的code、其他的初始账户和验证人信息等。
那么,如何生成 Chain Spec 呢?
- 在节点的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 正式网络的发布流程,具体可以分为下面几个步骤:
在部署和升级的过程中,可以借助一些监控和辅助工具如:
- Prometheus,用来监控节点的状态,参考文档 Visualizing Node Metrics;
- srtool,编译 runtime 的 wasm 代码,用来解决不同的硬件、操作系统导致同样的代码编译结果不同,无法验证的问题;
- 其它工具。
启动网络的 PoA 模式
在这一阶段,要确保:
./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 Hubsubstrate.dev
Parity介绍:
https://www.parity.io/www.parity.io
Substrate源码:
https://github.com/paritytech/substrategithub.com
Polkadot源码:
https://github.com/paritytech/polkadotgithub.com