lib.rs 12.4 KB
Newer Older
Gav's avatar
Gav committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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 CLI library.

#![warn(missing_docs)]

21
extern crate app_dirs;
Gav's avatar
Gav committed
22
extern crate env_logger;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
23
24
25
26
extern crate atty;
extern crate ansi_term;
extern crate regex;
extern crate time;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
27
28
29
extern crate futures;
extern crate tokio_core;
extern crate ctrlc;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
30
extern crate fdlimit;
Gav's avatar
Gav committed
31
32
extern crate ed25519;
extern crate triehash;
33
extern crate parking_lot;
Gav Wood's avatar
Gav Wood committed
34
35
extern crate serde;
extern crate serde_json;
36

Gav Wood's avatar
Gav Wood committed
37
extern crate substrate_primitives;
Gav's avatar
Gav committed
38
39
extern crate substrate_state_machine as state_machine;
extern crate substrate_client as client;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
40
extern crate substrate_network as network;
41
extern crate substrate_rpc;
Gav's avatar
Gav committed
42
extern crate substrate_rpc_servers as rpc;
Gav Wood's avatar
Gav Wood committed
43
extern crate substrate_runtime_primitives as runtime_primitives;
Gav's avatar
Gav committed
44
extern crate polkadot_primitives;
Gav Wood's avatar
Gav Wood committed
45
extern crate polkadot_runtime;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
46
extern crate polkadot_service as service;
Gav Wood's avatar
Gav Wood committed
47
48
49
50
#[macro_use]
extern crate slog;	// needed until we can reexport `slog_info` from `substrate_telemetry`
#[macro_use]
extern crate substrate_telemetry;
51
extern crate polkadot_transaction_pool as txpool;
Gav's avatar
Gav committed
52

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
53
54
#[macro_use]
extern crate lazy_static;
Gav's avatar
Gav committed
55
56
57
58
59
60
#[macro_use]
extern crate clap;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
Gav Wood's avatar
Gav Wood committed
61
62
#[macro_use]
extern crate hex_literal;
Gav's avatar
Gav committed
63
64

pub mod error;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
65
mod informant;
Gav Wood's avatar
Gav Wood committed
66
67
68
69
70
mod chain_spec;
mod preset_config;

pub use chain_spec::ChainSpec;
pub use preset_config::PresetConfig;
Gav's avatar
Gav committed
71

72
use std::io;
Gav Wood's avatar
Gav Wood committed
73
use std::fs::File;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
74
use std::net::SocketAddr;
75
use std::path::{Path, PathBuf};
Gav Wood's avatar
Gav Wood committed
76
77
78
use std::collections::HashMap;
use substrate_primitives::hexdisplay::HexDisplay;
use substrate_primitives::storage::{StorageData, StorageKey};
Gav Wood's avatar
Gav Wood committed
79
use substrate_telemetry::{init_telemetry, TelemetryConfig};
Gav Wood's avatar
Gav Wood committed
80
81
use runtime_primitives::StorageMap;
use polkadot_primitives::Block;
82

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
83
84
85
use futures::sync::mpsc;
use futures::{Sink, Future, Stream};
use tokio_core::reactor;
Gav Wood's avatar
Gav Wood committed
86
87

const DEFAULT_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io:443";
88

Gav Wood's avatar
Gav Wood committed
89
90
91
92
#[derive(Clone)]
struct SystemConfiguration {
	chain_name: String,
}
Gav Wood's avatar
Gav Wood committed
93

Gav Wood's avatar
Gav Wood committed
94
impl substrate_rpc::system::SystemApi for SystemConfiguration {
Gav Wood's avatar
Gav Wood committed
95
96
97
98
99
100
101
102
103
	fn system_name(&self) -> substrate_rpc::system::error::Result<String> {
		Ok("parity-polkadot".into())
	}

	fn system_version(&self) -> substrate_rpc::system::error::Result<String> {
		Ok(crate_version!().into())
	}

