lib.rs 12 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's avatar
Gav committed
37
extern crate substrate_client as client;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
38
extern crate substrate_network as network;
39
extern crate substrate_primitives;
40
extern crate substrate_rpc;
Gav's avatar
Gav committed
41
extern crate substrate_rpc_servers as rpc;
Gav Wood's avatar
Gav Wood committed
42
extern crate substrate_runtime_primitives as runtime_primitives;
43
extern crate substrate_state_machine as state_machine;
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
61
62
#[macro_use]
extern crate clap;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;

pub mod error;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
63
mod informant;
Gav Wood's avatar
Gav Wood committed
64
65
66
mod chain_spec;

pub use chain_spec::ChainSpec;
Gav's avatar
Gav committed
67

68
use std::io;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
69
use std::net::SocketAddr;
70
use std::path::{Path, PathBuf};
Gav Wood's avatar
Gav Wood committed
71
use substrate_telemetry::{init_telemetry, TelemetryConfig};
Gav Wood's avatar
Gav Wood committed
72
use polkadot_primitives::Block;
73

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
74
75
76
use futures::sync::mpsc;
use futures::{Sink, Future, Stream};
use tokio_core::reactor;
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
77
use service::PruningMode;
Gav Wood's avatar
Gav Wood committed
78

Gav Wood's avatar
Gav Wood committed
79
const DEFAULT_TELEMETRY_URL: &str = "ws://telemetry.polkadot.io:1024";
80

Gav Wood's avatar
Gav Wood committed
81
82
83
84
#[derive(Clone)]
struct SystemConfiguration {
	chain_name: String,
}
Gav Wood's avatar
Gav Wood committed
85

Gav Wood's avatar
Gav Wood committed
86
impl substrate_rpc::system::SystemApi for SystemConfiguration {
Gav Wood's avatar
Gav Wood committed
87
88
89
90
91
92
93
94
95
	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
96
		Ok(self.chain_name.clone())
Gav Wood's avatar
Gav Wood committed
97
98
99
	}
}

100
101
102
103
104
105
106
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)
Gav Wood's avatar
Gav Wood committed
107
108
}

Gav's avatar
Gav committed
109
110
111
112
113
114
115
116
/// 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
117
pub fn run<I, T>(args: I) -> error::Result<()> where
Gav's avatar
Gav committed
118
119
120
121
	I: IntoIterator<Item = T>,
	T: Into<std::ffi::OsString> + Clone,
{
	let yaml = load_yaml!("./cli.yml");
Gav Wood's avatar
Gav Wood committed
122
	let matches = match clap::App::from_yaml(yaml).version(&(crate_version!().to_owned() + "\n")[..]).get_matches_from_safe(args) {
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
123
124
		Ok(m) => m,
		Err(ref e) if e.kind == clap::ErrorKind::VersionDisplayed => return Ok(()),
Gav Wood's avatar
Gav Wood committed
125
		Err(ref e) if e.kind == clap::ErrorKind::HelpDisplayed => {
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
126
			let _ = clap::App::from_yaml(yaml).print_long_help();
Gav Wood's avatar
Gav Wood committed
127
			return Ok(())
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
128
129
130
		}
		Err(e) => return Err(e.into()),
	};
Gav's avatar
Gav committed
131
132
133
134

	// 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
135
	fdlimit::raise_fd_limit();
Gav's avatar
Gav committed
136

Gav Wood's avatar
Gav Wood committed
137
138
139
140
	info!("Parity ·:· Polkadot");
	info!("  version {}", crate_version!());
	info!("  by Parity Technologies, 2017, 2018");

141
142
143
144
145
146
147
148
149
150
	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);
151

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

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
157
158
159
160
	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
161
	config.keystore_path = matches.value_of("keystore")
162
		.map(|x| Path::new(x).to_owned())
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
163
		.unwrap_or_else(|| keystore_path(&base_path))
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
164
165
		.to_string_lossy()
		.into();
Gav's avatar
Gav committed
166

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

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
169
170
171
172
173
174
	config.pruning = match matches.value_of("pruning") {
		Some("archive") => PruningMode::ArchiveAll,
		None => PruningMode::keep_blocks(256),
		Some(s) => PruningMode::keep_blocks(s.parse()
			.map_err(|_| error::ErrorKind::Input("Invalid pruning mode specified".to_owned()))?),
	};
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
175

