mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 11:31:08 +03:00
bcc8f68f52
* Fix issue where we have no metadata for a block (#938)
when restarting node before initial sync completed
* Avoid double-locking on add eviction. Fixes #936
* Fix 33c5a983
* Add support for DNS Seed (#940)
* Add support for DNS Seed
* Add port
* Add seed.grin-tech.org
* Remove duplicate IPs
* minimal stratum server
* Modifications for review comments. Move stratum test into its own file, move get_block() into its own rust module, use pool and chain only rather than the entire Miner object
* rustfmt
* cleanup
* cleanup
* Introduce extending_readonly to simplify a forcing and cancelling rollbacks (#945)
readonly views of the txhashset
* Add DNS Seed and make DNSSeed default (#942)
* Add dns seed seeding type
* Add grin-seed.owncrypto.de and make DNSSeed default
* Add email address for each DNS Seed
* [WIP] Core PMMR and API updates to support wallet restore (#950)
* update pmmr to get batch of elements by insertion position
* update pmmr to get batch of elements by insertion position
* add api + chain calls to get traversed outputs back out
* add api + chain calls to get traversed outputs back out
* first pass getting wallet restore to work again with updated utxo-walking api
* Update simulation.md
* Fix Bus Error (core dumped) when validating fast sync txhashset (#956)
This PR fixes #953 by introducing a lock for txhashet_write. It's not enough
to synchronize access to in memory data, files also needs to be protected, so
a general txhashset lock was introduced.
* refactor grin crate into separate modules (#955)
* Add total kernel offset to block api (#954)
* minimal stratum server
* Modifications for review comments. Move stratum test into its own file, move get_block() into its own rust module, use pool and chain only rather than the entire Miner object
* rustfmt
* cleanup
* cleanup
* Merge with grin_grin -> servers code reorg
* Merge with grin_grin -> servers code reorg
* add stratum server stats
594 lines
17 KiB
Rust
594 lines
17 KiB
Rust
// 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_servers as servers;
|
|
extern crate grin_keychain as keychain;
|
|
extern crate grin_p2p as p2p;
|
|
extern crate grin_pow as pow;
|
|
extern crate grin_util as util;
|
|
extern crate grin_wallet as wallet;
|
|
|
|
extern crate blake2_rfc as blake2;
|
|
|
|
use std::thread;
|
|
use std::time;
|
|
use std::default::Default;
|
|
use std::fs;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use wallet::WalletConfig;
|
|
|
|
/// Just removes all results from previous runs
|
|
pub fn clean_all_output(test_name_dir: &str) {
|
|
let target_dir = format!("target/tmp/{}", 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"),
|
|
api_server_port: 13413,
|
|
p2p_server_port: 13414,
|
|
wallet_port: 13415,
|
|
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<servers::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
|
|
pub working_dir: String,
|
|
|
|
// Wallet configuration
|
|
pub wallet_config: WalletConfig,
|
|
}
|
|
|
|
impl LocalServerContainer {
|
|
/// Create a new local server container with defaults, with the given name
|
|
/// all related files will be created in the directory
|
|
/// target/tmp/test_servers/{name}
|
|
|
|
pub fn new(config: LocalServerContainerConfig) -> Result<LocalServerContainer, Error> {
|
|
let working_dir = format!("target/tmp/test_servers/{}", config.name);
|
|
let mut wallet_config = WalletConfig::default();
|
|
|
|
wallet_config.api_listen_port = config.wallet_port;
|
|
wallet_config.check_node_api_http_addr = config.wallet_validating_node_url.clone();
|
|
wallet_config.data_file_dir = working_dir.clone();
|
|
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(),
|
|
wallet_config: wallet_config,
|
|
})
|
|
}
|
|
|
|
pub fn run_server(&mut self, duration_in_seconds: u64) -> servers::ServerStats {
|
|
let api_addr = format!("{}:{}", self.config.base_addr, self.config.api_server_port);
|
|
|
|
let mut seeding_type = servers::Seeding::None;
|
|
let mut seeds = Vec::new();
|
|
|
|
if self.config.seed_addr.len() > 0 {
|
|
seeding_type = servers::Seeding::List;
|
|
seeds = vec![self.config.seed_addr.to_string()];
|
|
}
|
|
|
|
let mut plugin_config = pow::types::CuckooMinerPluginConfig::default();
|
|
let mut plugin_config_vec: Vec<pow::types::CuckooMinerPluginConfig> = Vec::new();
|
|
plugin_config.type_filter = String::from("mean_cpu");
|
|
plugin_config_vec.push(plugin_config);
|
|
|
|
let miner_config = pow::types::MinerConfig {
|
|
enable_mining: self.config.start_miner,
|
|
burn_reward: self.config.burn_mining_rewards,
|
|
miner_async_mode: Some(false),
|
|
miner_plugin_dir: None,
|
|
miner_plugin_config: Some(plugin_config_vec),
|
|
wallet_listener_url: self.config.coinbase_wallet_address.clone(),
|
|
slow_down_in_millis: Some(self.config.miner_slowdown_in_millis.clone()),
|
|
..Default::default()
|
|
};
|
|
|
|
let s = servers::Server::new(servers::ServerConfig {
|
|
api_http_addr: api_addr,
|
|
db_root: format!("{}/.grin", self.working_dir),
|
|
p2p_config: p2p::P2PConfig {
|
|
port: self.config.p2p_server_port,
|
|
..p2p::P2PConfig::default()
|
|
},
|
|
seeds: Some(seeds),
|
|
seeding_type: seeding_type,
|
|
chain_type: core::global::ChainTypes::AutomatedTesting,
|
|
skip_sync_wait: Some(true),
|
|
mining_config: Some(miner_config.clone()),
|
|
..Default::default()
|
|
}).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));
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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_mills: 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());
|
|
|
|
println!(
|
|
"Starting the Grin wallet receiving daemon on {} ",
|
|
self.config.wallet_port
|
|
);
|
|
|
|
self.wallet_config = WalletConfig::default();
|
|
|
|
self.wallet_config.api_listen_port = self.config.wallet_port;
|
|
self.wallet_config.check_node_api_http_addr =
|
|
self.config.wallet_validating_node_url.clone();
|
|
self.wallet_config.data_file_dir = self.working_dir.clone();
|
|
|
|
let _ = fs::create_dir_all(self.wallet_config.clone().data_file_dir);
|
|
wallet::WalletSeed::init_file(&self.wallet_config).unwrap();
|
|
|
|
let wallet_seed = wallet::WalletSeed::from_file(&self.wallet_config)
|
|
.expect("Failed to read wallet seed file.");
|
|
|
|
let keychain = wallet_seed
|
|
.derive_keychain("grin_test")
|
|
.expect("Failed to derive keychain from seed file and passphrase.");
|
|
|
|
wallet::server::start_rest_apis(self.wallet_config.clone(), keychain);
|
|
self.wallet_is_running = true;
|
|
}
|
|
|
|
pub fn get_wallet_seed(config: &WalletConfig) -> wallet::WalletSeed {
|
|
let _ = fs::create_dir_all(config.clone().data_file_dir);
|
|
wallet::WalletSeed::init_file(config).unwrap();
|
|
let wallet_seed =
|
|
wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file.");
|
|
wallet_seed
|
|
}
|
|
|
|
pub fn get_wallet_info(
|
|
config: &WalletConfig,
|
|
wallet_seed: &wallet::WalletSeed,
|
|
) -> wallet::WalletInfo {
|
|
let keychain = wallet_seed
|
|
.derive_keychain("grin_test")
|
|
.expect("Failed to derive keychain from seed file and passphrase.");
|
|
|
|
wallet::retrieve_info(config, &keychain)
|
|
}
|
|
|
|
pub fn send_amount_to(
|
|
config: &WalletConfig,
|
|
amount: &str,
|
|
minimum_confirmations: u64,
|
|
selection_strategy: &str,
|
|
dest: &str,
|
|
fluff: bool,
|
|
) {
|
|
let amount = core::core::amount_from_hr_string(amount)
|
|
.expect("Could not parse amount as a number with optional decimal point.");
|
|
|
|
let wallet_seed =
|
|
wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file.");
|
|
|
|
let mut keychain = wallet_seed
|
|
.derive_keychain("grin_test")
|
|
.expect("Failed to derive keychain from seed file and passphrase.");
|
|
let max_outputs = 500;
|
|
let result = wallet::issue_send_tx(
|
|
config,
|
|
&mut keychain,
|
|
amount,
|
|
minimum_confirmations,
|
|
dest.to_string(),
|
|
max_outputs,
|
|
selection_strategy == "all",
|
|
fluff,
|
|
);
|
|
match result {
|
|
Ok(_) => println!(
|
|
"Tx sent: {} grin to {} (strategy '{}')",
|
|
core::core::amount_to_hr_string(amount),
|
|
dest,
|
|
selection_strategy,
|
|
),
|
|
Err(e) => match e.kind() {
|
|
wallet::ErrorKind::NotEnoughFunds(available) => {
|
|
println!(
|
|
"Tx not sent: insufficient funds (max: {})",
|
|
core::core::amount_to_hr_string(available),
|
|
);
|
|
}
|
|
_ => {
|
|
println!("Tx not sent to {}: {:?}", dest, e);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
/// Stops the running wallet server
|
|
pub fn stop_wallet(&mut self) {
|
|
println!("Stop wallet!");
|
|
let 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<servers::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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create and return a ServerConfig
|
|
pub fn config(n: u16, test_name_dir: &str, seed_n: u16) -> servers::ServerConfig {
|
|
servers::ServerConfig {
|
|
api_http_addr: format!("127.0.0.1:{}", 20000 + n),
|
|
db_root: format!("target/tmp/{}/grin-sync-{}", test_name_dir, n),
|
|
p2p_config: p2p::P2PConfig {
|
|
port: 10000 + n,
|
|
..p2p::P2PConfig::default()
|
|
},
|
|
seeding_type: servers::Seeding::List,
|
|
seeds: Some(vec![format!("127.0.0.1:{}", 10000 + seed_n)]),
|
|
chain_type: core::global::ChainTypes::AutomatedTesting,
|
|
archive_mode: Some(true),
|
|
skip_sync_wait: Some(true),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create and return a MinerConfig
|
|
pub fn miner_config() -> pow::types::MinerConfig {
|
|
let mut plugin_config = pow::types::CuckooMinerPluginConfig::default();
|
|
let mut plugin_config_vec: Vec<pow::types::CuckooMinerPluginConfig> = Vec::new();
|
|
plugin_config.type_filter = String::from("mean_cpu");
|
|
plugin_config_vec.push(plugin_config);
|
|
|
|
pow::types::MinerConfig {
|
|
enable_mining: true,
|
|
burn_reward: true,
|
|
miner_async_mode: Some(false),
|
|
miner_plugin_dir: None,
|
|
miner_plugin_config: Some(plugin_config_vec),
|
|
..Default::default()
|
|
}
|
|
}
|