	fn system_chain(&self) -> substrate_rpc::system::error::Result<String> {
Gav Wood's avatar
Gav Wood committed
104
		Ok(self.chain_name.clone())
Gav Wood's avatar
Gav Wood committed
105
106
107
	}
}

Gav Wood's avatar
Gav Wood committed
108
109
110
111
112
113
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())
}

Gav's avatar
Gav committed
114
115
116
117
118
119
120
121
/// Parse command line arguments and start the node.
///
/// IANA unassigned port ranges that we could use:
/// 6717-6766		Unassigned
/// 8504-8553		Unassigned
/// 9556-9591		Unassigned
/// 9803-9874		Unassigned
/// 9926-9949		Unassigned
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
122
pub fn run<I, T>(args: I) -> error::Result<()> where
Gav's avatar
Gav committed
123
124
125
	I: IntoIterator<Item = T>,
	T: Into<std::ffi::OsString> + Clone,
{
126
	let core = reactor::Core::new().expect("tokio::Core could not be created");
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
127

Gav's avatar
Gav committed
128
	let yaml = load_yaml!("./cli.yml");
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
129
130
131
132
133
134
135
136
137
	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()),
	};
Gav's avatar
Gav committed
138
139
140
141

	// TODO [ToDr] Split parameters parsing from actual execution.
	let log_pattern = matches.value_of("log").unwrap_or("");
	init_logger(log_pattern);
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
142
	fdlimit::raise_fd_limit();
Gav's avatar
Gav committed
143

Gav Wood's avatar
Gav Wood committed
144
145
146
147
	info!("Parity ·:· Polkadot");
	info!("  version {}", crate_version!());
	info!("  by Parity Technologies, 2017, 2018");

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
148
	let mut config = service::Configuration::default();
149

Gav Wood's avatar
Gav Wood committed
150
151
152
153
154
	if let Some(name) = matches.value_of("name") {
		config.name = name.into();
		info!("Node name: {}", config.name);
	}

Gav Wood's avatar
Gav Wood committed
155
156
157
158
159
160
161
	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();

Gav Wood's avatar
Gav Wood committed
162
163
	let _guard = if matches.is_present("telemetry") || matches.value_of("telemetry-url").is_some() {
		let name = config.name.clone();
Gav Wood's avatar
Gav Wood committed
164
		let chain_name = config.chain_name.clone();
Gav Wood's avatar
Gav Wood committed
165
166
167
168
169
170
171
172
		Some(init_telemetry(TelemetryConfig {
			url: matches.value_of("telemetry-url").unwrap_or(DEFAULT_TELEMETRY_URL).into(),
			on_connect: Box::new(move || {
				telemetry!("system.connected";
					"name" => name.clone(),
					"implementation" => "parity-polkadot",
					"version" => crate_version!(),
					"config" => "",
Gav Wood's avatar
Gav Wood committed
173
					"chain" => chain_name.clone(),
Gav Wood's avatar
Gav Wood committed
174
175
176
177
178
179
180
				);
			}),
		}))
	} else {
		None
	};

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
181
182
183
184
	let base_path = matches.value_of("base-path")
		.map(|x| Path::new(x).to_owned())
		.unwrap_or_else(default_base_path);

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
185
	config.keystore_path = matches.value_of("keystore")
186
		.map(|x| Path::new(x).to_owned())
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
187
		.unwrap_or_else(|| keystore_path(&base_path))
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
188
189
		.to_string_lossy()
		.into();
Gav's avatar
Gav committed
190

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
191
192
	config.database_path = db_path(&base_path).to_string_lossy().into();

Gav Wood's avatar
Gav Wood committed
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
	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(())
Gav's avatar
Gav committed
208
209
	}

