// 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 implementation, glues the different parts of the system (mostly //! the peer-to-peer server, the blockchain and the transaction pool) and acts //! as a facade. use std::net::SocketAddr; use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; use std::time; use adapters::*; use api; use chain; use core::{consensus, genesis, global}; use core::core::target::Difficulty; use dandelion_monitor; use miner; use p2p; use pool; use seed; use sync; use types::*; use stats::*; use pow; use util::LOGGER; /// Grin server holding internal structures. pub struct Server { /// server config pub config: ServerConfig, /// handle to our network server pub p2p: Arc, /// data store access pub chain: Arc, /// in-memory transaction pool tx_pool: Arc>>, /// Whether we're currently syncing currently_syncing: Arc, /// To be passed around to collect stats and info state_info: ServerStateInfo, /// Stop flag stop: Arc, } impl Server { /// Instantiates and starts a new server. Optionally takes a callback /// for the server to send an ARC copy of itself, to allow another process /// to poll info about the server status pub fn start(config: ServerConfig, mut info_callback: F) -> Result<(), Error> where F: FnMut(Arc), { let mut mining_config = config.mining_config.clone(); let serv = Arc::new(Server::new(config)?); 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()); } info_callback(serv.clone()); loop { thread::sleep(time::Duration::from_secs(1)); if serv.stop.load(Ordering::Relaxed) { return Ok(()); } } } /// Instantiates a new server associated with the provided future reactor. pub fn new(mut config: ServerConfig) -> Result { let stop = Arc::new(AtomicBool::new(false)); let pool_adapter = Arc::new(PoolToChainAdapter::new()); let pool_net_adapter = Arc::new(PoolToNetAdapter::new()); let tx_pool = Arc::new(RwLock::new(pool::TransactionPool::new( config.pool_config.clone(), pool_adapter.clone(), pool_net_adapter.clone(), ))); let chain_adapter = Arc::new(ChainToPoolAndNetAdapter::new(tx_pool.clone())); let genesis = match config.chain_type { global::ChainTypes::Testnet1 => genesis::genesis_testnet1(), global::ChainTypes::Testnet2 => genesis::genesis_testnet2(), global::ChainTypes::AutomatedTesting => genesis::genesis_dev(), _ => pow::mine_genesis_block(config.mining_config.clone())?, }; info!(LOGGER, "Starting server, genesis block: {}", genesis.hash(),); let shared_chain = Arc::new(chain::Chain::init( config.db_root.clone(), chain_adapter.clone(), genesis.clone(), pow::verify_size, )?); pool_adapter.set_chain(Arc::downgrade(&shared_chain)); let currently_syncing = Arc::new(AtomicBool::new(true)); let awaiting_peers = Arc::new(AtomicBool::new(false)); let net_adapter = Arc::new(NetToChainAdapter::new( currently_syncing.clone(), Arc::downgrade(&shared_chain), tx_pool.clone(), config.clone(), )); let p2p_config = config.p2p_config.clone(); let p2p_server = Arc::new(p2p::Server::new( config.db_root.clone(), config.capabilities, p2p_config, net_adapter.clone(), genesis.hash(), stop.clone(), )?); chain_adapter.init(Arc::downgrade(&p2p_server.peers)); pool_net_adapter.init(Arc::downgrade(&p2p_server.peers)); net_adapter.init(Arc::downgrade(&p2p_server.peers)); if config.seeding_type.clone() != Seeding::Programmatic { let seeder = match config.seeding_type.clone() { Seeding::None => { warn!( LOGGER, "No seed configured, will stay solo until connected to" ); seed::predefined_seeds(vec![]) } Seeding::List => seed::predefined_seeds(config.seeds.as_mut().unwrap().clone()), Seeding::WebStatic => seed::web_seeds(), _ => unreachable!(), }; seed::connect_and_monitor( p2p_server.clone(), config.capabilities, seeder, stop.clone(), ); } // Defaults to None (optional) in config file. // This translates to false here. let archive_mode = match config.archive_mode { None => false, Some(b) => b, }; // Defaults to None (optional) in config file. // This translates to false here so we do not skip by default. let skip_sync_wait = match config.skip_sync_wait { None => false, Some(b) => b, }; sync::run_sync( currently_syncing.clone(), awaiting_peers.clone(), p2p_server.peers.clone(), shared_chain.clone(), skip_sync_wait, !archive_mode, stop.clone(), ); let p2p_inner = p2p_server.clone(); let _ = thread::Builder::new() .name("p2p-server".to_string()) .spawn(move || p2p_inner.listen()); info!(LOGGER, "Starting rest apis at: {}", &config.api_http_addr); api::start_rest_apis( config.api_http_addr.clone(), Arc::downgrade(&shared_chain), Arc::downgrade(&tx_pool), Arc::downgrade(&p2p_server.peers), ); info!( LOGGER, "Starting dandelion monitor: {}", &config.api_http_addr ); dandelion_monitor::monitor_transactions( config.pool_config.clone(), tx_pool.clone(), stop.clone(), ); warn!(LOGGER, "Grin server started."); Ok(Server { config: config, p2p: p2p_server, chain: shared_chain, tx_pool: tx_pool, currently_syncing: currently_syncing, state_info: ServerStateInfo { awaiting_peers: awaiting_peers, ..Default::default() }, stop: stop, }) } /// Asks the server to connect to a peer at the provided network address. pub fn connect_peer(&self, addr: SocketAddr) -> Result<(), Error> { self.p2p.connect(&addr)?; Ok(()) } /// Number of peers pub fn peer_count(&self) -> u32 { self.p2p.peers.peer_count() } /// Start mining for blocks on a separate thread. Uses toy miner by default, /// mostly for testing, but can also load a plugin from cuckoo-miner pub fn start_miner(&self, config: pow::types::MinerConfig) { let cuckoo_size = global::sizeshift(); let proof_size = global::proofsize(); let currently_syncing = self.currently_syncing.clone(); let mut miner = miner::Miner::new( config.clone(), self.chain.clone(), self.tx_pool.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)); let _ = thread::Builder::new() .name("miner".to_string()) .spawn(move || { // TODO push this down in the run loop so miner gets paused anytime we // decide to sync again let secs_5 = time::Duration::from_secs(5); while currently_syncing.load(Ordering::Relaxed) { thread::sleep(secs_5); } miner.run_loop(config.clone(), mining_stats, cuckoo_size as u32, proof_size); }); } /// The chain head pub fn head(&self) -> chain::Tip { self.chain.head().unwrap() } /// The head of the block header chain pub fn header_head(&self) -> chain::Tip { self.chain.get_header_head().unwrap() } /// Returns a set of stats about this server. This and the ServerStats /// structure /// can be updated over time to include any information needed by tests or /// other /// consumers pub fn get_server_stats(&self) -> Result { let mining_stats = self.state_info.mining_stats.read().unwrap().clone(); let awaiting_peers = self.state_info.awaiting_peers.load(Ordering::Relaxed); // Fill out stats on our current difficulty calculation // TODO: check the overhead of calculating this again isn't too much // could return it from next_difficulty, but would rather keep consensus // code clean. This may be handy for testing but not really needed // for release let diff_stats = { let diff_iter = self.chain.difficulty_iter(); let last_blocks: Vec> = global::difficulty_data_to_vector(diff_iter) .into_iter() .skip(consensus::MEDIAN_TIME_WINDOW as usize) .take(consensus::DIFFICULTY_ADJUST_WINDOW as usize) .collect(); let mut last_time = last_blocks[0].clone().unwrap().0; let tip_height = self.chain.head().unwrap().height as i64; let earliest_block_height = tip_height as i64 - last_blocks.len() as i64; let mut i = 1; let diff_entries: Vec = last_blocks .iter() .skip(1) .map(|n| { let (time, diff) = n.clone().unwrap(); let dur = time - last_time; let height = earliest_block_height + i + 1; let index = tip_height - height; i += 1; last_time = time; DiffBlock { block_number: height, block_index: index, difficulty: diff.into_num(), time: time, duration: dur, } }) .collect(); let block_time_sum = diff_entries.iter().fold(0, |sum, t| sum + t.duration); let block_diff_sum = diff_entries.iter().fold(0, |sum, d| sum + d.difficulty); DiffStats { height: tip_height as u64, last_blocks: diff_entries, average_block_time: block_time_sum / consensus::DIFFICULTY_ADJUST_WINDOW, average_difficulty: block_diff_sum / consensus::DIFFICULTY_ADJUST_WINDOW, window_size: consensus::DIFFICULTY_ADJUST_WINDOW, } }; let peer_stats = self.p2p .peers .connected_peers() .into_iter() .map(|p| { let p = p.read().unwrap(); PeerStats::from_peer(&p) }) .collect(); Ok(ServerStats { peer_count: self.peer_count(), head: self.head(), header_head: self.header_head(), is_syncing: self.currently_syncing.load(Ordering::Relaxed), awaiting_peers: awaiting_peers, mining_stats: mining_stats, peer_stats: peer_stats, diff_stats: diff_stats, }) } /// Stop the server. pub fn stop(&self) { self.p2p.stop(); self.stop.store(true, Ordering::Relaxed); } }