// 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. extern crate grin_api as api; extern crate grin_chain as chain; extern crate grin_core as core; extern crate grin_keychain as keychain; extern crate grin_p2p as p2p; extern crate grin_servers as servers; extern crate grin_util as util; extern crate grin_wallet as wallet; mod framework; use std::default::Default; use std::sync::{Arc, Mutex}; use std::{thread, time}; use core::core::hash::Hashed; use core::global::{self, ChainTypes}; use wallet::controller; use wallet::libtx::slate::Slate; use wallet::libwallet::types::{WalletBackend, WalletClient, WalletInst}; use wallet::lmdb_wallet::LMDBBackend; use wallet::HTTPWalletClient; use wallet::WalletConfig; use framework::{ config, stop_all_servers, stratum_config, LocalServerContainerConfig, LocalServerContainerPool, LocalServerContainerPoolConfig, }; /// Testing the frameworks by starting a fresh server, creating a genesis /// Block and mining into a wallet for a bit #[test] fn basic_genesis_mine() { util::init_test_logger(); global::set_mining_mode(ChainTypes::AutomatedTesting); let test_name_dir = "genesis_mine"; framework::clean_all_output(test_name_dir); // Create a server pool let mut pool_config = LocalServerContainerPoolConfig::default(); pool_config.base_name = String::from(test_name_dir); pool_config.run_length_in_seconds = 10; pool_config.base_api_port = 30000; pool_config.base_p2p_port = 31000; pool_config.base_wallet_port = 32000; let mut pool = LocalServerContainerPool::new(pool_config); // Create a server to add into the pool let mut server_config = LocalServerContainerConfig::default(); server_config.start_miner = true; server_config.start_wallet = false; server_config.burn_mining_rewards = true; pool.create_server(&mut server_config); let servers = pool.run_all_servers(); stop_all_servers(servers); } /// Creates 5 servers, first being a seed and check that through peer address /// messages they all end up connected. #[test] fn simulate_seeding() { util::init_test_logger(); global::set_mining_mode(ChainTypes::AutomatedTesting); let test_name_dir = "simulate_seeding"; framework::clean_all_output(test_name_dir); // Create a server pool let mut pool_config = LocalServerContainerPoolConfig::default(); pool_config.base_name = String::from(test_name_dir); pool_config.run_length_in_seconds = 30; // have to use different ports because of tests being run in parallel pool_config.base_api_port = 30020; pool_config.base_p2p_port = 31020; pool_config.base_wallet_port = 32020; let mut pool = LocalServerContainerPool::new(pool_config); // Create a first seed server to add into the pool let mut server_config = LocalServerContainerConfig::default(); // server_config.start_miner = true; server_config.start_wallet = false; server_config.burn_mining_rewards = true; server_config.is_seeding = true; pool.create_server(&mut server_config); // point next servers at first seed server_config.is_seeding = false; server_config.seed_addr = String::from(format!( "{}:{}", server_config.base_addr, server_config.p2p_server_port )); for _ in 0..4 { pool.create_server(&mut server_config); } // pool.connect_all_peers(); let servers = pool.run_all_servers(); stop_all_servers(servers); } /// Create 1 server, start it mining, then connect 4 other peers mining and /// using the first /// as a seed. Meant to test the evolution of mining difficulty with miners /// running at /// different rates // Just going to comment this out as an automatically run test for the time // being, // As it's more for actively testing and hurts CI a lot //#[test] #[allow(dead_code)] fn simulate_parallel_mining() { global::set_mining_mode(ChainTypes::AutomatedTesting); let test_name_dir = "simulate_parallel_mining"; // framework::clean_all_output(test_name_dir); // Create a server pool let mut pool_config = LocalServerContainerPoolConfig::default(); pool_config.base_name = String::from(test_name_dir); pool_config.run_length_in_seconds = 60; // have to use different ports because of tests being run in parallel pool_config.base_api_port = 30040; pool_config.base_p2p_port = 31040; pool_config.base_wallet_port = 32040; let mut pool = LocalServerContainerPool::new(pool_config); // Create a first seed server to add into the pool let mut server_config = LocalServerContainerConfig::default(); server_config.start_miner = true; server_config.start_wallet = true; server_config.is_seeding = true; pool.create_server(&mut server_config); // point next servers at first seed server_config.is_seeding = false; server_config.seed_addr = String::from(format!( "{}:{}", server_config.base_addr, server_config.p2p_server_port )); // And create 4 more, then let them run for a while for i in 1..4 { // fudge in some slowdown server_config.miner_slowdown_in_millis = i * 2; pool.create_server(&mut server_config); } // pool.connect_all_peers(); let servers = pool.run_all_servers(); stop_all_servers(servers); // Check mining difficulty here?, though I'd think it's more valuable // to simply output it. Can at least see the evolution of the difficulty target // in the debug log output for now } // TODO: Convert these tests to newer framework format /// Create a network of 5 servers and mine a block, verifying that the block /// gets propagated to all. #[test] fn simulate_block_propagation() { util::init_test_logger(); // we actually set the chain_type in the ServerConfig below // TODO - avoid needing to set it in two places? global::set_mining_mode(ChainTypes::AutomatedTesting); let test_name_dir = "grin-prop"; framework::clean_all_output(test_name_dir); // instantiates 5 servers on different ports let mut servers = vec![]; for n in 0..5 { let s = servers::Server::new(framework::config(10 * n, test_name_dir, 0)).unwrap(); servers.push(s); thread::sleep(time::Duration::from_millis(100)); } // start mining servers[0].start_test_miner(None); // monitor for a change of head on a different server and check whether // chain height has changed loop { let mut count = 0; for n in 0..5 { if servers[n].head().height > 3 { count += 1; } } if count == 5 { break; } thread::sleep(time::Duration::from_millis(1_000)); } for n in 0..5 { servers[n].stop(); } } /// Creates 2 different disconnected servers, mine a few blocks on one, connect /// them and check that the 2nd gets all the blocks #[test] fn simulate_full_sync() { util::init_test_logger(); // we actually set the chain_type in the ServerConfig below global::set_mining_mode(ChainTypes::AutomatedTesting); let test_name_dir = "grin-sync"; framework::clean_all_output(test_name_dir); let s1 = servers::Server::new(framework::config(1000, "grin-sync", 1000)).unwrap(); // mine a few blocks on server 1 s1.start_test_miner(None); thread::sleep(time::Duration::from_secs(8)); let s2 = servers::Server::new(framework::config(1001, "grin-sync", 1000)).unwrap(); // Get the current header from s1. let s1_header = s1.chain.head_header().unwrap(); // Wait for s2 to sync up to and including the header from s1. while s2.head().height < s1_header.height { thread::sleep(time::Duration::from_millis(1_000)); } // Confirm both s1 and s2 see a consistent header at that height. let s2_header = s2.chain.get_block_header(&s1_header.hash()).unwrap(); assert_eq!(s1_header, s2_header); // Stop our servers cleanly. s1.stop(); s2.stop(); } /// Creates 2 different disconnected servers, mine a few blocks on one, connect /// them and check that the 2nd gets all using fast sync algo #[test] fn simulate_fast_sync() { util::init_test_logger(); // we actually set the chain_type in the ServerConfig below global::set_mining_mode(ChainTypes::AutomatedTesting); let test_name_dir = "grin-fast"; framework::clean_all_output(test_name_dir); // start s1 and mine enough blocks to get beyond the fast sync horizon let s1 = servers::Server::new(framework::config(2000, "grin-fast", 2000)).unwrap(); s1.start_test_miner(None); while s1.head().height < 20 { thread::sleep(time::Duration::from_millis(1_000)); } let mut conf = config(2001, "grin-fast", 2000); conf.archive_mode = Some(false); let s2 = servers::Server::new(conf).unwrap(); while s2.header_head().height < 1 { s2.ping_peers(); thread::sleep(time::Duration::from_millis(1_000)); } s1.stop_test_miner(); // Get the current header from s1. let s1_header = s1.chain.head_header().unwrap(); // Wait for s2 to sync up to and including the header from s1. while s2.head().height < s1_header.height { thread::sleep(time::Duration::from_millis(1_000)); } // Confirm both s1 and s2 see a consistent header at that height. let s2_header = s2.chain.get_block_header(&s1_header.hash()).unwrap(); assert_eq!(s1_header, s2_header); // Stop our servers cleanly. s1.stop(); s2.stop(); } // #[test] fn simulate_fast_sync_double() { util::init_test_logger(); // we actually set the chain_type in the ServerConfig below global::set_mining_mode(ChainTypes::AutomatedTesting); framework::clean_all_output("grin-double-fast1"); framework::clean_all_output("grin-double-fast2"); let s1 = servers::Server::new(framework::config(3000, "grin-double-fast1", 3000)).unwrap(); // mine a few blocks on server 1 s1.start_test_miner(None); thread::sleep(time::Duration::from_secs(8)); { let mut conf = config(3001, "grin-double-fast2", 3000); conf.archive_mode = Some(false); let s2 = servers::Server::new(conf).unwrap(); while s2.head().height != s2.header_head().height || s2.head().height < 20 { thread::sleep(time::Duration::from_millis(1000)); } s2.stop(); } // locks files don't seem to be cleaned properly until process exit std::fs::remove_file("target/tmp/grin-double-fast2/grin-sync-1001/chain/LOCK").unwrap(); std::fs::remove_file("target/tmp/grin-double-fast2/grin-sync-1001/peers/LOCK").unwrap(); thread::sleep(time::Duration::from_secs(20)); let mut conf = config(3001, "grin-double-fast2", 3000); conf.archive_mode = Some(false); let s2 = servers::Server::new(conf).unwrap(); while s2.head().height != s2.header_head().height || s2.head().height < 50 { thread::sleep(time::Duration::from_millis(1000)); } s1.stop(); s2.stop(); } pub fn create_wallet( dir: &str, client: HTTPWalletClient, ) -> Box> { let mut wallet_config = WalletConfig::default(); wallet_config.data_file_dir = String::from(dir); let _ = wallet::WalletSeed::init_file(&wallet_config); let mut wallet: LMDBBackend = LMDBBackend::new(wallet_config.clone(), "", client).unwrap_or_else(|e| { panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config) }); wallet.open_with_credentials().unwrap_or_else(|e| { panic!( "Error initializing wallet: {:?} Config: {:?}", e, wallet_config ) }); Box::new(wallet) } /// Intended to replicate https://github.com/mimblewimble/grin/issues/1325 #[ignore] #[test] fn replicate_tx_fluff_failure() { util::init_test_logger(); global::set_mining_mode(ChainTypes::UserTesting); framework::clean_all_output("tx_fluff"); // Create Wallet 1 (Mining Input) and start it listening // Wallet 1 post to another node, just for fun let client1 = HTTPWalletClient::new("http://127.0.0.1:23003"); let wallet1 = create_wallet("target/tmp/tx_fluff/wallet1", client1.clone()); let wallet1_handle = thread::spawn(move || { controller::foreign_listener(wallet1, "127.0.0.1:33000") .unwrap_or_else(|e| panic!("Error creating wallet1 listener: {:?}", e,)); }); // Create Wallet 2 (Recipient) and launch let client2 = HTTPWalletClient::new("http://127.0.0.1:23001"); let wallet2 = create_wallet("target/tmp/tx_fluff/wallet2", client2.clone()); let wallet2_handle = thread::spawn(move || { controller::foreign_listener(wallet2, "127.0.0.1:33001") .unwrap_or_else(|e| panic!("Error creating wallet2 listener: {:?}", e,)); }); // Server 1 (mines into wallet 1) let mut s1_config = framework::config(3000, "tx_fluff", 3000); s1_config.test_miner_wallet_url = Some("http://127.0.0.1:33000".to_owned()); s1_config.dandelion_config.embargo_secs = Some(10); s1_config.dandelion_config.patience_secs = Some(1); s1_config.dandelion_config.relay_secs = Some(1); let s1 = servers::Server::new(s1_config.clone()).unwrap(); // Mine off of server 1 s1.start_test_miner(s1_config.test_miner_wallet_url); thread::sleep(time::Duration::from_secs(5)); // Server 2 (another node) let mut s2_config = framework::config(3001, "tx_fluff", 3001); s2_config.p2p_config.seeds = Some(vec!["127.0.0.1:13000".to_owned()]); s2_config.dandelion_config.embargo_secs = Some(10); s2_config.dandelion_config.patience_secs = Some(1); s2_config.dandelion_config.relay_secs = Some(1); let s2 = servers::Server::new(s2_config.clone()).unwrap(); let dl_nodes = 5; for i in 0..dl_nodes { // (create some stem nodes) let mut s_config = framework::config(3002 + i, "tx_fluff", 3002 + i); s_config.p2p_config.seeds = Some(vec!["127.0.0.1:13000".to_owned()]); s_config.dandelion_config.embargo_secs = Some(10); s_config.dandelion_config.patience_secs = Some(1); s_config.dandelion_config.relay_secs = Some(1); let _ = servers::Server::new(s_config.clone()).unwrap(); } thread::sleep(time::Duration::from_secs(10)); // get another instance of wallet1 (to update contents and perform a send) let wallet1 = create_wallet("target/tmp/tx_fluff/wallet1", client1.clone()); let wallet1 = Arc::new(Mutex::new(wallet1)); let amount = 30_000_000_000; let mut slate = Slate::blank(1); wallet::controller::owner_single_use(wallet1.clone(), |api| { slate = api.issue_send_tx( amount, // amount 2, // minimum confirmations "http://127.0.0.1:33001", // dest 500, // max outputs 1000, // num change outputs true, // select all outputs ).unwrap(); api.post_tx(&slate, false).unwrap(); Ok(()) }).unwrap(); // Give some time for propagation and mining thread::sleep(time::Duration::from_secs(200)); // get another instance of wallet (to check contents) let wallet2 = create_wallet("target/tmp/tx_fluff/wallet2", client2.clone()); let wallet2 = Arc::new(Mutex::new(wallet2)); wallet::controller::owner_single_use(wallet2.clone(), |api| { let res = api.retrieve_summary_info(true).unwrap(); assert_eq!(res.1.amount_currently_spendable, amount); Ok(()) }).unwrap(); }