mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
Basic TUI Integration (#756)
* first explorations at attempting to integrate more user-friendly status screen/ui * rustfmt * adding some logo and color for visual interest * formatting * better integration with stdout, cleaner looking startup and shutdown * rustfmt * update to framework, and first collection of stats from server * rustfmt * commit of basic stat screen, think it's in a good enough state to share * rustfmt * fix to automated tests * fix to automated tests * grin.toml setting * grin.toml setting * grin.toml setting
This commit is contained in:
parent
ab4b2a19e3
commit
23ac36a834
16 changed files with 630 additions and 72 deletions
|
@ -27,7 +27,8 @@ serde = "~1.0.8"
|
||||||
serde_json = "~1.0.7"
|
serde_json = "~1.0.7"
|
||||||
slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_trace"] }
|
slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
term = "~0.4.6"
|
term = "~0.4.6"
|
||||||
|
time = "^0.1"
|
||||||
|
cursive = { git = "https://github.com/gyscos/Cursive" }
|
||||||
# TODO - once "patch" is available we should be able to clean up the workspace dependencies
|
# TODO - once "patch" is available we should be able to clean up the workspace dependencies
|
||||||
# [patch.crate-io]
|
# [patch.crate-io]
|
||||||
# secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
|
# secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
|
||||||
|
|
|
@ -77,7 +77,7 @@ fn data_files() {
|
||||||
burn_reward: true,
|
burn_reward: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
miner_config.cuckoo_miner_plugin_dir = Some(String::from("../target/debug/deps"));
|
miner_config.miner_plugin_dir = Some(String::from("../target/debug/deps"));
|
||||||
|
|
||||||
let mut cuckoo_miner = cuckoo::Miner::new(
|
let mut cuckoo_miner = cuckoo::Miner::new(
|
||||||
consensus::EASINESS,
|
consensus::EASINESS,
|
||||||
|
|
|
@ -65,7 +65,7 @@ fn mine_empty_chain() {
|
||||||
burn_reward: true,
|
burn_reward: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
miner_config.cuckoo_miner_plugin_dir = Some(String::from("../target/debug/deps"));
|
miner_config.miner_plugin_dir = Some(String::from("../target/debug/deps"));
|
||||||
|
|
||||||
let mut cuckoo_miner = cuckoo::Miner::new(
|
let mut cuckoo_miner = cuckoo::Miner::new(
|
||||||
consensus::EASINESS,
|
consensus::EASINESS,
|
||||||
|
|
|
@ -60,7 +60,7 @@ fn test_coinbase_maturity() {
|
||||||
burn_reward: true,
|
burn_reward: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
miner_config.cuckoo_miner_plugin_dir = Some(String::from("../target/debug/deps"));
|
miner_config.miner_plugin_dir = Some(String::from("../target/debug/deps"));
|
||||||
|
|
||||||
let mut cuckoo_miner = cuckoo::Miner::new(
|
let mut cuckoo_miner = cuckoo::Miner::new(
|
||||||
consensus::EASINESS,
|
consensus::EASINESS,
|
||||||
|
|
36
grin.toml
36
grin.toml
|
@ -52,6 +52,10 @@ capabilities = [7]
|
||||||
#skip waiting for sync on startup, (optional param, mostly for testing)
|
#skip waiting for sync on startup, (optional param, mostly for testing)
|
||||||
#skip_sync_wait = false
|
#skip_sync_wait = false
|
||||||
|
|
||||||
|
#whether to run the ncurses TUI. Ncurses must be installed and this
|
||||||
|
#will also disable logging to stdout
|
||||||
|
run_tui = false
|
||||||
|
|
||||||
#The P2P server details (i.e. the server that communicates with other
|
#The P2P server details (i.e. the server that communicates with other
|
||||||
#grin server nodes
|
#grin server nodes
|
||||||
|
|
||||||
|
@ -99,23 +103,19 @@ log_file_append = true
|
||||||
|
|
||||||
#flag whether mining is enabled
|
#flag whether mining is enabled
|
||||||
|
|
||||||
enable_mining = false
|
enable_mining = true
|
||||||
|
|
||||||
#Whether to use cuckoo-miner, and related parameters
|
#Whether to use async mode for the miner, if the plugin supports it.
|
||||||
|
|
||||||
use_cuckoo_miner = true
|
|
||||||
|
|
||||||
#Whether to use async mode for cuckoo miner, if the plugin supports it.
|
|
||||||
#this allows for many searches to be run in parallel, e.g. if the system
|
#this allows for many searches to be run in parallel, e.g. if the system
|
||||||
#has multiple GPUs, or if you want to mine using multiple plugins
|
#has multiple GPUs, or if you want to mine using multiple plugins
|
||||||
|
|
||||||
cuckoo_miner_async_mode = false
|
miner_async_mode = false
|
||||||
|
|
||||||
#If using cuckoo_miner, the directory in which plugins are installed
|
#The directory in which mining plugins are installed
|
||||||
#if not specified, grin will look in the directory /deps relative
|
#if not specified, grin will look in the directory /deps relative
|
||||||
#to the executable
|
#to the executable
|
||||||
|
|
||||||
#cuckoo_miner_plugin_dir = "target/debug/plugins"
|
#miner_plugin_dir = "target/debug/plugins"
|
||||||
|
|
||||||
#The amount of time, in seconds, to attempt to mine on a particular
|
#The amount of time, in seconds, to attempt to mine on a particular
|
||||||
#header before stopping and re-collecting transactions from the pool
|
#header before stopping and re-collecting transactions from the pool
|
||||||
|
@ -160,23 +160,23 @@ burn_reward = false
|
||||||
#Also requires instructions that aren't available on
|
#Also requires instructions that aren't available on
|
||||||
#older processors. In this case, use mean_compat_cpu
|
#older processors. In this case, use mean_compat_cpu
|
||||||
#instead
|
#instead
|
||||||
#[[mining.cuckoo_miner_plugin_config]]
|
#[[mining.miner_plugin_config]]
|
||||||
#type_filter = "mean_cpu"
|
#type_filter = "mean_cpu"
|
||||||
#[mining.cuckoo_miner_plugin_config.device_parameters.0]
|
#[mining.cuckoo_miner_plugin_config.device_parameters.0]
|
||||||
#NUM_THREADS = 1
|
#NUM_THREADS = 1
|
||||||
|
|
||||||
#As above, but for older processors
|
#As above, but for older processors
|
||||||
[[mining.cuckoo_miner_plugin_config]]
|
[[mining.miner_plugin_config]]
|
||||||
type_filter = "mean_compat_cpu"
|
type_filter = "mean_compat_cpu"
|
||||||
[mining.cuckoo_miner_plugin_config.device_parameters.0]
|
[mining.miner_plugin_config.device_parameters.0]
|
||||||
NUM_THREADS = 1
|
NUM_THREADS = 1
|
||||||
|
|
||||||
#note lean_cpu currently has a bug which prevents it from
|
#note lean_cpu currently has a bug which prevents it from
|
||||||
#working with threads > 1
|
#working with threads > 1
|
||||||
|
|
||||||
#[[mining.cuckoo_miner_plugin_config]]
|
#[[mining.miner_plugin_config]]
|
||||||
#type_filter = "lean_cpu"
|
#type_filter = "lean_cpu"
|
||||||
#[mining.cuckoo_miner_plugin_config.device_parameters.0]
|
#[mining.miner_plugin_config.device_parameters.0]
|
||||||
#NUM_THREADS = 1
|
#NUM_THREADS = 1
|
||||||
|
|
||||||
#CUDA Miner (Included here for integration only, Not ready for use)
|
#CUDA Miner (Included here for integration only, Not ready for use)
|
||||||
|
@ -193,9 +193,9 @@ NUM_THREADS = 1
|
||||||
#disabled unless explicitly enabled by setting the 'USE_DEVICE'
|
#disabled unless explicitly enabled by setting the 'USE_DEVICE'
|
||||||
#param to 1 on each device, as demonstrated below.
|
#param to 1 on each device, as demonstrated below.
|
||||||
|
|
||||||
#[[mining.cuckoo_miner_plugin_config]]
|
#[[mining.miner_plugin_config]]
|
||||||
#type_filter = "cuda"
|
#type_filter = "cuda"
|
||||||
#[mining.cuckoo_miner_plugin_config.device_parameters.0]
|
#[mining.miner_plugin_config.device_parameters.0]
|
||||||
#USE_DEVICE = 1
|
#USE_DEVICE = 1
|
||||||
|
|
||||||
# Below are advanced optional per-device tweakable params
|
# Below are advanced optional per-device tweakable params
|
||||||
|
@ -215,8 +215,8 @@ NUM_THREADS = 1
|
||||||
#TRIM_3_TPB = 32
|
#TRIM_3_TPB = 32
|
||||||
#RENAME_3_TPB = 8
|
#RENAME_3_TPB = 8
|
||||||
|
|
||||||
#[mining.cuckoo_miner_plugin_config.device_parameters.1]
|
#[mining.miner_plugin_config.device_parameters.1]
|
||||||
#USE_DEVICE = 1
|
#USE_DEVICE = 1
|
||||||
|
|
||||||
#[mining.cuckoo_miner_plugin_config.device_parameters.2]
|
#[mining.miner_plugin_config.device_parameters.2]
|
||||||
#USE_DEVICE = 1
|
#USE_DEVICE = 1
|
||||||
|
|
|
@ -26,16 +26,16 @@ use adapters::PoolToChainAdapter;
|
||||||
use core::consensus;
|
use core::consensus;
|
||||||
use core::core;
|
use core::core;
|
||||||
use core::core::Proof;
|
use core::core::Proof;
|
||||||
use pow::cuckoo;
|
|
||||||
use core::core::target::Difficulty;
|
use core::core::target::Difficulty;
|
||||||
use core::core::{Block, BlockHeader, Transaction};
|
use core::core::{Block, BlockHeader, Transaction};
|
||||||
use core::core::hash::{Hash, Hashed};
|
use core::core::hash::{Hash, Hashed};
|
||||||
use pow::MiningWorker;
|
use pow::{cuckoo, MiningWorker};
|
||||||
use pow::types::MinerConfig;
|
use pow::types::MinerConfig;
|
||||||
use core::ser;
|
use core::ser;
|
||||||
|
use core::global;
|
||||||
use core::ser::AsFixedBytes;
|
use core::ser::AsFixedBytes;
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
use types::Error;
|
use types::{Error, MiningStats};
|
||||||
|
|
||||||
use chain;
|
use chain;
|
||||||
use pool;
|
use pool;
|
||||||
|
@ -158,6 +158,7 @@ impl Miner {
|
||||||
head: &BlockHeader,
|
head: &BlockHeader,
|
||||||
latest_hash: &Hash,
|
latest_hash: &Hash,
|
||||||
attempt_time_per_block: u32,
|
attempt_time_per_block: u32,
|
||||||
|
mining_stats: Arc<RwLock<MiningStats>>,
|
||||||
) -> Option<Proof> {
|
) -> Option<Proof> {
|
||||||
debug!(
|
debug!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
|
@ -250,6 +251,10 @@ impl Miner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!(LOGGER, "Mining at {} graphs per second", sps_total);
|
info!(LOGGER, "Mining at {} graphs per second", sps_total);
|
||||||
|
if sps_total.is_finite() {
|
||||||
|
let mut mining_stats = mining_stats.write().unwrap();
|
||||||
|
mining_stats.combined_gps = sps_total;
|
||||||
|
}
|
||||||
next_stat_output = time::get_time().sec + stat_output_interval;
|
next_stat_output = time::get_time().sec + stat_output_interval;
|
||||||
}
|
}
|
||||||
// avoid busy wait
|
// avoid busy wait
|
||||||
|
@ -277,6 +282,7 @@ impl Miner {
|
||||||
head: &BlockHeader,
|
head: &BlockHeader,
|
||||||
attempt_time_per_block: u32,
|
attempt_time_per_block: u32,
|
||||||
latest_hash: &mut Hash,
|
latest_hash: &mut Hash,
|
||||||
|
mining_stats: Arc<RwLock<MiningStats>>,
|
||||||
) -> Option<Proof> {
|
) -> Option<Proof> {
|
||||||
// look for a pow for at most attempt_time_per_block sec on the same block (to
|
// look for a pow for at most attempt_time_per_block sec on the same block (to
|
||||||
// give a chance to new
|
// give a chance to new
|
||||||
|
@ -350,6 +356,10 @@ impl Miner {
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Mining at {} graphs per second", last_hashes_per_sec
|
"Mining at {} graphs per second", last_hashes_per_sec
|
||||||
);
|
);
|
||||||
|
if last_hashes_per_sec.is_finite() {
|
||||||
|
let mut mining_stats = mining_stats.write().unwrap();
|
||||||
|
mining_stats.combined_gps = last_hashes_per_sec;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next_stat_check = time::get_time().sec + stat_check_interval;
|
next_stat_check = time::get_time().sec + stat_check_interval;
|
||||||
}
|
}
|
||||||
|
@ -381,6 +391,7 @@ impl Miner {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inner part of mining loop for the internal miner
|
/// The inner part of mining loop for the internal miner
|
||||||
|
/// kept around mostly for automated testing purposes
|
||||||
pub fn inner_loop_sync_internal<T: MiningWorker>(
|
pub fn inner_loop_sync_internal<T: MiningWorker>(
|
||||||
&self,
|
&self,
|
||||||
miner: &mut T,
|
miner: &mut T,
|
||||||
|
@ -453,14 +464,21 @@ impl Miner {
|
||||||
|
|
||||||
/// Starts the mining loop, building a new block on top of the existing
|
/// Starts the mining loop, building a new block on top of the existing
|
||||||
/// chain anytime required and looking for PoW solution.
|
/// chain anytime required and looking for PoW solution.
|
||||||
pub fn run_loop(&self, miner_config: MinerConfig, cuckoo_size: u32, proof_size: usize) {
|
pub fn run_loop(
|
||||||
|
&self,
|
||||||
|
miner_config: MinerConfig,
|
||||||
|
mining_stats: Arc<RwLock<MiningStats>>,
|
||||||
|
cuckoo_size: u32,
|
||||||
|
proof_size: usize,
|
||||||
|
) {
|
||||||
info!(
|
info!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"(Server ID: {}) Starting miner loop.", self.debug_output_id
|
"(Server ID: {}) Starting miner loop.", self.debug_output_id
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut plugin_miner = None;
|
let mut plugin_miner = None;
|
||||||
let mut miner = None;
|
let mut miner = None;
|
||||||
if miner_config.use_cuckoo_miner {
|
if !global::is_automated_testing_mode() {
|
||||||
plugin_miner = Some(PluginMiner::new(
|
plugin_miner = Some(PluginMiner::new(
|
||||||
consensus::EASINESS,
|
consensus::EASINESS,
|
||||||
cuckoo_size,
|
cuckoo_size,
|
||||||
|
@ -475,11 +493,16 @@ impl Miner {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// to prevent the wallet from generating a new HD key derivation for each
|
|
||||||
// iteration, we keep the returned derivation to provide it back when
|
// iteration, we keep the returned derivation to provide it back when
|
||||||
// nothing has changed
|
// nothing has changed
|
||||||
let mut key_id = None;
|
let mut key_id = None;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut mining_stats = mining_stats.write().unwrap();
|
||||||
|
mining_stats.is_mining = true;
|
||||||
|
mining_stats.cuckoo_size = cuckoo_size as u16;
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
debug!(LOGGER, "in miner loop...");
|
debug!(LOGGER, "in miner loop...");
|
||||||
trace!(LOGGER, "key_id: {:?}", key_id);
|
trace!(LOGGER, "key_id: {:?}", key_id);
|
||||||
|
@ -503,10 +526,15 @@ impl Miner {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut b, block_fees) = result.unwrap();
|
let (mut b, block_fees) = result.unwrap();
|
||||||
|
{
|
||||||
|
let mut mining_stats = mining_stats.write().unwrap();
|
||||||
|
mining_stats.block_height = b.header.height;
|
||||||
|
mining_stats.network_difficulty = b.header.difficulty.into_num();
|
||||||
|
}
|
||||||
|
|
||||||
let mut sol = None;
|
let mut sol = None;
|
||||||
let mut use_async = false;
|
let mut use_async = false;
|
||||||
if let Some(c) = self.config.cuckoo_miner_async_mode {
|
if let Some(c) = self.config.miner_async_mode {
|
||||||
if c {
|
if c {
|
||||||
use_async = true;
|
use_async = true;
|
||||||
}
|
}
|
||||||
|
@ -521,6 +549,7 @@ impl Miner {
|
||||||
&head,
|
&head,
|
||||||
&latest_hash,
|
&latest_hash,
|
||||||
miner_config.attempt_time_per_block,
|
miner_config.attempt_time_per_block,
|
||||||
|
mining_stats.clone(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
sol = self.inner_loop_sync_plugin(
|
sol = self.inner_loop_sync_plugin(
|
||||||
|
@ -530,6 +559,7 @@ impl Miner {
|
||||||
&head,
|
&head,
|
||||||
miner_config.attempt_time_per_block,
|
miner_config.attempt_time_per_block,
|
||||||
&mut latest_hash,
|
&mut latest_hash,
|
||||||
|
mining_stats.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,19 +45,33 @@ pub struct Server {
|
||||||
pub chain: Arc<chain::Chain>,
|
pub chain: Arc<chain::Chain>,
|
||||||
/// in-memory transaction pool
|
/// in-memory transaction pool
|
||||||
tx_pool: Arc<RwLock<pool::TransactionPool<PoolToChainAdapter>>>,
|
tx_pool: Arc<RwLock<pool::TransactionPool<PoolToChainAdapter>>>,
|
||||||
|
/// Whether we're currently syncing
|
||||||
currently_syncing: Arc<AtomicBool>,
|
currently_syncing: Arc<AtomicBool>,
|
||||||
|
/// To be passed around to collect stats and info
|
||||||
|
state_info: ServerStateInfo,
|
||||||
|
/// Stop flag
|
||||||
stop: Arc<AtomicBool>,
|
stop: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
/// Instantiates and starts a new server.
|
/// Instantiates and starts a new server. Optionally takes a callback
|
||||||
pub fn start(config: ServerConfig) -> Result<(), Error> {
|
/// for the server to send an ARC copy of itself, to allow another process
|
||||||
|
/// to poll info about the server status
|
||||||
|
pub fn start<F>(config: ServerConfig, mut info_callback: F) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
F: FnMut(Arc<Server>),
|
||||||
|
{
|
||||||
let mut mining_config = config.mining_config.clone();
|
let mut mining_config = config.mining_config.clone();
|
||||||
let serv = Server::new(config)?;
|
let serv = Arc::new(Server::new(config)?);
|
||||||
if mining_config.as_mut().unwrap().enable_mining {
|
if mining_config.as_mut().unwrap().enable_mining {
|
||||||
|
{
|
||||||
|
let mut mining_stats = serv.state_info.mining_stats.write().unwrap();
|
||||||
|
mining_stats.is_enabled = true;
|
||||||
|
}
|
||||||
serv.start_miner(mining_config.unwrap());
|
serv.start_miner(mining_config.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info_callback(serv.clone());
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(time::Duration::from_secs(1));
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
if serv.stop.load(Ordering::Relaxed) {
|
if serv.stop.load(Ordering::Relaxed) {
|
||||||
|
@ -97,6 +111,7 @@ impl Server {
|
||||||
pool_adapter.set_chain(Arc::downgrade(&shared_chain));
|
pool_adapter.set_chain(Arc::downgrade(&shared_chain));
|
||||||
|
|
||||||
let currently_syncing = Arc::new(AtomicBool::new(true));
|
let currently_syncing = Arc::new(AtomicBool::new(true));
|
||||||
|
let awaiting_peers = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
let net_adapter = Arc::new(NetToChainAdapter::new(
|
let net_adapter = Arc::new(NetToChainAdapter::new(
|
||||||
currently_syncing.clone(),
|
currently_syncing.clone(),
|
||||||
|
@ -154,6 +169,7 @@ impl Server {
|
||||||
|
|
||||||
sync::run_sync(
|
sync::run_sync(
|
||||||
currently_syncing.clone(),
|
currently_syncing.clone(),
|
||||||
|
awaiting_peers.clone(),
|
||||||
p2p_server.peers.clone(),
|
p2p_server.peers.clone(),
|
||||||
shared_chain.clone(),
|
shared_chain.clone(),
|
||||||
skip_sync_wait,
|
skip_sync_wait,
|
||||||
|
@ -182,6 +198,10 @@ impl Server {
|
||||||
chain: shared_chain,
|
chain: shared_chain,
|
||||||
tx_pool: tx_pool,
|
tx_pool: tx_pool,
|
||||||
currently_syncing: currently_syncing,
|
currently_syncing: currently_syncing,
|
||||||
|
state_info: ServerStateInfo {
|
||||||
|
awaiting_peers: awaiting_peers,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
stop: stop,
|
stop: stop,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -210,6 +230,7 @@ impl Server {
|
||||||
self.tx_pool.clone(),
|
self.tx_pool.clone(),
|
||||||
self.stop.clone(),
|
self.stop.clone(),
|
||||||
);
|
);
|
||||||
|
let mining_stats = self.state_info.mining_stats.clone();
|
||||||
miner.set_debug_output_id(format!("Port {}", self.config.p2p_config.port));
|
miner.set_debug_output_id(format!("Port {}", self.config.p2p_config.port));
|
||||||
let _ = thread::Builder::new()
|
let _ = thread::Builder::new()
|
||||||
.name("miner".to_string())
|
.name("miner".to_string())
|
||||||
|
@ -220,7 +241,7 @@ impl Server {
|
||||||
while currently_syncing.load(Ordering::Relaxed) {
|
while currently_syncing.load(Ordering::Relaxed) {
|
||||||
thread::sleep(secs_5);
|
thread::sleep(secs_5);
|
||||||
}
|
}
|
||||||
miner.run_loop(config.clone(), cuckoo_size as u32, proof_size);
|
miner.run_loop(config.clone(), mining_stats, cuckoo_size as u32, proof_size);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,9 +261,15 @@ impl Server {
|
||||||
/// other
|
/// other
|
||||||
/// consumers
|
/// consumers
|
||||||
pub fn get_server_stats(&self) -> Result<ServerStats, Error> {
|
pub fn get_server_stats(&self) -> Result<ServerStats, Error> {
|
||||||
|
let mining_stats = self.state_info.mining_stats.read().unwrap().clone();
|
||||||
|
let awaiting_peers = self.state_info.awaiting_peers.load(Ordering::Relaxed);
|
||||||
Ok(ServerStats {
|
Ok(ServerStats {
|
||||||
peer_count: self.peer_count(),
|
peer_count: self.peer_count(),
|
||||||
head: self.head(),
|
head: self.head(),
|
||||||
|
header_head: self.header_head(),
|
||||||
|
is_syncing: self.currently_syncing.load(Ordering::Relaxed),
|
||||||
|
awaiting_peers: awaiting_peers,
|
||||||
|
mining_stats: mining_stats,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ use util::LOGGER;
|
||||||
/// Starts the syncing loop, just spawns two threads that loop forever
|
/// Starts the syncing loop, just spawns two threads that loop forever
|
||||||
pub fn run_sync(
|
pub fn run_sync(
|
||||||
currently_syncing: Arc<AtomicBool>,
|
currently_syncing: Arc<AtomicBool>,
|
||||||
|
awaiting_peers: Arc<AtomicBool>,
|
||||||
peers: Arc<p2p::Peers>,
|
peers: Arc<p2p::Peers>,
|
||||||
chain: Arc<chain::Chain>,
|
chain: Arc<chain::Chain>,
|
||||||
skip_sync_wait: bool,
|
skip_sync_wait: bool,
|
||||||
|
@ -45,7 +46,9 @@ pub fn run_sync(
|
||||||
|
|
||||||
// initial sleep to give us time to peer with some nodes
|
// initial sleep to give us time to peer with some nodes
|
||||||
if !skip_sync_wait {
|
if !skip_sync_wait {
|
||||||
|
awaiting_peers.store(true, Ordering::Relaxed);
|
||||||
thread::sleep(Duration::from_secs(30));
|
thread::sleep(Duration::from_secs(30));
|
||||||
|
awaiting_peers.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fast sync has 3 states:
|
// fast sync has 3 states:
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use api;
|
use api;
|
||||||
use chain;
|
use chain;
|
||||||
|
@ -145,6 +147,10 @@ pub struct ServerConfig {
|
||||||
/// Whether to skip the sync timeout on startup
|
/// Whether to skip the sync timeout on startup
|
||||||
/// (To assist testing on solo chains)
|
/// (To assist testing on solo chains)
|
||||||
pub skip_sync_wait: Option<bool>,
|
pub skip_sync_wait: Option<bool>,
|
||||||
|
|
||||||
|
/// Whether to run the TUI
|
||||||
|
/// if enabled, this will disable logging to stdout
|
||||||
|
pub run_tui: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerConfig {
|
impl Default for ServerConfig {
|
||||||
|
@ -161,19 +167,74 @@ impl Default for ServerConfig {
|
||||||
archive_mode: None,
|
archive_mode: None,
|
||||||
pool_config: pool::PoolConfig::default(),
|
pool_config: pool::PoolConfig::default(),
|
||||||
skip_sync_wait: None,
|
skip_sync_wait: None,
|
||||||
|
run_tui: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Thread-safe container to return all server related stats that other
|
/// Server state info collection struct, to be passed around into internals
|
||||||
/// consumers might be interested in, such as test results
|
/// and populated when required
|
||||||
///
|
#[derive(Clone)]
|
||||||
///
|
pub struct ServerStateInfo {
|
||||||
///
|
/// whether we're in a state of waiting for peers at startup
|
||||||
|
pub awaiting_peers: Arc<AtomicBool>,
|
||||||
|
/// Mining stats
|
||||||
|
pub mining_stats: Arc<RwLock<MiningStats>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServerStateInfo {
|
||||||
|
fn default() -> ServerStateInfo {
|
||||||
|
ServerStateInfo {
|
||||||
|
awaiting_peers: Arc::new(AtomicBool::new(false)),
|
||||||
|
mining_stats: Arc::new(RwLock::new(MiningStats::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Simpler thread-unware version of above to be populated and retured to
|
||||||
|
/// consumers might be interested in, such as test results or UI
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerStats {
|
pub struct ServerStats {
|
||||||
/// Number of peers
|
/// Number of peers
|
||||||
pub peer_count: u32,
|
pub peer_count: u32,
|
||||||
/// Chain head
|
/// Chain head
|
||||||
pub head: chain::Tip,
|
pub head: chain::Tip,
|
||||||
|
/// sync header head
|
||||||
|
pub header_head: chain::Tip,
|
||||||
|
/// Whether we're currently syncing
|
||||||
|
pub is_syncing: bool,
|
||||||
|
/// Whether we're awaiting peers
|
||||||
|
pub awaiting_peers: bool,
|
||||||
|
/// Handle to current mining stats
|
||||||
|
pub mining_stats: MiningStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct to return relevant information about the mining process
|
||||||
|
/// back to interested callers (such as the TUI)
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MiningStats {
|
||||||
|
/// whether mining is enabled
|
||||||
|
pub is_enabled: bool,
|
||||||
|
/// whether we're currently mining
|
||||||
|
pub is_mining: bool,
|
||||||
|
/// combined graphs per second
|
||||||
|
pub combined_gps: f64,
|
||||||
|
/// what block height we're mining at
|
||||||
|
pub block_height: u64,
|
||||||
|
/// current network difficulty we're working on
|
||||||
|
pub network_difficulty: u64,
|
||||||
|
/// cuckoo size used for mining
|
||||||
|
pub cuckoo_size: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MiningStats {
|
||||||
|
fn default() -> MiningStats {
|
||||||
|
MiningStats {
|
||||||
|
is_enabled: false,
|
||||||
|
is_mining: false,
|
||||||
|
combined_gps: 0.0,
|
||||||
|
block_height: 0,
|
||||||
|
network_difficulty: 0,
|
||||||
|
cuckoo_size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,10 +219,9 @@ impl LocalServerContainer {
|
||||||
let miner_config = pow::types::MinerConfig {
|
let miner_config = pow::types::MinerConfig {
|
||||||
enable_mining: self.config.start_miner,
|
enable_mining: self.config.start_miner,
|
||||||
burn_reward: self.config.burn_mining_rewards,
|
burn_reward: self.config.burn_mining_rewards,
|
||||||
use_cuckoo_miner: false,
|
miner_async_mode: Some(false),
|
||||||
cuckoo_miner_async_mode: Some(false),
|
miner_plugin_dir: None,
|
||||||
cuckoo_miner_plugin_dir: Some(String::from("../target/debug/deps")),
|
miner_plugin_config: Some(plugin_config_vec),
|
||||||
cuckoo_miner_plugin_config: Some(plugin_config_vec),
|
|
||||||
wallet_listener_url: self.config.coinbase_wallet_address.clone(),
|
wallet_listener_url: self.config.coinbase_wallet_address.clone(),
|
||||||
slow_down_in_millis: Some(self.config.miner_slowdown_in_millis.clone()),
|
slow_down_in_millis: Some(self.config.miner_slowdown_in_millis.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -48,7 +48,7 @@ fn basic_genesis_mine() {
|
||||||
// Create a server pool
|
// Create a server pool
|
||||||
let mut pool_config = LocalServerContainerPoolConfig::default();
|
let mut pool_config = LocalServerContainerPoolConfig::default();
|
||||||
pool_config.base_name = String::from(test_name_dir);
|
pool_config.base_name = String::from(test_name_dir);
|
||||||
pool_config.run_length_in_seconds = 5;
|
pool_config.run_length_in_seconds = 10;
|
||||||
|
|
||||||
pool_config.base_api_port = 30000;
|
pool_config.base_api_port = 30000;
|
||||||
pool_config.base_p2p_port = 31000;
|
pool_config.base_p2p_port = 31000;
|
||||||
|
@ -338,10 +338,9 @@ fn miner_config() -> pow::types::MinerConfig {
|
||||||
pow::types::MinerConfig {
|
pow::types::MinerConfig {
|
||||||
enable_mining: true,
|
enable_mining: true,
|
||||||
burn_reward: true,
|
burn_reward: true,
|
||||||
use_cuckoo_miner: false,
|
miner_async_mode: Some(false),
|
||||||
cuckoo_miner_async_mode: Some(false),
|
miner_plugin_dir: None,
|
||||||
cuckoo_miner_plugin_dir: Some(String::from("../target/debug/deps")),
|
miner_plugin_config: Some(plugin_config_vec),
|
||||||
cuckoo_miner_plugin_config: Some(plugin_config_vec),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ pub fn mine_genesis_block(
|
||||||
let proof_size = global::proofsize();
|
let proof_size = global::proofsize();
|
||||||
|
|
||||||
let mut miner: Box<MiningWorker> = match miner_config {
|
let mut miner: Box<MiningWorker> = match miner_config {
|
||||||
Some(c) => if c.use_cuckoo_miner {
|
Some(c) => if c.enable_mining {
|
||||||
let mut p = plugin::PluginMiner::new(consensus::EASINESS, sz, proof_size);
|
let mut p = plugin::PluginMiner::new(consensus::EASINESS, sz, proof_size);
|
||||||
p.init(c.clone());
|
p.init(c.clone());
|
||||||
Box::new(p)
|
Box::new(p)
|
||||||
|
|
|
@ -69,16 +69,16 @@ impl PluginMiner {
|
||||||
let mut exe_path = env::current_exe().unwrap();
|
let mut exe_path = env::current_exe().unwrap();
|
||||||
exe_path.pop();
|
exe_path.pop();
|
||||||
let exe_path = exe_path.to_str().unwrap();
|
let exe_path = exe_path.to_str().unwrap();
|
||||||
let plugin_install_path = match miner_config.cuckoo_miner_plugin_dir.clone() {
|
let plugin_install_path = match miner_config.miner_plugin_dir.clone() {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => String::from(format!("{}/plugins", exe_path)),
|
None => String::from(format!("{}/plugins", exe_path)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut plugin_vec_filters = Vec::new();
|
let mut plugin_vec_filters = Vec::new();
|
||||||
if let None = miner_config.cuckoo_miner_plugin_config {
|
if let None = miner_config.miner_plugin_config {
|
||||||
plugin_vec_filters.push(String::from("simple"));
|
plugin_vec_filters.push(String::from("simple"));
|
||||||
} else {
|
} else {
|
||||||
for p in miner_config.clone().cuckoo_miner_plugin_config.unwrap() {
|
for p in miner_config.clone().miner_plugin_config.unwrap() {
|
||||||
plugin_vec_filters.push(p.type_filter);
|
plugin_vec_filters.push(p.type_filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ impl PluginMiner {
|
||||||
caps[0].full_path.clone()
|
caps[0].full_path.clone()
|
||||||
);
|
);
|
||||||
config.plugin_full_path = caps[0].full_path.clone();
|
config.plugin_full_path = caps[0].full_path.clone();
|
||||||
if let Some(l) = miner_config.clone().cuckoo_miner_plugin_config {
|
if let Some(l) = miner_config.clone().miner_plugin_config {
|
||||||
if let Some(dp) = l[index].device_parameters.clone() {
|
if let Some(dp) = l[index].device_parameters.clone() {
|
||||||
for (device, param_map) in dp.into_iter() {
|
for (device, param_map) in dp.into_iter() {
|
||||||
for (param_name, param_value) in param_map.into_iter() {
|
for (param_name, param_value) in param_map.into_iter() {
|
||||||
|
|
|
@ -37,20 +37,17 @@ impl Default for CuckooMinerPluginConfig {
|
||||||
/// Mining configuration
|
/// Mining configuration
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct MinerConfig {
|
pub struct MinerConfig {
|
||||||
/// Whether to start the miner with the server
|
/// Whether to start the miner with the server (requires using cuckoo-miner)
|
||||||
pub enable_mining: bool,
|
pub enable_mining: bool,
|
||||||
|
|
||||||
/// Whether to use the cuckoo-miner crate and plugin for mining
|
|
||||||
pub use_cuckoo_miner: bool,
|
|
||||||
|
|
||||||
/// Whether to use the async version of mining
|
/// Whether to use the async version of mining
|
||||||
pub cuckoo_miner_async_mode: Option<bool>,
|
pub miner_async_mode: Option<bool>,
|
||||||
|
|
||||||
/// plugin dir
|
/// plugin dir
|
||||||
pub cuckoo_miner_plugin_dir: Option<String>,
|
pub miner_plugin_dir: Option<String>,
|
||||||
|
|
||||||
/// Cuckoo miner plugin configuration, one for each plugin
|
/// Cuckoo miner plugin configuration, one for each plugin
|
||||||
pub cuckoo_miner_plugin_config: Option<Vec<CuckooMinerPluginConfig>>,
|
pub miner_plugin_config: Option<Vec<CuckooMinerPluginConfig>>,
|
||||||
|
|
||||||
/// How long to wait before stopping the miner, recollecting transactions
|
/// How long to wait before stopping the miner, recollecting transactions
|
||||||
/// and starting again
|
/// and starting again
|
||||||
|
@ -72,10 +69,9 @@ impl Default for MinerConfig {
|
||||||
fn default() -> MinerConfig {
|
fn default() -> MinerConfig {
|
||||||
MinerConfig {
|
MinerConfig {
|
||||||
enable_mining: false,
|
enable_mining: false,
|
||||||
use_cuckoo_miner: false,
|
miner_async_mode: None,
|
||||||
cuckoo_miner_async_mode: None,
|
miner_plugin_dir: None,
|
||||||
cuckoo_miner_plugin_dir: None,
|
miner_plugin_config: None,
|
||||||
cuckoo_miner_plugin_config: None,
|
|
||||||
wallet_listener_url: "http://localhost:13415".to_string(),
|
wallet_listener_url: "http://localhost:13415".to_string(),
|
||||||
burn_reward: false,
|
burn_reward: false,
|
||||||
slow_down_in_millis: Some(0),
|
slow_down_in_millis: Some(0),
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
|
|
||||||
extern crate blake2_rfc as blake2;
|
extern crate blake2_rfc as blake2;
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
extern crate cursive;
|
||||||
extern crate daemonize;
|
extern crate daemonize;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate slog;
|
extern crate slog;
|
||||||
|
extern crate time;
|
||||||
|
|
||||||
extern crate grin_api as api;
|
extern crate grin_api as api;
|
||||||
extern crate grin_config as config;
|
extern crate grin_config as config;
|
||||||
|
@ -32,10 +34,13 @@ extern crate grin_util as util;
|
||||||
extern crate grin_wallet as wallet;
|
extern crate grin_wallet as wallet;
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use daemonize::Daemonize;
|
use daemonize::Daemonize;
|
||||||
|
@ -45,6 +50,38 @@ use core::global;
|
||||||
use core::core::amount_to_hr_string;
|
use core::core::amount_to_hr_string;
|
||||||
use util::{init_logger, LoggingConfig, LOGGER};
|
use util::{init_logger, LoggingConfig, LOGGER};
|
||||||
|
|
||||||
|
/// wrap below to allow UI to clean up on stop
|
||||||
|
fn start_server(config: grin::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
|
||||||
|
println!("Shutting down...");
|
||||||
|
thread::sleep(Duration::from_millis(1000));
|
||||||
|
println!("Shutdown complete.");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_server_tui(config: grin::ServerConfig) {
|
||||||
|
// Run the UI controller.. here for now for simplicity to access
|
||||||
|
// everything it might need
|
||||||
|
if config.run_tui.is_some() && config.run_tui.unwrap() {
|
||||||
|
println!("Starting GRIN in UI mode...");
|
||||||
|
grin::Server::start(config, |serv: Arc<grin::Server>| {
|
||||||
|
let _ = thread::Builder::new()
|
||||||
|
.name("ui".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
let mut controller = ui::Controller::new().unwrap_or_else(|e| {
|
||||||
|
panic!("Error loading UI controller: {}", e);
|
||||||
|
});
|
||||||
|
controller.run(serv.clone());
|
||||||
|
});
|
||||||
|
}).unwrap();
|
||||||
|
} else {
|
||||||
|
grin::Server::start(config, |_| {}).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn start_from_config_file(mut global_config: GlobalConfig) {
|
fn start_from_config_file(mut global_config: GlobalConfig) {
|
||||||
info!(
|
info!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
|
@ -62,7 +99,7 @@ fn start_from_config_file(mut global_config: GlobalConfig) {
|
||||||
.chain_type,
|
.chain_type,
|
||||||
);
|
);
|
||||||
|
|
||||||
grin::Server::start(global_config.members.as_mut().unwrap().server.clone()).unwrap();
|
start_server(global_config.members.as_mut().unwrap().server.clone());
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(Duration::from_secs(60));
|
thread::sleep(Duration::from_secs(60));
|
||||||
}
|
}
|
||||||
|
@ -85,7 +122,18 @@ fn main() {
|
||||||
|
|
||||||
if global_config.using_config_file {
|
if global_config.using_config_file {
|
||||||
// initialise the logger
|
// initialise the logger
|
||||||
init_logger(global_config.members.as_mut().unwrap().logging.clone());
|
let mut log_conf = global_config
|
||||||
|
.members
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.logging
|
||||||
|
.clone()
|
||||||
|
.unwrap();
|
||||||
|
let run_tui = global_config.members.as_mut().unwrap().server.run_tui;
|
||||||
|
if run_tui.is_some() && run_tui.unwrap() {
|
||||||
|
log_conf.log_to_stdout = false;
|
||||||
|
}
|
||||||
|
init_logger(Some(log_conf));
|
||||||
info!(
|
info!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Using configuration file at: {}",
|
"Using configuration file at: {}",
|
||||||
|
@ -351,7 +399,7 @@ fn server_command(server_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
// start the server in the different run modes (interactive or daemon)
|
// start the server in the different run modes (interactive or daemon)
|
||||||
match server_args.subcommand() {
|
match server_args.subcommand() {
|
||||||
("run", _) => {
|
("run", _) => {
|
||||||
grin::Server::start(server_config).unwrap();
|
start_server(server_config);
|
||||||
}
|
}
|
||||||
("start", _) => {
|
("start", _) => {
|
||||||
let daemonize = Daemonize::new()
|
let daemonize = Daemonize::new()
|
||||||
|
@ -359,7 +407,7 @@ fn server_command(server_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
.chown_pid_file(true)
|
.chown_pid_file(true)
|
||||||
.working_directory(current_dir().unwrap())
|
.working_directory(current_dir().unwrap())
|
||||||
.privileged_action(move || {
|
.privileged_action(move || {
|
||||||
grin::Server::start(server_config.clone()).unwrap();
|
start_server(server_config.clone());
|
||||||
loop {
|
loop {
|
||||||
thread::sleep(Duration::from_secs(60));
|
thread::sleep(Duration::from_secs(60));
|
||||||
}
|
}
|
||||||
|
@ -532,11 +580,11 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
recipient_fee,
|
recipient_fee,
|
||||||
} => {
|
} => {
|
||||||
error!(
|
error!(
|
||||||
LOGGER,
|
LOGGER,
|
||||||
"Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).",
|
"Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).",
|
||||||
amount_to_hr_string(recipient_fee),
|
amount_to_hr_string(recipient_fee),
|
||||||
amount_to_hr_string(sender_amount)
|
amount_to_hr_string(sender_amount)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!(LOGGER, "Tx not sent: {:?}", e);
|
error!(LOGGER, "Tx not sent: {:?}", e);
|
||||||
|
|
394
src/bin/ui.rs
Normal file
394
src/bin/ui.rs
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Basic TUI to better output the overall system status and status
|
||||||
|
//! of various subsystems
|
||||||
|
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
use time;
|
||||||
|
|
||||||
|
use cursive::Cursive;
|
||||||
|
use cursive::theme::{BaseColor, BorderStyle, Color};
|
||||||
|
use cursive::theme::PaletteColor::*;
|
||||||
|
use cursive::theme::Color::*;
|
||||||
|
use cursive::theme::BaseColor::*;
|
||||||
|
use cursive::utils::markup::StyledString;
|
||||||
|
use cursive::align::{HAlign, VAlign};
|
||||||
|
use cursive::event::Key;
|
||||||
|
use cursive::views::{BoxView, LayerPosition, LinearLayout, Panel, StackView, TextView};
|
||||||
|
use cursive::direction::Orientation;
|
||||||
|
use cursive::traits::*;
|
||||||
|
|
||||||
|
use grin::Server;
|
||||||
|
|
||||||
|
const WELCOME_LOGO: &str = " GGGGG GGGGGGG
|
||||||
|
GGGGGGG GGGGGGGGG
|
||||||
|
GGGGGGGGG GGGG GGGGGGGGGG
|
||||||
|
GGGGGGGGGGG GGGGGGGG GGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
|
||||||
|
GGGGGG
|
||||||
|
GGGGGGG
|
||||||
|
GGGGGGGG
|
||||||
|
GGGGGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGGG GGGGGGGG GGGGGGGGGGGGG
|
||||||
|
GGGGGGGGGGG GGGGGGGG GGGGGGGGGGGG
|
||||||
|
GGGGGGGGGG GGGGGGGG GGGGGGGGGGG
|
||||||
|
GGGGGGGG GGGGGGGG GGGGGGGGG
|
||||||
|
GGGGGGG GGGGGGGG GGGGGGG
|
||||||
|
GGGG GGGGGGGG GGGG
|
||||||
|
GG GGGGGGGG GG
|
||||||
|
GGGGGGGG ";
|
||||||
|
|
||||||
|
pub struct UI {
|
||||||
|
cursive: Cursive,
|
||||||
|
ui_rx: mpsc::Receiver<UIMessage>,
|
||||||
|
ui_tx: mpsc::Sender<UIMessage>,
|
||||||
|
controller_tx: mpsc::Sender<ControllerMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StatusUpdates {
|
||||||
|
pub basic_status: String,
|
||||||
|
pub peer_count: String,
|
||||||
|
pub chain_height: String,
|
||||||
|
pub basic_mining_config_status: String,
|
||||||
|
pub basic_mining_status: String,
|
||||||
|
pub basic_network_info: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum UIMessage {
|
||||||
|
UpdateStatus(StatusUpdates),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UI {
|
||||||
|
/// Create a new UI
|
||||||
|
pub fn new(controller_tx: mpsc::Sender<ControllerMessage>) -> UI {
|
||||||
|
let (ui_tx, ui_rx) = mpsc::channel::<UIMessage>();
|
||||||
|
let mut grin_ui = UI {
|
||||||
|
cursive: Cursive::new(),
|
||||||
|
ui_tx: ui_tx,
|
||||||
|
ui_rx: ui_rx,
|
||||||
|
controller_tx: controller_tx,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut logo_string = StyledString::new();
|
||||||
|
logo_string.append(StyledString::styled(
|
||||||
|
WELCOME_LOGO,
|
||||||
|
Color::Dark(BaseColor::Green),
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut title_string = StyledString::new();
|
||||||
|
title_string.append(StyledString::styled(
|
||||||
|
"Grin Version 0.0.1",
|
||||||
|
Color::Dark(BaseColor::Green),
|
||||||
|
));
|
||||||
|
let mut logo_view = TextView::new(logo_string)
|
||||||
|
.v_align(VAlign::Center)
|
||||||
|
.h_align(HAlign::Center);
|
||||||
|
logo_view.set_scrollable(false);
|
||||||
|
|
||||||
|
// Create UI objects, etc
|
||||||
|
let basic_status_view = BoxView::with_full_screen(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(BoxView::with_full_screen(logo_view))
|
||||||
|
.child(BoxView::with_full_screen(
|
||||||
|
LinearLayout::new(Orientation::Vertical)
|
||||||
|
.child(TextView::new(title_string))
|
||||||
|
.child(TextView::new("------------------------"))
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(TextView::new("Current Status: "))
|
||||||
|
.child(TextView::new("Starting").with_id("basic_current_status")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(TextView::new("Connected Peers: "))
|
||||||
|
.child(TextView::new("0").with_id("connected_peers")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(TextView::new("Chain Height: "))
|
||||||
|
.child(TextView::new("").with_id("chain_height")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(TextView::new("------------------------")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(TextView::new("").with_id("basic_mining_config_status")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(TextView::new("").with_id("basic_mining_status")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(TextView::new("").with_id("basic_network_info")),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
).with_id("basic_status_view");
|
||||||
|
|
||||||
|
let advanced_status_view = BoxView::with_full_screen(TextView::new(
|
||||||
|
"Advanced Status Display will go here and should contain detailed readouts for:
|
||||||
|
--Latest Blocks
|
||||||
|
--Sync Info
|
||||||
|
--Chain Info
|
||||||
|
--Peer Info
|
||||||
|
--Mining Info
|
||||||
|
",
|
||||||
|
)).with_id("advanced_status");
|
||||||
|
|
||||||
|
let root_stack = StackView::new()
|
||||||
|
.layer(advanced_status_view)
|
||||||
|
.layer(basic_status_view)
|
||||||
|
.with_id("root_stack");
|
||||||
|
|
||||||
|
/*
|
||||||
|
let mut basic_button = Button::new("", |s| {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
basic_button.set_label_raw("1 - Basic Status");
|
||||||
|
let mut advanced_button = Button::new("2 - Advanced Status", |s| {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
//advanced_button.set_label_raw("2 - Advanced Status");
|
||||||
|
let mut config_button = Button::new("", |s| {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
config_button.set_label_raw("3 - Config");
|
||||||
|
let mut quit_button = Button::new("", |s| {
|
||||||
|
//s.quit();
|
||||||
|
});
|
||||||
|
quit_button.set_label_raw("Quit");*/
|
||||||
|
|
||||||
|
let top_layer = LinearLayout::new(Orientation::Vertical)
|
||||||
|
.child(Panel::new(root_stack))
|
||||||
|
.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(Panel::new(TextView::new(
|
||||||
|
"<TAB> Toggle Basic / Advanced view",
|
||||||
|
)))
|
||||||
|
.child(Panel::new(TextView::new("<Q> Quit"))),
|
||||||
|
);
|
||||||
|
/*.child(
|
||||||
|
LinearLayout::new(Orientation::Horizontal)
|
||||||
|
.child(Panel::new(basic_button))
|
||||||
|
.child(Panel::new(advanced_button))
|
||||||
|
.child(Panel::new(config_button))
|
||||||
|
.child(Panel::new(quit_button))
|
||||||
|
);*/
|
||||||
|
|
||||||
|
grin_ui.cursive.add_global_callback(Key::Tab, |s| {
|
||||||
|
//let bas_sta = s.find_id::<Panel<BoxView<TextView>>>("basic_status").unwrap();
|
||||||
|
//let mut root_stack = s.find_id::<StackView>("root_stack").unwrap();
|
||||||
|
//root_stack.add_layer(adv_sta);
|
||||||
|
|
||||||
|
s.call_on_id("root_stack", |sv: &mut StackView| {
|
||||||
|
/*if let FunctionalState::BasicStatus = cur_state {
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
sv.move_to_front(LayerPosition::FromBack(0));
|
||||||
|
//sv.add_layer(advanced_status);
|
||||||
|
/*sv.pop_layer();
|
||||||
|
sv.add_layer(bas_sta);*/ });
|
||||||
|
});
|
||||||
|
|
||||||
|
//set theme
|
||||||
|
let mut theme = grin_ui.cursive.current_theme().clone();
|
||||||
|
theme.shadow = false;
|
||||||
|
theme.borders = BorderStyle::Simple;
|
||||||
|
theme.palette[Background] = Dark(Black);
|
||||||
|
theme.palette[Shadow] = Dark(Black);
|
||||||
|
theme.palette[View] = Dark(Black);
|
||||||
|
theme.palette[Primary] = Dark(White);
|
||||||
|
theme.palette[Highlight] = Dark(Cyan);
|
||||||
|
theme.palette[HighlightInactive] = Dark(Blue);
|
||||||
|
// also secondary, tertiary, TitlePrimary, TitleSecondary
|
||||||
|
grin_ui.cursive.set_theme(theme);
|
||||||
|
|
||||||
|
grin_ui.cursive.add_layer(top_layer);
|
||||||
|
|
||||||
|
// Configure a callback (shutdown, for the first test)
|
||||||
|
let controller_tx_clone = grin_ui.controller_tx.clone();
|
||||||
|
grin_ui.cursive.add_global_callback('q', move |_| {
|
||||||
|
controller_tx_clone
|
||||||
|
.send(ControllerMessage::Shutdown)
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
grin_ui.cursive.set_fps(4);
|
||||||
|
grin_ui
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Step the UI by calling into Cursive's step function, then
|
||||||
|
/// processing any UI messages
|
||||||
|
pub fn step(&mut self) -> bool {
|
||||||
|
if !self.cursive.is_running() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any pending UI messages
|
||||||
|
while let Some(message) = self.ui_rx.try_iter().next() {
|
||||||
|
match message {
|
||||||
|
UIMessage::UpdateStatus(update) => {
|
||||||
|
//find and update here as needed
|
||||||
|
self.cursive
|
||||||
|
.call_on_id("basic_current_status", |t: &mut TextView| {
|
||||||
|
t.set_content(update.basic_status.clone());
|
||||||
|
});
|
||||||
|
self.cursive
|
||||||
|
.call_on_id("connected_peers", |t: &mut TextView| {
|
||||||
|
t.set_content(update.peer_count.clone());
|
||||||
|
});
|
||||||
|
self.cursive.call_on_id("chain_height", |t: &mut TextView| {
|
||||||
|
t.set_content(update.chain_height.clone());
|
||||||
|
});
|
||||||
|
self.cursive
|
||||||
|
.call_on_id("basic_mining_config_status", |t: &mut TextView| {
|
||||||
|
t.set_content(update.basic_mining_config_status.clone());
|
||||||
|
});
|
||||||
|
self.cursive
|
||||||
|
.call_on_id("basic_mining_status", |t: &mut TextView| {
|
||||||
|
t.set_content(update.basic_mining_status.clone());
|
||||||
|
});
|
||||||
|
self.cursive
|
||||||
|
.call_on_id("basic_network_info", |t: &mut TextView| {
|
||||||
|
t.set_content(update.basic_network_info.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step the UI
|
||||||
|
self.cursive.step();
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the UI
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.cursive.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Controller {
|
||||||
|
rx: mpsc::Receiver<ControllerMessage>,
|
||||||
|
ui: UI,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ControllerMessage {
|
||||||
|
Shutdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Controller {
|
||||||
|
/// Create a new controller
|
||||||
|
pub fn new() -> Result<Controller, String> {
|
||||||
|
let (tx, rx) = mpsc::channel::<ControllerMessage>();
|
||||||
|
Ok(Controller {
|
||||||
|
rx: rx,
|
||||||
|
ui: UI::new(tx.clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Run the controller
|
||||||
|
pub fn run(&mut self, server: Arc<Server>) {
|
||||||
|
let stat_update_interval = 1;
|
||||||
|
let mut next_stat_update = time::get_time().sec + stat_update_interval;
|
||||||
|
while self.ui.step() {
|
||||||
|
while let Some(message) = self.rx.try_iter().next() {
|
||||||
|
match message {
|
||||||
|
ControllerMessage::Shutdown => {
|
||||||
|
server.stop();
|
||||||
|
self.ui.stop();
|
||||||
|
/*self.ui
|
||||||
|
.ui_tx
|
||||||
|
.send(UIMessage::UpdateOutput("update".to_string()))
|
||||||
|
.unwrap();*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time::get_time().sec > next_stat_update {
|
||||||
|
self.update_status(server.clone());
|
||||||
|
next_stat_update = time::get_time().sec + stat_update_interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// update the UI with server status at given intervals (should be
|
||||||
|
/// once a second at present
|
||||||
|
pub fn update_status(&mut self, server: Arc<Server>) {
|
||||||
|
let stats = server.get_server_stats().unwrap();
|
||||||
|
let basic_status = {
|
||||||
|
if stats.is_syncing {
|
||||||
|
if stats.awaiting_peers {
|
||||||
|
"Waiting for peers".to_string()
|
||||||
|
} else {
|
||||||
|
format!("Syncing - Latest header: {}", stats.header_head.height).to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"Running".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let basic_mining_config_status = {
|
||||||
|
if stats.mining_stats.is_enabled {
|
||||||
|
"Configured as mining node"
|
||||||
|
} else {
|
||||||
|
"Configured as validating node only (not mining)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (basic_mining_status, basic_network_info) = {
|
||||||
|
if stats.mining_stats.is_enabled {
|
||||||
|
if stats.is_syncing {
|
||||||
|
(
|
||||||
|
"Mining Status: Paused while syncing".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
)
|
||||||
|
} else if stats.mining_stats.combined_gps == 0.0 {
|
||||||
|
(
|
||||||
|
"Mining Status: Starting miner and awating first solution...".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
format!(
|
||||||
|
"Mining Status: Mining at height {} at {:.*} GPS",
|
||||||
|
stats.mining_stats.block_height, 4, stats.mining_stats.combined_gps
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"Cuckoo {} - Network Difficulty {}",
|
||||||
|
stats.mining_stats.cuckoo_size,
|
||||||
|
stats.mining_stats.network_difficulty.to_string()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
("".to_string(), "".to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let update = StatusUpdates {
|
||||||
|
basic_status: basic_status,
|
||||||
|
peer_count: stats.peer_count.to_string(),
|
||||||
|
chain_height: stats.head.height.to_string(),
|
||||||
|
basic_mining_config_status: basic_mining_config_status.to_string(),
|
||||||
|
basic_mining_status: basic_mining_status,
|
||||||
|
basic_network_info: basic_network_info,
|
||||||
|
};
|
||||||
|
self.ui.ui_tx.send(UIMessage::UpdateStatus(update)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue