hustxiaoc/example

grin node

Opened this issue · 0 comments

// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Grin server commands processing
use std::process::exit;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use chrono::prelude::Utc;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::io;
use std::env;

use clap::ArgMatches;
use ctrlc;


use crate::config::GlobalConfig;
use crate::core::global;
use crate::p2p::{PeerAddr, Seeding};
use crate::servers;
use crate::tui::ui;
use crate::servers::common::types::SyncStatus;
use crate::servers::ServerStats;

const NANO_TO_MILLIS: f64 = 1.0 / 1_000_000.0;


fn echo(s: &str, path: &Path) -> io::Result<()> {
    let mut f = File::create(path)?;
    f.write_all(s.as_bytes())
}

/// wrap below to allow UI to clean up on stop
pub fn start_server(config: servers::ServerConfig) {
	start_server_tui(config);
	// Just kill process for now, otherwise the process
	// hangs around until sigint because the API server
	// currently has no shutdown facility
	warn!("Shutting down...");
	thread::sleep(Duration::from_millis(1000));
	warn!("Shutdown complete.");
	exit(0);
}

fn start_server_tui(config: servers::ServerConfig) {
	// Run the UI controller.. here for now for simplicity to access
	// everything it might need
	warn!("Starting GRIN w/o UI...");
	servers::Server::start(config, |serv: Arc<servers::Server>| {
		let server = serv.clone();
		let _ = thread::Builder::new()
			.name("progress".to_string())
			.spawn(move || {
				loop {
					let stats = server.get_server_stats().unwrap();
					let basic_status = {
						match stats.sync_status {
							SyncStatus::Initial => "Initializing".to_string(),
							SyncStatus::NoSync => "Running".to_string(),
							SyncStatus::AwaitingPeers(_) => "Waiting for peers".to_string(),
							SyncStatus::HeaderSync {
								current_height,
								highest_height,
							} => {
								let percent = if highest_height == 0 {
									0
								} else {
									current_height * 100 / highest_height
								};
								format!("Downloading headers: {}%", percent)
							}
							SyncStatus::TxHashsetDownload {
								start_time,
								downloaded_size,
								total_size,
							} => {
								if total_size > 0 {
									let percent = if total_size > 0 {
										downloaded_size * 100 / total_size
									} else {
										0
									};
									let start = start_time.timestamp_nanos();
									let fin = Utc::now().timestamp_nanos();
									let dur_ms = (fin - start) as f64 * NANO_TO_MILLIS;

									format!("Downloading {}(MB) chain state for state sync: {}% at {:.1?}(kB/s)",
									total_size / 1_000_000,
									percent,
									if dur_ms > 1.0f64 { downloaded_size as f64 / dur_ms as f64 } else { 0f64 },
									)
								} else {
									let start = start_time.timestamp_millis();
									let fin = Utc::now().timestamp_millis();
									let dur_secs = (fin - start) / 1000;

									format!("Downloading chain state for state sync. Waiting remote peer to start: {}s",
													dur_secs,
													)
								}
							}
							SyncStatus::TxHashsetSetup => {
								"Preparing chain state for validation".to_string()
							}
							SyncStatus::TxHashsetValidation {
								kernels,
								kernel_total,
								rproofs,
								rproof_total,
							} => {
								// 10% of overall progress is attributed to kernel validation
								// 90% to range proofs (which are much longer)
								let mut percent = if kernel_total > 0 {
									kernels * 10 / kernel_total
								} else {
									0
								};
								percent += if rproof_total > 0 {
									rproofs * 90 / rproof_total
								} else {
									0
								};
								format!("Validating chain state: {}%", percent)
							}
							SyncStatus::TxHashsetSave => {
								"Finalizing chain state for state sync".to_string()
							}
							SyncStatus::TxHashsetDone => {
								"Finalized chain state for state sync".to_string()
							}
							SyncStatus::BodySync {
								current_height,
								highest_height,
							} => {
								let percent = if highest_height == 0 {
									0
								} else {
									current_height * 100 / highest_height
								};
								format!("Downloading blocks: {}%", percent)
							}
						}
					};

					println!("[stats]{:?}|{:?}|{:?}|{:?}",
						basic_status,
						stats.peer_count,
						stats.header_head.height,
						stats.head.height
					);

					echo(&format!("[stats]{:?}|{:?}|{:?}|{:?}",
						basic_status,
						stats.peer_count,
						stats.header_head.height,
						stats.head.height
					), &Path::new(&env::var("status_path").unwrap())).unwrap_or_else(|why| { });

					thread::sleep(Duration::from_secs(3));
				}
			});


		let running = Arc::new(AtomicBool::new(true));
		let r = running.clone();
		ctrlc::set_handler(move || {
			r.store(false, Ordering::SeqCst);
		})
		.expect("Error setting handler for both SIGINT (Ctrl+C) and SIGTERM (kill)");
		while running.load(Ordering::SeqCst) {
			thread::sleep(Duration::from_secs(1));
		}
		warn!("Received SIGINT (Ctrl+C) or SIGTERM (kill).");
		serv.stop();
	})
	.unwrap();
}

/// Handles the server part of the command line, mostly running, starting and
/// stopping the Grin blockchain server. Processes all the command line
/// arguments to build a proper configuration and runs Grin with that
/// configuration.
pub fn server_command(
	server_args: Option<&ArgMatches<'_>>,
	mut global_config: GlobalConfig,
) -> i32 {
	global::set_mining_mode(
		global_config
			.members
			.as_mut()
			.unwrap()
			.server
			.clone()
			.chain_type,
	);

	// just get defaults from the global config
	let mut server_config = global_config.members.as_ref().unwrap().server.clone();

	if let Some(a) = server_args {
		if let Some(port) = a.value_of("port") {
			server_config.p2p_config.port = port.parse().unwrap();
		}

		if let Some(api_port) = a.value_of("api_port") {
			let default_ip = "0.0.0.0";
			server_config.api_http_addr = format!("{}:{}", default_ip, api_port);
		}

		if let Some(wallet_url) = a.value_of("wallet_url") {
			server_config
				.stratum_mining_config
				.as_mut()
				.unwrap()
				.wallet_listener_url = wallet_url.to_string();
		}

		if let Some(seeds) = a.values_of("seed") {
			let seed_addrs = seeds
				.filter_map(|x| x.parse().ok())
				.map(|x| PeerAddr(x))
				.collect();
			server_config.p2p_config.seeding_type = Seeding::List;
			server_config.p2p_config.seeds = Some(seed_addrs);
		}
	}

	if let Some(a) = server_args {
		match a.subcommand() {
			("run", _) => {
				start_server(server_config);
			}
			("", _) => {
				println!("Subcommand required, use 'grin help server' for details");
			}
			(cmd, _) => {
				println!(":: {:?}", server_args);
				panic!(
					"Unknown server command '{}', use 'grin help server' for details",
					cmd
				);
			}
		}
	} else {
		start_server(server_config);
	}
	0
}