mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 01:11:10 +00:00
Serialisable genesis config (#229)
* Genesis serialization * Custom type for AuthorityId * Merge w master * Fixed a few minor issues * Fixed unmerged file * Renamed tag * Deferred genesis loading * Upated wasm runtime * Minor issues
This commit is contained in:
committed by
Gav Wood
parent
276c464b50
commit
9b885ba092
@@ -16,6 +16,9 @@
|
||||
|
||||
//! Predefined chains.
|
||||
|
||||
use service;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// The chain specification (this should eventually be replaced by a more general JSON-based chain
|
||||
/// specification).
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -32,6 +35,19 @@ pub enum ChainSpec {
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
/// Get a chain config from a spec setting.
|
||||
impl ChainSpec {
|
||||
pub(crate) fn load(self) -> Result<service::ChainSpec, String> {
|
||||
Ok(match self {
|
||||
ChainSpec::PoC1Testnet => service::ChainSpec::poc_1_testnet_config()?,
|
||||
ChainSpec::Development => service::ChainSpec::development_config(),
|
||||
ChainSpec::LocalTestnet => service::ChainSpec::local_testnet_config(),
|
||||
ChainSpec::PoC2Testnet => service::ChainSpec::poc_2_testnet_config(),
|
||||
ChainSpec::Custom(f) => service::ChainSpec::from_json_file(PathBuf::from(f))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for ChainSpec {
|
||||
fn from(s: &'a str) -> Self {
|
||||
match s {
|
||||
@@ -55,19 +71,3 @@ impl From<ChainSpec> for String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for ChainSpec {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
if let ChainSpec::Custom(n) = self {
|
||||
write!(f, "Custom ({})", n)
|
||||
} else {
|
||||
write!(f, "{}", match *self {
|
||||
ChainSpec::Development => "Development",
|
||||
ChainSpec::LocalTestnet => "Local Testnet",
|
||||
ChainSpec::PoC1Testnet => "PoC-1 Testnet",
|
||||
ChainSpec::PoC2Testnet => "PoC-2 Testnet",
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,6 @@ args:
|
||||
long: dev
|
||||
help: Run in development mode; implies --chain=dev --validator --key Alice
|
||||
takes_value: false
|
||||
- build-genesis:
|
||||
long: build-genesis
|
||||
help: Build a genesis.json file, outputing to stdout
|
||||
takes_value: false
|
||||
- port:
|
||||
long: port
|
||||
value_name: PORT
|
||||
@@ -96,3 +92,15 @@ args:
|
||||
help: The URL of the telemetry server. Implies --telemetry
|
||||
takes_value: true
|
||||
subcommands:
|
||||
- build-spec:
|
||||
about: Build a spec.json file, outputing to stdout
|
||||
args:
|
||||
- raw:
|
||||
long: raw
|
||||
help: Force raw genesis storage output.
|
||||
takes_value: false
|
||||
- chain:
|
||||
long: chain
|
||||
value_name: CHAIN_SPEC
|
||||
help: Specify the chain specification (one of dev, local or poc-2)
|
||||
takes_value: true
|
||||
|
||||
@@ -58,26 +58,17 @@ extern crate clap;
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
pub mod error;
|
||||
mod informant;
|
||||
mod chain_spec;
|
||||
mod preset_config;
|
||||
|
||||
pub use chain_spec::ChainSpec;
|
||||
pub use preset_config::PresetConfig;
|
||||
|
||||
use std::io;
|
||||
use std::fs::File;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::HashMap;
|
||||
use substrate_primitives::hexdisplay::HexDisplay;
|
||||
use substrate_primitives::storage::{StorageData, StorageKey};
|
||||
use substrate_telemetry::{init_telemetry, TelemetryConfig};
|
||||
use runtime_primitives::StorageMap;
|
||||
use polkadot_primitives::Block;
|
||||
|
||||
use futures::sync::mpsc;
|
||||
@@ -106,10 +97,13 @@ impl substrate_rpc::system::SystemApi for SystemConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_storage_json(filename: &str) -> Option<StorageMap> {
|
||||
let file = File::open(PathBuf::from(filename)).ok()?;
|
||||
let h: HashMap<StorageKey, StorageData> = ::serde_json::from_reader(&file).ok()?;
|
||||
Some(h.into_iter().map(|(k, v)| (k.0, v.0)).collect())
|
||||
fn load_spec(matches: &clap::ArgMatches) -> Result<service::ChainSpec, String> {
|
||||
let chain_spec = matches.value_of("chain")
|
||||
.map(ChainSpec::from)
|
||||
.unwrap_or_else(|| if matches.is_present("dev") { ChainSpec::Development } else { ChainSpec::PoC2Testnet });
|
||||
let spec = chain_spec.load()?;
|
||||
info!("Chain specification: {}", spec.name());
|
||||
Ok(spec)
|
||||
}
|
||||
|
||||
/// Parse command line arguments and start the node.
|
||||
@@ -124,8 +118,6 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<std::ffi::OsString> + Clone,
|
||||
{
|
||||
let core = reactor::Core::new().expect("tokio::Core could not be created");
|
||||
|
||||
let yaml = load_yaml!("./cli.yml");
|
||||
let matches = match clap::App::from_yaml(yaml).version(&(crate_version!().to_owned() + "\n")[..]).get_matches_from_safe(args) {
|
||||
Ok(m) => m,
|
||||
@@ -146,20 +138,22 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
||||
info!(" version {}", crate_version!());
|
||||
info!(" by Parity Technologies, 2017, 2018");
|
||||
|
||||
let mut config = service::Configuration::default();
|
||||
if let Some(matches) = matches.subcommand_matches("build-spec") {
|
||||
let spec = load_spec(&matches)?;
|
||||
info!("Building chain spec");
|
||||
let json = spec.to_json(matches.is_present("raw"))?;
|
||||
print!("{}", json);
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let spec = load_spec(&matches)?;
|
||||
let mut config = service::Configuration::default_with_spec(spec);
|
||||
|
||||
if let Some(name) = matches.value_of("name") {
|
||||
config.name = name.into();
|
||||
info!("Node name: {}", config.name);
|
||||
}
|
||||
|
||||
let chain_spec = matches.value_of("chain")
|
||||
.map(ChainSpec::from)
|
||||
.unwrap_or_else(|| if matches.is_present("dev") { ChainSpec::Development } else { ChainSpec::PoC2Testnet });
|
||||
info!("Chain specification: {}", chain_spec);
|
||||
|
||||
config.chain_name = chain_spec.clone().into();
|
||||
|
||||
let base_path = matches.value_of("base-path")
|
||||
.map(|x| Path::new(x).to_owned())
|
||||
.unwrap_or_else(default_base_path);
|
||||
@@ -171,6 +165,7 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
||||
.into();
|
||||
|
||||
config.database_path = db_path(&base_path).to_string_lossy().into();
|
||||
|
||||
config.pruning = match matches.value_of("pruning") {
|
||||
Some("archive") => PruningMode::ArchiveAll,
|
||||
None => PruningMode::keep_blocks(256),
|
||||
@@ -178,25 +173,6 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
||||
.map_err(|_| error::ErrorKind::Input("Invalid pruning mode specified".to_owned()))?),
|
||||
};
|
||||
|
||||
let (mut genesis_storage, boot_nodes) = PresetConfig::from_spec(chain_spec)
|
||||
.map(PresetConfig::deconstruct)
|
||||
.unwrap_or_else(|f| (Box::new(move ||
|
||||
read_storage_json(&f)
|
||||
.map(|s| { info!("{} storage items read from {}", s.len(), f); s })
|
||||
.unwrap_or_else(|| panic!("Bad genesis state file: {}", f))
|
||||
), vec![]));
|
||||
|
||||
if matches.is_present("build-genesis") {
|
||||
info!("Building genesis");
|
||||
for (i, (k, v)) in genesis_storage().iter().enumerate() {
|
||||
print!("{}\n\"0x{}\": \"0x{}\"", if i > 0 {','} else {'{'}, HexDisplay::from(k), HexDisplay::from(v));
|
||||
}
|
||||
println!("\n}}");
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
config.genesis_storage = genesis_storage;
|
||||
|
||||
let role =
|
||||
if matches.is_present("collator") {
|
||||
info!("Starting collator");
|
||||
@@ -214,10 +190,9 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
||||
|
||||
config.roles = role;
|
||||
{
|
||||
config.network.boot_nodes = matches
|
||||
config.network.boot_nodes.extend(matches
|
||||
.values_of("bootnodes")
|
||||
.map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect());
|
||||
config.network.boot_nodes.extend(boot_nodes);
|
||||
.map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect::<Vec<_>>()));
|
||||
config.network.config_path = Some(network_path(&base_path).to_string_lossy().into());
|
||||
config.network.net_config_path = config.network.config_path.clone();
|
||||
|
||||
@@ -241,12 +216,12 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
||||
}
|
||||
|
||||
let sys_conf = SystemConfiguration {
|
||||
chain_name: config.chain_name.clone(),
|
||||
chain_name: config.chain_spec.name().to_owned(),
|
||||
};
|
||||
|
||||
let _guard = if matches.is_present("telemetry") || matches.value_of("telemetry-url").is_some() {
|
||||
let name = config.name.clone();
|
||||
let chain_name = config.chain_name.clone();
|
||||
let chain_name = config.chain_spec.name().to_owned();
|
||||
Some(init_telemetry(TelemetryConfig {
|
||||
url: matches.value_of("telemetry-url").unwrap_or(DEFAULT_TELEMETRY_URL).into(),
|
||||
on_connect: Box::new(move || {
|
||||
@@ -263,6 +238,7 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
|
||||
None
|
||||
};
|
||||
|
||||
let core = reactor::Core::new().expect("tokio::Core could not be created");
|
||||
match role == service::Role::LIGHT {
|
||||
true => run_until_exit(core, service::new_light(config)?, &matches, sys_conf),
|
||||
false => run_until_exit(core, service::new_full(config)?, &matches, sys_conf),
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot chain configurations.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use ed25519;
|
||||
use serde_json;
|
||||
use substrate_primitives::{AuthorityId, storage::{StorageKey, StorageData}};
|
||||
use runtime_primitives::{MakeStorage, BuildStorage, StorageMap};
|
||||
use polkadot_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig,
|
||||
SessionConfig, StakingConfig, TimestampConfig};
|
||||
use chain_spec::ChainSpec;
|
||||
|
||||
enum Config {
|
||||
Local(GenesisConfig),
|
||||
Raw(&'static [u8]),
|
||||
}
|
||||
|
||||
/// A configuration of a chain. Can be used to build a genesis block.
|
||||
pub struct PresetConfig {
|
||||
genesis_config: Config,
|
||||
pub(crate) boot_nodes: Vec<String>,
|
||||
}
|
||||
|
||||
impl BuildStorage for Config {
|
||||
fn build_storage(self) -> StorageMap {
|
||||
match self {
|
||||
Config::Local(gc) => gc.build_storage(),
|
||||
Config::Raw(json) => {
|
||||
let h: HashMap<StorageKey, StorageData> = serde_json::from_slice(json).expect("Data is from an internal source and is guaranteed to be of the correct format");
|
||||
h.into_iter().map(|(k, v)| (k.0, v.0)).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PresetConfig {
|
||||
/// Get a chain config from a spec, if it's predefined.
|
||||
pub fn from_spec(chain_spec: ChainSpec) -> Result<Self, String> {
|
||||
Ok(match chain_spec {
|
||||
ChainSpec::PoC1Testnet => Self::poc_1_testnet_config(),
|
||||
ChainSpec::Development => Self::development_config(),
|
||||
ChainSpec::LocalTestnet => Self::local_testnet_config(),
|
||||
ChainSpec::PoC2Testnet => Self::poc_2_testnet_config(),
|
||||
ChainSpec::Custom(f) => return Err(f),
|
||||
})
|
||||
}
|
||||
|
||||
/// Provide the boot nodes and a storage-builder function.
|
||||
// TODO: Change return type to FnOnce as soon as Box<FnOnce> is callable or BoxFn is stablised.
|
||||
pub fn deconstruct(self) -> (MakeStorage, Vec<String>) {
|
||||
let mut gc = Some(self.genesis_config);
|
||||
let f = move || gc.take().map(BuildStorage::build_storage).unwrap_or_default();
|
||||
(Box::new(f), self.boot_nodes)
|
||||
}
|
||||
|
||||
/// PoC-1 testnet config.
|
||||
fn poc_1_testnet_config() -> Self {
|
||||
let genesis_config = Config::Raw(include_bytes!("../poc-1.json"));
|
||||
let boot_nodes = vec![
|
||||
"enode://a93a29fa68d965452bf0ff8c1910f5992fe2273a72a1ee8d3a3482f68512a61974211ba32bb33f051ceb1530b8ba3527fc36224ba6b9910329025e6d9153cf50@104.211.54.233:30333".into(),
|
||||
"enode://051b18f63a316c4c5fef4631f8c550ae0adba179153588406fac3e5bbbbf534ebeda1bf475dceda27a531f6cdef3846ab6a010a269aa643a1fec7bff51af66bd@104.211.48.51:30333".into(),
|
||||
"enode://c831ec9011d2c02d2c4620fc88db6d897a40d2f88fd75f47b9e4cf3b243999acb6f01b7b7343474650b34eeb1363041a422a91f1fc3850e43482983ee15aa582@104.211.48.247:30333".into(),
|
||||
];
|
||||
PresetConfig { genesis_config, boot_nodes }
|
||||
}
|
||||
|
||||
/// PoC-2 testnet config.
|
||||
fn poc_2_testnet_config() -> Self {
|
||||
let initial_authorities = vec![
|
||||
hex!["82c39b31a2b79a90f8e66e7a77fdb85a4ed5517f2ae39f6a80565e8ecae85cf5"].into(),
|
||||
hex!["4de37a07567ebcbf8c64568428a835269a566723687058e017b6d69db00a77e7"].into(),
|
||||
hex!["063d7787ebca768b7445dfebe7d62cbb1625ff4dba288ea34488da266dd6dca5"].into(),
|
||||
hex!["8101764f45778d4980dadaceee6e8af2517d3ab91ac9bec9cd1714fa5994081c"].into(),
|
||||
];
|
||||
let endowed_accounts = vec![
|
||||
hex!["f295940fa750df68a686fcf4abd4111c8a9c5a5a5a83c4c8639c451a94a7adfd"].into(),
|
||||
];
|
||||
let genesis_config = Config::Local(GenesisConfig {
|
||||
consensus: Some(ConsensusConfig {
|
||||
code: include_bytes!("../../runtime/wasm/genesis.wasm").to_vec(), // TODO change
|
||||
authorities: initial_authorities.clone(),
|
||||
}),
|
||||
system: None,
|
||||
session: Some(SessionConfig {
|
||||
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
session_length: 60, // that's 5 minutes per session.
|
||||
broken_percent_late: 50,
|
||||
}),
|
||||
staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
transaction_base_fee: 100,
|
||||
transaction_byte_fee: 1,
|
||||
existential_deposit: 500,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
contract_fee: 0,
|
||||
reclaim_rebate: 0,
|
||||
early_era_slash: 10000,
|
||||
session_reward: 100,
|
||||
balances: endowed_accounts.iter().map(|&k|(k, 1u128 << 60)).collect(),
|
||||
validator_count: 12,
|
||||
sessions_per_era: 12, // 1 hour per era
|
||||
bonding_duration: 24, // 1 day per bond.
|
||||
}),
|
||||
democracy: Some(DemocracyConfig {
|
||||
launch_period: 12 * 60 * 24, // 1 day per public referendum
|
||||
voting_period: 12 * 60 * 24 * 3, // 3 days to discuss & vote on an active referendum
|
||||
minimum_deposit: 5000, // 12000 as the minimum deposit for a referendum
|
||||
}),
|
||||
council: Some(CouncilConfig {
|
||||
active_council: vec![],
|
||||
candidacy_bond: 5000, // 5000 to become a council candidate
|
||||
voter_bond: 1000, // 1000 down to vote for a candidate
|
||||
present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation.
|
||||
carry_count: 6, // carry over the 6 runners-up to the next council election
|
||||
presentation_duration: 12 * 60 * 24, // one day for presenting winners.
|
||||
approval_voting_period: 12 * 60 * 24 * 2, // two days period between possible council elections.
|
||||
term_duration: 12 * 60 * 24 * 24, // 24 day term duration for the council.
|
||||
desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit.
|
||||
inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped.
|
||||
|
||||
cooloff_period: 12 * 60 * 24 * 4, // 4 day cooling off period if council member vetoes a proposal.
|
||||
voting_period: 12 * 60 * 24, // 1 day voting period for council members.
|
||||
}),
|
||||
parachains: Some(Default::default()),
|
||||
timestamp: Some(TimestampConfig {
|
||||
period: 5, // 5 second block time.
|
||||
}),
|
||||
});
|
||||
let boot_nodes = vec![
|
||||
"enode://a93a29fa68d965452bf0ff8c1910f5992fe2273a72a1ee8d3a3482f68512a61974211ba32bb33f051ceb1530b8ba3527fc36224ba6b9910329025e6d9153cf50@104.211.54.233:30333".into(),
|
||||
"enode://051b18f63a316c4c5fef4631f8c550ae0adba179153588406fac3e5bbbbf534ebeda1bf475dceda27a531f6cdef3846ab6a010a269aa643a1fec7bff51af66bd@104.211.48.51:30333".into(),
|
||||
"enode://c831ec9011d2c02d2c4620fc88db6d897a40d2f88fd75f47b9e4cf3b243999acb6f01b7b7343474650b34eeb1363041a422a91f1fc3850e43482983ee15aa582@104.211.48.247:30333".into(),
|
||||
];
|
||||
PresetConfig { genesis_config, boot_nodes }
|
||||
}
|
||||
|
||||
fn testnet_config(initial_authorities: Vec<AuthorityId>) -> PresetConfig {
|
||||
let endowed_accounts = vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Bob ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Charlie ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Dave ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Eve ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Ferdie ").public().0.into(),
|
||||
];
|
||||
let genesis_config = Config::Local(GenesisConfig {
|
||||
consensus: Some(ConsensusConfig {
|
||||
code: include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm").to_vec(),
|
||||
authorities: initial_authorities.clone(),
|
||||
}),
|
||||
system: None,
|
||||
session: Some(SessionConfig {
|
||||
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
session_length: 10,
|
||||
broken_percent_late: 30,
|
||||
}),
|
||||
staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 500,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
contract_fee: 0,
|
||||
reclaim_rebate: 0,
|
||||
balances: endowed_accounts.iter().map(|&k|(k, (1u128 << 60))).collect(),
|
||||
validator_count: 2,
|
||||
sessions_per_era: 5,
|
||||
bonding_duration: 2,
|
||||
early_era_slash: 0,
|
||||
session_reward: 0,
|
||||
}),
|
||||
democracy: Some(DemocracyConfig {
|
||||
launch_period: 9,
|
||||
voting_period: 18,
|
||||
minimum_deposit: 10,
|
||||
}),
|
||||
council: Some(CouncilConfig {
|
||||
active_council: endowed_accounts.iter().filter(|a| initial_authorities.iter().find(|&b| &a.0 == b).is_none()).map(|a| (a.clone(), 1000000)).collect(),
|
||||
candidacy_bond: 10,
|
||||
voter_bond: 2,
|
||||
present_slash_per_voter: 1,
|
||||
carry_count: 4,
|
||||
presentation_duration: 10,
|
||||
approval_voting_period: 20,
|
||||
term_duration: 1000000,
|
||||
desired_seats: (endowed_accounts.len() - initial_authorities.len()) as u32,
|
||||
inactive_grace_period: 1,
|
||||
|
||||
cooloff_period: 75,
|
||||
voting_period: 20,
|
||||
}),
|
||||
parachains: Some(Default::default()),
|
||||
timestamp: Some(TimestampConfig {
|
||||
period: 5, // 5 second block time.
|
||||
}),
|
||||
});
|
||||
let boot_nodes = Vec::new();
|
||||
PresetConfig { genesis_config, boot_nodes }
|
||||
}
|
||||
|
||||
/// Development config (single validator Alice)
|
||||
fn development_config() -> Self {
|
||||
Self::testnet_config(vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().into(),
|
||||
])
|
||||
}
|
||||
|
||||
/// Local testnet config (multivalidator Alice + Bob)
|
||||
fn local_testnet_config() -> Self {
|
||||
Self::testnet_config(vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().into(),
|
||||
ed25519::Pair::from_seed(b"Bob ").public().into(),
|
||||
])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user