Gav Wood's avatar
Gav Wood committed
176
177
178
179
180
181
182
183
184
185
186
187
188
189
	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
190

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
191
	config.roles = role;
192
	{
193
		config.network.boot_nodes.extend(matches
194
			.values_of("bootnodes")
195
			.map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect::<Vec<_>>()));
196
197
198
199
200
201
202
203
204
205
		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!());
206
207
208
209
210
		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,
		};
211
	}
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
212
213

	config.keys = matches.values_of("key").unwrap_or_default().map(str::to_owned).collect();
Gav Wood's avatar
Gav Wood committed
214
215
216
217
218
	if matches.is_present("dev") {
		config.keys.push("Alice".into());
	}

	let sys_conf = SystemConfiguration {
219
		chain_name: config.chain_spec.name().to_owned(),
Gav Wood's avatar
Gav Wood committed
220
	};
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
221

Gav Wood's avatar
Gav Wood committed
222
223
	let _guard = if matches.is_present("telemetry") || matches.value_of("telemetry-url").is_some() {
		let name = config.name.clone();
224
		let chain_name = config.chain_spec.name().to_owned();
Gav Wood's avatar
Gav Wood committed
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
		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" => "",
					"chain" => chain_name.clone(),
				);
			}),
		}))
	} else {
		None
	};

241
	let core = reactor::Core::new().expect("tokio::Core could not be created");
242
	match role == service::Role::LIGHT {
Gav Wood's avatar
Gav Wood committed
243
244
		true => run_until_exit(core, service::new_light(config)?, &matches, sys_conf),
		false => run_until_exit(core, service::new_full(config)?, &matches, sys_conf),
245
246
247
	}
}

Gav Wood's avatar
Gav Wood committed
248
fn run_until_exit<C>(mut core: reactor::Core, service: service::Service<C>, matches: &clap::ArgMatches, sys_conf: SystemConfiguration) -> error::Result<()>
249
	where
250
251
		C: service::Components,
		client::error::Error: From<<<<C as service::Components>::Backend as client::backend::Backend<Block>>::State as state_machine::Backend>::Error>,
252
253
254
255
256
257
258
259
260
261
{
	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
262

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

265
	let _rpc_servers = {
266
267
		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)?;
268
269
270

		let handler = || {
			let chain = rpc::apis::chain::Chain::new(service.client(), core.remote());
271
			let author = rpc::apis::author::Author::new(service.client(), service.transaction_pool());
Gav Wood's avatar
Gav Wood committed
272
			rpc::rpc_handler::<Block, _, _, _, _>(
Tomasz Drwięga's avatar
Tomasz Drwięga committed
273
274
				service.client(),
				chain,
275
				author,
Gav Wood's avatar
Gav Wood committed
276
				sys_conf.clone(),
Tomasz Drwięga's avatar
Tomasz Drwięga committed
277
			)
278
279
280
281
282
283
284
		};
		(
			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
285
	core.run(exit.into_future()).expect("Error running informant event loop");
Gav's avatar
Gav committed
286
287
288
	Ok(())
}

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
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
305
	let mut address: SocketAddr = default.parse().ok().ok_or(format!("Invalid address specified for --{}.", port_param))?;
306
307
308
309
310
311
312
313
	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
314
315
316
317
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
318
319
320
321
322
323
}

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
324
325
326
327
328
329
330
331
332
}

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

fn default_base_path() -> PathBuf {
333
334
335
336
337
338
339
	use app_dirs::{AppInfo, AppDataType};

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

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
340
	app_dirs::get_app_root(
341
342
343
344
		AppDataType::UserData,
		&app_info,
	).expect("app directories exist on all supported platforms; qed")
}
345

Gav's avatar
Gav committed
346
fn init_logger(pattern: &str) {
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
347
348
	use ansi_term::Colour;

Gav's avatar
Gav committed
349
350
	let mut builder = env_logger::LogBuilder::new();
	// Disable info logging by default for some modules:
351
	builder.filter(Some("ws"), log::LogLevelFilter::Warn);
Gav's avatar
Gav committed
352
353
354
355
356
357
358
359
360
	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
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
	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
377

Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
378
379
380
381
382
383
384
		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
385
386
387

	builder.init().expect("Logger initialized only once.");
}
Arkadiy Paronyan's avatar
Arkadiy Paronyan committed
388
389
390
391
392
393
394

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()
}