Gav Wood's avatar
Gav Wood committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
	config.genesis_storage = genesis_storage;

	let role =
		if matches.is_present("collator") {
			info!("Starting collator");
			service::Role::COLLATOR
		} else if matches.is_present("light") {
			info!("Starting (light)");
			service::Role::LIGHT
		} else if matches.is_present("validator") || matches.is_present("dev") {
			info!("Starting validator");
			service::Role::VALIDATOR
		} else {
			info!("Starting (heavy)");
			service::Role::FULL
		};
Gav Wood's avatar
Gav Wood committed
226

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
227
	config.roles = role;
228
229
230
231
	{
		config.network.boot_nodes = matches
			.values_of("bootnodes")
			.map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect());
Gav Wood's avatar
Gav Wood committed
232
		config.network.boot_nodes.extend(boot_nodes);
233
234
235
236
237
238
239
240
241
242
		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.network.public_address = None;
		config.network.client_version = format!("parity-polkadot/{}", crate_version!());
243
244
245
246
247
		config.network.use_secret = match matches.value_of("node-key").map(|s| s.parse()) {
			Some(Ok(secret)) => Some(secret),
			Some(Err(err)) => return Err(format!("Error parsing node key: {}", err).into()),
			None => None,
		};
248
	}
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
249
250

	config.keys = matches.values_of("key").unwrap_or_default().map(str::to_owned).collect();
Gav Wood's avatar
Gav Wood committed
251
252
253
254
255
256
257
	if matches.is_present("dev") {
		config.keys.push("Alice".into());
	}

	let sys_conf = SystemConfiguration {
		chain_name: config.chain_name.clone(),
	};
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
258

259
	match role == service::Role::LIGHT {
Gav Wood's avatar
Gav Wood committed
260
261
		true => run_until_exit(core, service::new_light(config)?, &matches, sys_conf),
		false => run_until_exit(core, service::new_full(config)?, &matches, sys_conf),
262
263
264
	}
}

Gav Wood's avatar
Gav Wood committed
265
fn run_until_exit<C>(mut core: reactor::Core, service: service::Service<C>, matches: &clap::ArgMatches, sys_conf: SystemConfiguration) -> error::Result<()>
266
	where
267
268
		C: service::Components,
		client::error::Error: From<<<<C as service::Components>::Backend as client::backend::Backend<Block>>::State as state_machine::Backend>::Error>,
269
270
271
272
273
274
275
276
277
278
{
	let exit = {
		// can't use signal directly here because CtrlC takes only `Fn`.
		let (exit_send, exit) = mpsc::channel(1);
		ctrlc::CtrlC::set_handler(move || {
			exit_send.clone().send(()).wait().expect("Error sending exit notification");
		});

		exit
	};
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
279

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
280
281
	informant::start(&service, core.handle());

282
	let _rpc_servers = {
283
284
		let http_address = parse_address("127.0.0.1:9933", "rpc-port", matches)?;
		let ws_address = parse_address("127.0.0.1:9944", "ws-port", matches)?;
285
286
287

		let handler = || {
			let chain = rpc::apis::chain::Chain::new(service.client(), core.remote());
Gav Wood's avatar
Gav Wood committed
288
			rpc::rpc_handler::<Block, _, _, _, _>(
Tomasz Drwięga's avatar
Tomasz Drwięga committed
289
290
291
				service.client(),
				chain,
				service.transaction_pool(),
Gav Wood's avatar
Gav Wood committed
292
				sys_conf.clone(),
Tomasz Drwięga's avatar
Tomasz Drwięga committed
293
			)
294
295
296
297
298
299
300
		};
		(
			start_server(http_address, |address| rpc::start_http(address, handler())),
			start_server(ws_address, |address| rpc::start_ws(address, handler())),
		)
	};

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
301
	core.run(exit.into_future()).expect("Error running informant event loop");
Gav's avatar
Gav committed
302
303
304
	Ok(())
}

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
fn start_server<T, F>(mut address: SocketAddr, start: F) -> Result<T, io::Error> where
	F: Fn(&SocketAddr) -> Result<T, io::Error>,
{
	start(&address)
		.or_else(|e| match e.kind() {
			io::ErrorKind::AddrInUse |
			io::ErrorKind::PermissionDenied => {
				warn!("Unable to bind server to {}. Trying random port.", address);
				address.set_port(0);
				start(&address)
			},
			_ => Err(e),
		})
}

