BFT gossip (#106)

* CLI options and keystore integration

* Replace multiqueue with future::mpsc

* BFT gossip

* Revert to app_dirs

* generate_from_seed commented
This commit is contained in:
Arkadiy Paronyan
2018-04-03 18:06:34 +02:00
committed by Robert Habermeier
parent 67ce6f9e36
commit 5983a6654e
8 changed files with 146 additions and 38 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ log = "0.3"
hex-literal = "0.1" hex-literal = "0.1"
triehash = "0.1" triehash = "0.1"
ed25519 = { path = "../../substrate/ed25519" } ed25519 = { path = "../../substrate/ed25519" }
app_dirs = "1.1" app_dirs = "1.2"
substrate-client = { path = "../../substrate/client" } substrate-client = { path = "../../substrate/client" }
substrate-codec = { path = "../../substrate/codec" } substrate-codec = { path = "../../substrate/codec" }
substrate-runtime-io = { path = "../../substrate/runtime-io" } substrate-runtime-io = { path = "../../substrate/runtime-io" }
+39 -6
View File
@@ -4,15 +4,48 @@ about: Polkadot Node Rust Implementation
args: args:
- log: - log:
short: l short: l
long: log
value_name: LOG_PATTERN value_name: LOG_PATTERN
help: Sets a custom logging filter help: Sets a custom logging filter
takes_value: true takes_value: true
- keystore-path: - base-path:
value_name: KEYSTORE_PATH long: base-path
help: specify custom keystore path short: d
value_name: PATH
help: Specify custom base path
takes_value: true
- keystore-path:
long: keystore-path
value_name: PATH
help: Specify custom keystore path
takes_value: true
- key:
long: key
value_name: STRING
help: Specify additional key seed
takes_value: true takes_value: true
subcommands:
- collator: - collator:
about: Run collator node long: collator
help: Enable collator mode
takes_value: false
- validator: - validator:
about: Run validator node long: validator
help: Enable validator mode
takes_value: false
- port:
long: port
value_name: PORT
help: Specify p2p protocol TCP port
takes_value: true
- rpc-port:
long: rpc-port
value_name: PORT
help: Specify RPC server TCP port
takes_value: true
- bootnodes:
long: bootnodes
value_name: URL
help: Specify a list of bootnodes
takes_value: true
multiple: true
subcommands:
+49 -12
View File
@@ -42,6 +42,7 @@ extern crate log;
pub mod error; pub mod error;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::net::SocketAddr;
/// Parse command line arguments and start the node. /// Parse command line arguments and start the node.
/// ///
@@ -56,7 +57,15 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
T: Into<std::ffi::OsString> + Clone, T: Into<std::ffi::OsString> + Clone,
{ {
let yaml = load_yaml!("./cli.yml"); let yaml = load_yaml!("./cli.yml");
let matches = clap::App::from_yaml(yaml).version(crate_version!()).get_matches_from_safe(args)?; let matches = match clap::App::from_yaml(yaml).version(crate_version!()).get_matches_from_safe(args) {
Ok(m) => m,
Err(ref e) if e.kind == clap::ErrorKind::VersionDisplayed => return Ok(()),
Err(ref e) if e.kind == clap::ErrorKind::HelpDisplayed || e.kind == clap::ErrorKind::VersionDisplayed => {
let _ = clap::App::from_yaml(yaml).print_long_help();
return Ok(());
}
Err(e) => return Err(e.into()),
};
// TODO [ToDr] Split parameters parsing from actual execution. // TODO [ToDr] Split parameters parsing from actual execution.
let log_pattern = matches.value_of("log").unwrap_or(""); let log_pattern = matches.value_of("log").unwrap_or("");
@@ -64,38 +73,68 @@ pub fn run<I, T>(args: I) -> error::Result<()> where
let mut config = service::Configuration::default(); let mut config = service::Configuration::default();
let base_path = matches.value_of("base-path")
.map(|x| Path::new(x).to_owned())
.unwrap_or_else(default_base_path);
config.keystore_path = matches.value_of("keystore") config.keystore_path = matches.value_of("keystore")
.map(|x| Path::new(x).to_owned()) .map(|x| Path::new(x).to_owned())
.unwrap_or_else(default_keystore_path) .unwrap_or_else(|| keystore_path(&base_path))
.to_string_lossy() .to_string_lossy()
.into(); .into();
let mut role = service::Role::FULL; let mut role = service::Role::FULL;
if let Some(_) = matches.subcommand_matches("collator") { if matches.is_present("collator") {
info!("Starting collator."); info!("Starting collator.");
role = service::Role::COLLATOR; role = service::Role::COLLATOR;
} }
else if let Some(_) = matches.subcommand_matches("validator") { else if matches.is_present("validator") {
info!("Starting validator."); info!("Starting validator.");
role = service::Role::VALIDATOR; role = service::Role::VALIDATOR;
} }
config.roles = role; config.roles = role;
config.network.boot_nodes = matches
.values_of("bootnodes")
.map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect());
config.network.config_path = Some(network_path(&base_path).to_string_lossy().into());
config.network.net_config_path = config.network.config_path.clone();
let port = match matches.value_of("port") {
Some(port) => port.parse().expect("Invalid p2p port value specified."),
None => 30333,
};
config.network.listen_address = Some(SocketAddr::new("0.0.0.0".parse().unwrap(), port));
config.keys = matches.values_of("key").unwrap_or_default().map(str::to_owned).collect();
let service = service::Service::new(config)?; let service = service::Service::new(config)?;
let address = "127.0.0.1:9933".parse().unwrap(); let mut address: SocketAddr = "127.0.0.1:9933".parse().unwrap();
if let Some(port) = matches.value_of("rpc-port") {
let rpc_port: u16 = port.parse().expect("Invalid RPC port value specified.");
address.set_port(rpc_port);
}
let handler = rpc::rpc_handler(service.client()); let handler = rpc::rpc_handler(service.client());
let server = rpc::start_http(&address, handler)?; let server = rpc::start_http(&address, handler)?;
server.wait(); server.wait();
println!("No command given.\n");
let _ = clap::App::from_yaml(yaml).print_long_help();
Ok(()) Ok(())
} }
fn default_keystore_path() -> PathBuf { fn keystore_path(base_path: &Path) -> PathBuf {
let mut path = base_path.to_owned();
path.push("keystore");
path
}
fn network_path(base_path: &Path) -> PathBuf {
let mut path = base_path.to_owned();
path.push("network");
path
}
fn default_base_path() -> PathBuf {
use app_dirs::{AppInfo, AppDataType}; use app_dirs::{AppInfo, AppDataType};
let app_info = AppInfo { let app_info = AppInfo {
@@ -103,13 +142,11 @@ fn default_keystore_path() -> PathBuf {
author: "Parity Technologies", author: "Parity Technologies",
}; };
app_dirs::get_app_dir( app_dirs::get_app_root(
AppDataType::UserData, AppDataType::UserData,
&app_info, &app_info,
"keystore",
).expect("app directories exist on all supported platforms; qed") ).expect("app directories exist on all supported platforms; qed")
} }
fn init_logger(pattern: &str) { fn init_logger(pattern: &str) {
let mut builder = env_logger::LogBuilder::new(); let mut builder = env_logger::LogBuilder::new();
// Disable info logging by default for some modules: // Disable info logging by default for some modules:
+7 -4
View File
@@ -26,7 +26,6 @@ use parking_lot::Mutex;
use substrate_network as net; use substrate_network as net;
use tokio_core::reactor; use tokio_core::reactor;
use client::BlockchainEvents; use client::BlockchainEvents;
use substrate_keyring::Keyring;
use primitives::{Hash, AuthorityId}; use primitives::{Hash, AuthorityId};
use primitives::block::{Id as BlockId, HeaderHash, Header}; use primitives::block::{Id as BlockId, HeaderHash, Header};
use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt}; use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt};
@@ -136,14 +135,18 @@ struct Network(Arc<net::ConsensusService>);
impl Service { impl Service {
/// Create and start a new instance. /// Create and start a new instance.
pub fn new<C>(client: Arc<C>, network: Arc<net::ConsensusService>, transaction_pool: Arc<Mutex<TransactionPool>>, best_header: &Header) -> Service pub fn new<C>(
client: Arc<C>,
network: Arc<net::ConsensusService>,
transaction_pool: Arc<Mutex<TransactionPool>>,
key: ed25519::Pair,
best_header: &Header) -> Service
where C: BlockchainEvents + bft::BlockImport + bft::Authorities + PolkadotApi + Send + Sync + 'static where C: BlockchainEvents + bft::BlockImport + bft::Authorities + PolkadotApi + Send + Sync + 'static
{ {
let best_header = best_header.clone(); let best_header = best_header.clone();
let thread = thread::spawn(move || { let thread = thread::spawn(move || {
let mut core = reactor::Core::new().expect("tokio::Core could not be created"); let mut core = reactor::Core::new().expect("tokio::Core could not be created");
let key = Arc::new(Keyring::One.into()); let key = Arc::new(key);
let factory = ProposerFactory { let factory = ProposerFactory {
client: client.clone(), client: client.clone(),
transaction_pool: transaction_pool.clone(), transaction_pool: transaction_pool.clone(),
+22 -2
View File
@@ -33,6 +33,7 @@ extern crate error_chain;
#[cfg(test)] #[cfg(test)]
extern crate tempdir; extern crate tempdir;
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{self, Write}; use std::io::{self, Write};
@@ -120,16 +121,19 @@ impl EncryptedKey {
} }
} }
type Seed = [u8; 32];
/// Key store. /// Key store.
pub struct Store { pub struct Store {
path: PathBuf, path: PathBuf,
additional: HashMap<Public, Seed>,
} }
impl Store { impl Store {
/// Create a new store at the given path. /// Create a new store at the given path.
pub fn open(path: PathBuf) -> Result<Self> { pub fn open(path: PathBuf) -> Result<Self> {
fs::create_dir_all(&path)?; fs::create_dir_all(&path)?;
Ok(Store { path }) Ok(Store { path, additional: HashMap::new() })
} }
/// Generate a new key, placing it into the store. /// Generate a new key, placing it into the store.
@@ -145,8 +149,24 @@ impl Store {
Ok(pair) Ok(pair)
} }
/// Create a new key from seed. Do not place it into the store.
/// Only the first 32 bytes of the sead are used. This is meant to be used for testing only.
// TODO: Remove this
pub fn generate_from_seed(&mut self, seed: &str) -> Result<Pair> {
let mut s: [u8; 32] = [' ' as u8; 32];
let len = ::std::cmp::min(32, seed.len());
&mut s[..len].copy_from_slice(&seed.as_bytes()[..len]);
let pair = Pair::from_seed(&s);
self.additional.insert(pair.public(), s);
Ok(pair)
}
/// Load a key file with given public key. /// Load a key file with given public key.
pub fn load(&self, public: &Public, password: &str) -> Result<Pair> { pub fn load(&self, public: &Public, password: &str) -> Result<Pair> {
if let Some(ref seed) = self.additional.get(public) {
let pair = Pair::from_seed(seed);
return Ok(pair);
}
let path = self.key_file_path(public); let path = self.key_file_path(public);
let file = File::open(path)?; let file = File::open(path)?;
@@ -158,7 +178,7 @@ impl Store {
/// Get public keys of all stored keys. /// Get public keys of all stored keys.
pub fn contents(&self) -> Result<Vec<Public>> { pub fn contents(&self) -> Result<Vec<Public>> {
let mut public_keys = Vec::new(); let mut public_keys: Vec<Public> = self.additional.keys().cloned().collect();
for entry in fs::read_dir(&self.path)? { for entry in fs::read_dir(&self.path)? {
let entry = entry?; let entry = entry?;
let path = entry.path(); let path = entry.path();
+3 -1
View File
@@ -30,7 +30,8 @@ pub struct Configuration {
pub network: NetworkConfiguration, pub network: NetworkConfiguration,
/// Path to key files. /// Path to key files.
pub keystore_path: String, pub keystore_path: String,
// TODO: add more network, client, tx pool configuration options /// Additional key seeds.
pub keys: Vec<String>,
} }
impl Default for Configuration { impl Default for Configuration {
@@ -40,6 +41,7 @@ impl Default for Configuration {
transaction_pool: Default::default(), transaction_pool: Default::default(),
network: Default::default(), network: Default::default(),
keystore_path: Default::default(), keystore_path: Default::default(),
keys: Default::default(),
} }
} }
} }
+2 -5
View File
@@ -18,18 +18,15 @@
use client; use client;
use network; use network;
use keystore;
error_chain! { error_chain! {
links { links {
Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"]; Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"];
Network(network::error::Error, network::error::ErrorKind) #[doc="Network error"]; Network(network::error::Error, network::error::ErrorKind) #[doc="Network error"];
Keystore(keystore::Error, keystore::ErrorKind) #[doc="Keystore error"];
} }
errors { errors {
/// Key store errors
Keystore(e: ::keystore::Error) {
description("Keystore error"),
display("Keystore error: {:?}", e),
}
} }
} }
+23 -7
View File
@@ -56,7 +56,6 @@ use transaction_pool::TransactionPool;
use substrate_keyring::Keyring; use substrate_keyring::Keyring;
use substrate_executor::NativeExecutor; use substrate_executor::NativeExecutor;
use polkadot_executor::Executor as LocalDispatch; use polkadot_executor::Executor as LocalDispatch;
use polkadot_primitives::AccountId;
use keystore::Store as Keystore; use keystore::Store as Keystore;
use polkadot_api::PolkadotApi; use polkadot_api::PolkadotApi;
use polkadot_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig}; use polkadot_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig};
@@ -126,12 +125,27 @@ impl Service {
// Create client // Create client
let executor = polkadot_executor::Executor::new(); let executor = polkadot_executor::Executor::new();
let mut storage = Default::default(); let mut storage = Default::default();
let key: AccountId = Keyring::One.into();
let mut keystore = Keystore::open(config.keystore_path.into())?;
for seed in &config.keys {
keystore.generate_from_seed(seed)?;
}
if keystore.contents()?.is_empty() {
let key = keystore.generate("")?;
info!("Generated a new keypair: {:?}", key.public());
}
let genesis_config = GenesisConfig { let genesis_config = GenesisConfig {
validators: vec![key.clone()], validators: vec![Keyring::Alice.into(), Keyring::Bob.into(), Keyring::Charlie.into()],
authorities: vec![key.clone()], authorities: vec![Keyring::Alice.into(), Keyring::Bob.into(), Keyring::Charlie.into()],
balances: vec![(Keyring::One.into(), 1u64 << 63), (Keyring::Two.into(), 1u64 << 63)].into_iter().collect(), balances: vec![
(Keyring::One.into(), 1u64 << 63),
(Keyring::Two.into(), 1u64 << 63),
(Keyring::Alice.into(), 1u64 << 63),
(Keyring::Bob.into(), 1u64 << 63),
(Keyring::Charlie.into(), 1u64 << 63),
].into_iter().collect(),
block_time: 5, // 5 second block time. block_time: 5, // 5 second block time.
session_length: 720, // that's 1 hour per session. session_length: 720, // that's 1 hour per session.
sessions_per_era: 24, // 24 hours per era. sessions_per_era: 24, // 24 hours per era.
@@ -145,7 +159,6 @@ impl Service {
(primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) (primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect())
}; };
let _keystore = Keystore::open(config.keystore_path.into()).map_err(::error::ErrorKind::Keystore)?;
let client = Arc::new(client::new_in_mem(executor, prepare_genesis)?); let client = Arc::new(client::new_in_mem(executor, prepare_genesis)?);
let best_header = client.header(&BlockId::Hash(client.info()?.chain.best_hash))?.expect("Best header always exists; qed"); let best_header = client.header(&BlockId::Hash(client.info()?.chain.best_hash))?.expect("Best header always exists; qed");
info!("Starting Polkadot. Best block is #{}", best_header.number); info!("Starting Polkadot. Best block is #{}", best_header.number);
@@ -166,7 +179,10 @@ impl Service {
// Spin consensus service if configured // Spin consensus service if configured
let consensus_service = if config.roles & Role::VALIDATOR == Role::VALIDATOR { let consensus_service = if config.roles & Role::VALIDATOR == Role::VALIDATOR {
Some(consensus::Service::new(client.clone(), network.clone(), transaction_pool, &best_header)) // Load the first available key. Code above makes sure it exisis.
let key = keystore.load(&keystore.contents()?[0], "")?;
info!("Using authority key {:?}", key.public());
Some(consensus::Service::new(client.clone(), network.clone(), transaction_pool, key, &best_header))
} else { } else {
None None
}; };