Files
grin-node/grin/tests/framework.rs
T
Yeastplume 045f5bb4da POW refactor (#100)
* Adding output stats from plugin, when available
* adding grin config
* moving pow crate from core/pow to it's own crate
* moving POW/mining stuff into pow module, and removing mining dependency from chain
* refactored most of mining into pow module...miner.rs still in server
* update grin tests
* updating genesis block mining to use configured miner, if available
* chain tests back into chain, done with pow refactor
* use tag of cuckoo-miner for pr
2017-08-22 18:23:54 +00:00

518 lines
16 KiB
Rust

// Copyright 2017 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_grin as grin;
extern crate grin_core as core;
extern crate grin_p2p as p2p;
extern crate grin_chain as chain;
extern crate grin_api as api;
extern crate grin_wallet as wallet;
extern crate grin_pow as pow;
extern crate secp256k1zkp as secp;
extern crate blake2_rfc as blake2;
extern crate env_logger;
extern crate futures;
extern crate tokio_core;
extern crate tokio_timer;
extern crate futures_cpupool;
use std::thread;
use std::time;
use std::default::Default;
use std::fs;
use std::sync::{Arc, Mutex};
use tokio_core::reactor;
use tokio_timer::Timer;
use secp::Secp256k1;
use wallet::WalletConfig;
/// Just removes all results from previous runs
pub fn clean_all_output(test_name_dir:&str){
let target_dir = format!("target/test_servers/{}", test_name_dir);
let result = fs::remove_dir_all(target_dir);
if let Err(e) = result {
println!("{}",e);
}
}
/// Errors that can be returned by LocalServerContainer
#[derive(Debug)]
#[allow(dead_code)]
pub enum Error {
Internal(String),
Argument(String),
NotFound,
}
/// All-in-one server configuration struct, for convenience
///
#[derive(Clone)]
pub struct LocalServerContainerConfig {
//user friendly name for the server, also denotes what dir
//the data files will appear in
pub name: String,
//Base IP address
pub base_addr: String,
//Port the server (p2p) is running on
pub p2p_server_port: u16,
//Port the API server is running on
pub api_server_port: u16,
//Port the wallet server is running on
pub wallet_port: u16,
//Whether we're going to mine
pub start_miner: bool,
//time in millis by which to artifically slow down the mining loop
//in this container
pub miner_slowdown_in_millis: u64,
//Whether we're going to run a wallet as well,
//can use same server instance as a validating node for convenience
pub start_wallet: bool,
//address of a server to use as a seed
pub seed_addr: String,
//keep track of whether this server is supposed to be seeding
pub is_seeding: bool,
//Whether to burn mining rewards
pub burn_mining_rewards: bool,
//full address to send coinbase rewards to
pub coinbase_wallet_address: String,
//When running a wallet, the address to check inputs and send
//finalised transactions to,
pub wallet_validating_node_url:String,
}
/// Default server config
impl Default for LocalServerContainerConfig {
fn default() -> LocalServerContainerConfig {
LocalServerContainerConfig {
name: String::from("test_host"),
base_addr: String::from("127.0.0.1"),
p2p_server_port: 13414,
api_server_port: 13415,
wallet_port: 13416,
seed_addr: String::from(""),
is_seeding: false,
start_miner: false,
start_wallet: false,
burn_mining_rewards: false,
coinbase_wallet_address: String::from(""),
wallet_validating_node_url: String::from(""),
miner_slowdown_in_millis: 0,
}
}
}
/// A top-level container to hold everything that might be running
/// on a server, i.e. server, wallet in send or receive mode
pub struct LocalServerContainer {
//Configuration
config: LocalServerContainerConfig,
//Structure of references to the
//internal server data
pub p2p_server_stats: Option<grin::ServerStats>,
//The API server instance
api_server: Option<api::ApiServer>,
//whether the server is running
pub server_is_running: bool,
//Whether the server is mining
pub server_is_mining: bool,
//Whether the server is also running a wallet
//Not used if running wallet without server
pub wallet_is_running: bool,
//the list of peers to connect to
pub peer_list: Vec<String>,
//base directory for the server instance
working_dir: String,
}
impl LocalServerContainer {
/// Create a new local server container with defaults, with the given name
/// all related files will be created in the directory target/test_servers/{name}
pub fn new(config:LocalServerContainerConfig) -> Result<LocalServerContainer, Error> {
let working_dir = format!("target/test_servers/{}", config.name);
Ok((LocalServerContainer {
config:config,
p2p_server_stats: None,
api_server: None,
server_is_running: false,
server_is_mining: false,
wallet_is_running: false,
working_dir: working_dir,
peer_list: Vec::new(),
}))
}
pub fn run_server(&mut self, duration_in_seconds: u64) -> grin::ServerStats
{
let mut event_loop = reactor::Core::new().unwrap();
let api_addr = format!("{}:{}", self.config.base_addr, self.config.api_server_port);
let mut seeding_type=grin::Seeding::None;
let mut seeds=Vec::new();
if self.config.seed_addr.len()>0{
seeding_type=grin::Seeding::List;
seeds=vec![self.config.seed_addr.to_string()];
}
let s = grin::Server::future(
grin::ServerConfig{
api_http_addr: api_addr,
db_root: format!("{}/.grin", self.working_dir),
p2p_config: Some(p2p::P2PConfig{port: self.config.p2p_server_port, ..p2p::P2PConfig::default()}),
seeds: Some(seeds),
seeding_type: seeding_type,
..Default::default()
}, &event_loop.handle()).unwrap();
self.p2p_server_stats = Some(s.get_server_stats().unwrap());
if self.config.start_wallet == true{
self.run_wallet(duration_in_seconds+5);
//give a second to start wallet before continuing
thread::sleep(time::Duration::from_millis(1000));
}
let miner_config = pow::types::MinerConfig {
enable_mining: self.config.start_miner,
burn_reward: self.config.burn_mining_rewards,
use_cuckoo_miner: false,
cuckoo_miner_async_mode: Some(false),
cuckoo_miner_plugin_dir: Some(String::from("../target/debug/deps")),
cuckoo_miner_plugin_type: Some(String::from("simple")),
wallet_receiver_url : self.config.coinbase_wallet_address.clone(),
slow_down_in_millis: Some(self.config.miner_slowdown_in_millis.clone()),
..Default::default()
};
if self.config.start_miner == true {
println!("starting Miner on port {}", self.config.p2p_server_port);
s.start_miner(miner_config);
}
for p in &mut self.peer_list {
println!("{} connecting to peer: {}", self.config.p2p_server_port, p);
s.connect_peer(p.parse().unwrap()).unwrap();
}
let timeout = Timer::default().sleep(time::Duration::from_secs(duration_in_seconds));
event_loop.run(timeout).unwrap();
if self.wallet_is_running{
self.stop_wallet();
}
s.get_server_stats().unwrap()
}
/// Starts a wallet daemon to receive and returns the
/// listening server url
pub fn run_wallet(&mut self, _duration_in_seconds: u64) {
//URL on which to start the wallet listener (i.e. api server)
let url = format!("{}:{}", self.config.base_addr, self.config.wallet_port);
//Just use the name of the server for a seed for now
let seed = format!("{}", self.config.name);
let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes());
let s = Secp256k1::new();
let key = wallet::ExtendedKey::from_seed(&s, seed.as_bytes())
.expect("Error deriving extended key from seed.");
println!("Starting the Grin wallet receiving daemon on {} ", self.config.wallet_port );
let mut wallet_config = WalletConfig::default();
wallet_config.api_http_addr = format!("http://{}", url);
wallet_config.check_node_api_http_addr = self.config.wallet_validating_node_url.clone();
wallet_config.data_file_dir=self.working_dir.clone();
let mut api_server = api::ApiServer::new("/v1".to_string());
api_server.register_endpoint("/receive".to_string(), wallet::WalletReceiver {
key: key,
config: wallet_config,
});
api_server.start(url).unwrap_or_else(|e| {
println!("Failed to start Grin wallet receiver: {}.", e);
});
self.api_server = Some(api_server);
self.wallet_is_running = true;
}
/// Stops the running wallet server
pub fn stop_wallet(&mut self){
let mut api_server = self.api_server.as_mut().unwrap();
api_server.stop();
}
/// Adds a peer to this server to connect to upon running
pub fn add_peer(&mut self, addr:String){
self.peer_list.push(addr);
}
}
/// Configuration values for container pool
pub struct LocalServerContainerPoolConfig {
//Base name to append to all the servers in this pool
pub base_name: String,
//Base http address for all of the servers in this pool
pub base_http_addr: String,
//Base port server for all of the servers in this pool
//Increment the number by 1 for each new server
pub base_p2p_port: u16,
//Base api port for all of the servers in this pool
//Increment this number by 1 for each new server
pub base_api_port: u16,
//Base wallet port for this server
//
pub base_wallet_port: u16,
//How long the servers in the pool are going to run
pub run_length_in_seconds: u64,
}
/// Default server config
///
impl Default for LocalServerContainerPoolConfig {
fn default() -> LocalServerContainerPoolConfig {
LocalServerContainerPoolConfig {
base_name: String::from("test_pool"),
base_http_addr: String::from("127.0.0.1"),
base_p2p_port: 10000,
base_api_port: 11000,
base_wallet_port: 12000,
run_length_in_seconds: 30,
}
}
}
/// A convenience pool for running many servers simultaneously
/// without necessarily having to configure each one manually
pub struct LocalServerContainerPool {
//configuration
pub config: LocalServerContainerPoolConfig,
//keep ahold of all the created servers thread-safely
server_containers: Vec<LocalServerContainer>,
//Keep track of what the last ports a server was opened on
next_p2p_port: u16,
next_api_port: u16,
next_wallet_port: u16,
//keep track of whether a seed exists, and pause a bit if so
is_seeding: bool,
}
impl LocalServerContainerPool {
pub fn new(config: LocalServerContainerPoolConfig)->LocalServerContainerPool{
(LocalServerContainerPool{
next_api_port: config.base_api_port,
next_p2p_port: config.base_p2p_port,
next_wallet_port: config.base_wallet_port,
config: config,
server_containers: Vec::new(),
is_seeding: false,
})
}
/// adds a single server on the next available port
/// overriding passed-in values as necessary. Config object is an OUT value with
/// ports/addresses filled in
///
pub fn create_server(&mut self, server_config:&mut LocalServerContainerConfig)
{
//If we're calling it this way, need to override these
server_config.p2p_server_port=self.next_p2p_port;
server_config.api_server_port=self.next_api_port;
server_config.wallet_port=self.next_wallet_port;
server_config.name=String::from(format!("{}/{}-{}",
self.config.base_name,
self.config.base_name,
server_config.p2p_server_port));
//Use self as coinbase wallet
server_config.coinbase_wallet_address=String::from(format!("http://{}:{}",
server_config.base_addr,
server_config.wallet_port));
self.next_p2p_port+=1;
self.next_api_port+=1;
self.next_wallet_port+=1;
if server_config.is_seeding {
self.is_seeding=true;
}
let _server_address = format!("{}:{}",
server_config.base_addr,
server_config.p2p_server_port);
let server_container = LocalServerContainer::new(server_config.clone()).unwrap();
//self.server_containers.push(server_arc);
//Create a future that runs the server for however many seconds
//collect them all and run them in the run_all_servers
let _run_time = self.config.run_length_in_seconds;
self.server_containers.push(server_container);
}
/// adds n servers, ready to run
///
///
#[allow(dead_code)]
pub fn create_servers(&mut self, number: u16){
for _ in 0..number {
//self.create_server();
}
}
/// runs all servers, and returns a vector of references to the servers
/// once they've all been run
///
pub fn run_all_servers(self) -> Vec<grin::ServerStats>{
let run_length = self.config.run_length_in_seconds;
let mut handles = vec![];
// return handles to all of the servers, wrapped in mutexes, handles, etc
let return_containers = Arc::new(Mutex::new(Vec::new()));
let is_seeding = self.is_seeding.clone();
for mut s in self.server_containers {
let return_container_ref = return_containers.clone();
let handle=thread::spawn(move || {
if is_seeding && !s.config.is_seeding {
//there's a seed and we're not it, so hang around longer and give the seed
//a chance to start
thread::sleep(time::Duration::from_millis(2000));
}
let server_ref=s.run_server(run_length);
return_container_ref.lock().unwrap().push(server_ref);
});
//Not a big fan of sleeping hack here, but there appears to be a
//concurrency issue when creating files in rocksdb that causes
//failure if we don't pause a bit before starting the next server
thread::sleep(time::Duration::from_millis(500));
handles.push(handle);
}
for handle in handles {
match handle.join() {
Ok(_) => {}
Err(e) => {
println!("Error starting server thread: {:?}", e);
panic!(e);
}
}
}
//return a much simplified version of the results
let return_vec=return_containers.lock().unwrap();
return_vec.clone()
}
pub fn connect_all_peers(&mut self){
/// just pull out all currently active servers, build a list,
/// and feed into all servers
let mut server_addresses:Vec<String> = Vec::new();
for s in &self.server_containers {
let server_address = format!("{}:{}",
s.config.base_addr,
s.config.p2p_server_port);
server_addresses.push(server_address);
}
for a in server_addresses {
for s in &mut self.server_containers {
if format!("{}:{}", s.config.base_addr,
s.config.p2p_server_port) != a {
s.add_peer(a.clone());
}
}
}
}
}