fn parse_address(default: &str, port_param: &str, matches: &clap::ArgMatches) -> Result<SocketAddr, String> {
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
321
	let mut address: SocketAddr = default.parse().ok().ok_or(format!("Invalid address specified for --{}.", port_param))?;
322
323
324
325
326
327
328
329
	if let Some(port) = matches.value_of(port_param) {
		let port: u16 = port.parse().ok().ok_or(format!("Invalid port for --{} specified.", port_param))?;
		address.set_port(port);
	}

	Ok(address)
}

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
330
331
332
333
fn keystore_path(base_path: &Path) -> PathBuf {
	let mut path = base_path.to_owned();
	path.push("keystore");
	path
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
334
335
336
337
338
339
}

fn db_path(base_path: &Path) -> PathBuf {
	let mut path = base_path.to_owned();
	path.push("db");
	path
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
340
341
342
343
344
345
346
347
348
}

fn network_path(base_path: &Path) -> PathBuf {
	let mut path = base_path.to_owned();
	path.push("network");
	path
}

fn default_base_path() -> PathBuf {
349
350
351
352
353
354
355
	use app_dirs::{AppInfo, AppDataType};

	let app_info = AppInfo {
		name: "Polkadot",
		author: "Parity Technologies",
	};

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
356
	app_dirs::get_app_root(
357
358
359
360
		AppDataType::UserData,
		&app_info,
	).expect("app directories exist on all supported platforms; qed")
}
361

Gav's avatar
Gav committed
362
fn init_logger(pattern: &str) {
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
363
364
	use ansi_term::Colour;

Gav's avatar
Gav committed
365
366
	let mut builder = env_logger::LogBuilder::new();
	// Disable info logging by default for some modules:
367
	builder.filter(Some("ws"), log::LogLevelFilter::Warn);
Gav's avatar
Gav committed
368
369
370
371
372
373
374
375
376
	builder.filter(Some("hyper"), log::LogLevelFilter::Warn);
	// Enable info for others.
	builder.filter(None, log::LogLevelFilter::Info);

	if let Ok(lvl) = std::env::var("RUST_LOG") {
		builder.parse(&lvl);
	}

	builder.parse(pattern);
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
	let isatty = atty::is(atty::Stream::Stderr);
	let enable_color = isatty;

	let format = move |record: &log::LogRecord| {
		let timestamp = time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).expect("Error formatting log timestamp");

		let mut output = if log::max_log_level() <= log::LogLevelFilter::Info {
			format!("{} {}", Colour::Black.bold().paint(timestamp), record.args())
		} else {
			let name = ::std::thread::current().name().map_or_else(Default::default, |x| format!("{}", Colour::Blue.bold().paint(x)));
			format!("{} {} {} {}  {}", Colour::Black.bold().paint(timestamp), name, record.level(), record.target(), record.args())
		};

		if !enable_color {
			output = kill_color(output.as_ref());
		}
Gav's avatar
Gav committed
393

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
394
395
396
397
398
399
400
		if !isatty && record.level() <= log::LogLevel::Info && atty::is(atty::Stream::Stdout) {
			// duplicate INFO/WARN output to console
			println!("{}", output);
		}
		output
	};
	builder.format(format);
Gav's avatar
Gav committed
401
402
403

	builder.init().expect("Logger initialized only once.");
}
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
404
405
406
407
408
409
410

fn kill_color(s: &str) -> String {
	lazy_static! {
		static ref RE: regex::Regex = regex::Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex");
	}
	RE.replace_all(s, "").to_string()
}