mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
Wallet HTTP posting, fixes and additions to port address configurability. (#66)
* Starting to refactor test, adding http post to wallet sender * Implemented ability to run servers on different ports (mostly for testing), and implemented ability to post http requests directly to receiving wallets * Adding detailed instructions on running multiple servers on the same machine * Changes to build.doc to outline server running process * Removed unwanted debug statements * WIP Local server testing framework evolution * More refactoring of server pool, checked in because there's a problem * Added server reference structure, and ability to return server references from tests cleanly * Added simulate_parallel_mining test, which puts some artificial slowdown into test mining loops, and the difficulty can currently be watched in the log * Removing the ServerRef structure placed in earlier and replaced with a ServerStats structure, that just returns relevant info about the Server state without exposing it to the world
This commit is contained in:
parent
75f19ecfab
commit
5f8a0d9f1c
14 changed files with 918 additions and 331 deletions
75
doc/build.md
75
doc/build.md
|
@ -17,16 +17,79 @@ https://www.rust-lang.org
|
|||
cd grin
|
||||
cargo build
|
||||
|
||||
## Exec Grin
|
||||
After compiling you'll get a binary at target/debug/grin. Place that in your path. Running 'grin help' should print a helpful message.
|
||||
|
||||
After compiling you'll get a binary at target/debug/grin. Place that in your path. Running 'grin help' should print a helpful message. Then create 3 directories for each server (the .grin db dir is created wherever you run the command from for now). Run the first server:
|
||||
## Basic Execution
|
||||
|
||||
serv1$ RUST_LOG#info grin server run --mine run
|
||||
For a basic example simulating a single node network, create a directory called 'node1' and change your working directory to it. You'll use this directory to run a wallet and create a new blockchain via a server running in mining mode.
|
||||
|
||||
Let it find a few blocks. Then open a new terminal in the directory for the 2nd server and run (check 'grin help server' for the options):
|
||||
Before running your mining server, a wallet server needs to be set up and listening so that the mining server knows where to send mining rewards. Do this from the first node directory with the following command:
|
||||
|
||||
serv2$ RUST_LOG#info grin server -p 13424 --seed "127.0.0.1:13414" run
|
||||
node1$ RUST_LOG=grin=info grin wallet -p "password" receive
|
||||
|
||||
(Note you can substitute 'RUST_LOG=grin=debug' if you'd like to see more detailed debug output.) This will create a wallet server listening on the default port 13415 with the password "password". Next, in another terminal window in the 'node1' directory, run a full mining node with the following command:
|
||||
|
||||
node1$ RUST_LOG=grin=info grin server -m run
|
||||
|
||||
This creates a new .grin database directory in the current directory, and begins mining new blocks (with no transactions, for now). Note this starts two services listening on two default ports,
|
||||
port 13414 for the peer-to-peer (P2P) service which keeps all nodes synchronised, and 13415 for the Rest API service used to verify transactions and post new transactions to the pool (for example). These ports can be configured via command line arguments.
|
||||
|
||||
Let the mining server find a few blocks, then stop (just ctrl-c) the mining server and the wallet server. You'll notice grin has created a database directory (.grin) in which the blockchain and peer data is stored. There should also be a wallet.dat file in the current directory, which contains a few coinbase mining rewards created each time the server mines a new block.
|
||||
|
||||
## Advanced Example
|
||||
|
||||
The following outlines a more advanced example simulating a multi-server network with transactions being posted.
|
||||
|
||||
For the sake of example, we're going to run three nodes with varying setups. Create two more directories beside your node1 directory, called node2 and node3. If you want to clear data from your previous run (or anytime you want to reset the blockchain and all peer data,) just delete the wallet.dat file in the server1 directory and run rm -rf .grin to remove grin's database.
|
||||
|
||||
### Node 1: Genesis and Miner
|
||||
|
||||
As before, node 1 will create the blockchain and begin mining. As we'll be running many servers from the same machine, we'll configure specific ports for other servers to explicitly connect to.
|
||||
|
||||
First, we run a wallet server to recieve rewards on port 15000 (we'll log in debug mode for more information about what's happening)
|
||||
|
||||
node1$ RUST_LOG=grin=debug grin wallet -p "password" -r 15000 receive
|
||||
|
||||
Then we start node 1 mining with its P2P server bound to port 10000 and its api server at 10001. We also provide our wallet address where we'll recieve mining rewards. In another terminal:
|
||||
|
||||
node1$ RUST_LOG=grin=debug grin server -m -p 10000 -a 10001 -w "http://127.0.0.1:15000" run
|
||||
|
||||
### Node 2: Regular Node (not mining)
|
||||
|
||||
We'll set up Node 2 as a simple validating node (i.e. it won't mine,) but we'll pass in the address of node 1 as a seed. Node 2 will join the network founded by node 1 and then sync its blockchain and peer data.
|
||||
|
||||
In a new terminal, tell node 2 to run a sever using node 1's P2P address as a seed. Node 2's P2P server will run on port 20000 and its API server will run on port 20001.
|
||||
|
||||
node2$ RUST_LOG=grin=debug grin server -s "127.0.0.1:10000" -p 20000 -a 20001 run
|
||||
|
||||
Node 2 will then sync and process and validate new blocks that node 1 may find.
|
||||
|
||||
### Node 3: Regular node running wallet listener
|
||||
|
||||
Similar to Node 2, we'll set up node 3 as a non-mining node seeded with node 2 (node 1 could also be used). However, we'll also run another wallet in listener mode on this node:
|
||||
|
||||
node3$ RUST_LOG=grin=debug grin server -s "127.0.0.1:20000" -p 30000 -a 30001 run
|
||||
|
||||
Node 3 is now running it's P2P service on port 30000 and its API server on 30001. You should be able to see it syncing its blockchain and peer data with nodes 1 and 2. Now start up a wallet listener.
|
||||
|
||||
node3$ RUST_LOG=grin=debug grin wallet -p "password" -a "http://127.0.0.1:10001" -r 35000 receive
|
||||
|
||||
In contrast to other blockchains, a feature of a MimbleWimble is that a transaction cannot just be directly posted to the blockchain. It first needs to be sent from the sender to the receiver,
|
||||
who will add a blinding factor before posting it to the blockchain. The above command tells the wallet server to listen for transactions on port 35000, and, after applying it's own blinding factor to the transaction, forward them on to the listening API server on node 1. (NB: we should theoretically be able to post transactions to node 3 or 2, but for some reason transactions posted to peers don't seem to propagate properly at present)
|
||||
|
||||
### Node 1 - Send money to node 3
|
||||
|
||||
With all of your severs happily running and your terminals scrolling away, let's spend some of the coins mined in node 1 by sending them to node 3's listening wallet.
|
||||
|
||||
In yet another terminal in node 1's directory, create a new partial transaction spending 20000 coins and send them on to node 3's wallet listener. We'll also specify that we'll
|
||||
use node 2's API listener to validate our transaction inputs before sending:
|
||||
|
||||
node1$ RUST_LOG=grin=debug grin wallet -p "password" -a "http://127.0.0.1:20001" send 20000 -d "http://127.0.0.1:35000"
|
||||
|
||||
Your terminal windows should all light up now. Node 1 will check its inputs against node 2, and then send a partial transaction to node 3's wallet listener. Node 3 has been configured to
|
||||
send signed and finalised transactions to the api listener on node 1, which should then add the transaction to the next block and validate it via mining.
|
||||
|
||||
You can feel free to try any number of permutations or combinations of the above, just note that grin is very new and under active development, so your mileage may vary.
|
||||
|
||||
You'll need to give it a little bit, as it hangs for 10 sec trying to get more peers before deciding it'll only get one. Then it will sync and process and validate new blocks that serv1 may find. The "coinbase" for each block is generated with a random private key right now. You can then run a 3rd server, seeding either with the 1st or 2nd and it should connect to both and sync as well.
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ extern crate grin_p2p as p2p;
|
|||
extern crate grin_pool as pool;
|
||||
extern crate grin_store as store;
|
||||
extern crate grin_util as util;
|
||||
extern crate grin_wallet as wallet;
|
||||
extern crate secp256k1zkp as secp;
|
||||
|
||||
mod adapters;
|
||||
|
@ -52,5 +53,5 @@ mod seed;
|
|||
mod sync;
|
||||
mod types;
|
||||
|
||||
pub use server::Server;
|
||||
pub use types::{ServerConfig, MinerConfig, Seeding};
|
||||
pub use server::{Server};
|
||||
pub use types::{ServerConfig, MinerConfig, Seeding, ServerStats};
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
use rand::{self, Rng};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use std;
|
||||
use time;
|
||||
|
||||
use adapters::{ChainToPoolAndNetAdapter, PoolToChainAdapter};
|
||||
|
@ -31,6 +33,7 @@ use secp;
|
|||
use pool;
|
||||
use types::{MinerConfig, Error};
|
||||
use util;
|
||||
use wallet::{CbAmount, WalletReceiveRequest, CbData};
|
||||
|
||||
// Max number of transactions this miner will assemble in a block
|
||||
const MAX_TX: u32 = 5000;
|
||||
|
@ -86,6 +89,10 @@ impl Miner {
|
|||
latest_hash,
|
||||
b.header.difficulty);
|
||||
let mut iter_count = 0;
|
||||
if self.config.slow_down_in_millis > 0 {
|
||||
debug!("Artifically slowing down loop by {}ms per iteration.",
|
||||
self.config.slow_down_in_millis);
|
||||
}
|
||||
while head.hash() == latest_hash && time::get_time().sec < deadline {
|
||||
let pow_hash = b.hash();
|
||||
let mut miner =
|
||||
|
@ -101,6 +108,11 @@ impl Miner {
|
|||
latest_hash = self.chain_head.lock().unwrap().last_block_h;
|
||||
}
|
||||
iter_count += 1;
|
||||
|
||||
//Artifical slow down
|
||||
if self.config.slow_down_in_millis > 0 {
|
||||
thread::sleep(std::time::Duration::from_millis(2000));
|
||||
}
|
||||
}
|
||||
|
||||
// if we found a solution, push our block out
|
||||
|
@ -174,8 +186,9 @@ impl Miner {
|
|||
} else {
|
||||
let url = format!("{}/v1/receive/coinbase",
|
||||
self.config.wallet_receiver_url.as_str());
|
||||
let request = WalletReceiveRequest::Coinbase(CbAmount{amount: consensus::REWARD});
|
||||
let res: CbData = api::client::post(url.as_str(),
|
||||
&CbAmount { amount: consensus::REWARD })
|
||||
&request)
|
||||
.expect("Wallet receiver unreachable, could not claim reward. Is it running?");
|
||||
let out_bin = util::from_hex(res.output).unwrap();
|
||||
let kern_bin = util::from_hex(res.kernel).unwrap();
|
||||
|
@ -186,14 +199,3 @@ impl Miner {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct CbAmount {
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct CbData {
|
||||
output: String,
|
||||
kernel: String,
|
||||
}
|
||||
|
|
|
@ -120,6 +120,8 @@ impl Server {
|
|||
|
||||
evt_handle.spawn(server.start(evt_handle.clone()).map_err(|_| ()));
|
||||
|
||||
info!("Starting rest apis at: {}", &config.api_http_addr);
|
||||
|
||||
api::start_rest_apis(config.api_http_addr.clone(),
|
||||
chain_store.clone(),
|
||||
shared_head.clone(),
|
||||
|
@ -166,6 +168,17 @@ impl Server {
|
|||
let h = head.lock().unwrap();
|
||||
h.clone()
|
||||
}
|
||||
|
||||
/// 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<ServerStats, Error>{
|
||||
Ok(ServerStats{
|
||||
peer_count: self.peer_count(),
|
||||
head: self.head(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create the chain storage and check if it already has a
|
||||
|
|
|
@ -107,8 +107,13 @@ pub struct MinerConfig {
|
|||
/// wallet receiver. Mostly used for tests.
|
||||
pub burn_reward: bool,
|
||||
|
||||
/// a testing attribute for the time being that artifically slows down the
|
||||
/// mining loop by adding a sleep to the thread
|
||||
pub slow_down_in_millis: u64,
|
||||
|
||||
/// Size of Cuckoo Cycle to mine on
|
||||
pub cuckoo_size: u32,
|
||||
|
||||
}
|
||||
|
||||
impl Default for ServerConfig {
|
||||
|
@ -131,7 +136,20 @@ impl Default for MinerConfig {
|
|||
enable_mining: false,
|
||||
wallet_receiver_url: "http://localhost:13416".to_string(),
|
||||
burn_reward: false,
|
||||
slow_down_in_millis: 0,
|
||||
cuckoo_size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe container to return all sever related stats that other
|
||||
/// consumers might be interested in, such as test results
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerStats {
|
||||
pub peer_count:u32,
|
||||
pub head: chain::Tip,
|
||||
}
|
||||
|
|
526
grin/tests/framework.rs
Normal file
526
grin/tests/framework.rs
Normal file
|
@ -0,0 +1,526 @@
|
|||
// 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 secp256k1zkp as secp;
|
||||
extern crate tiny_keccak;
|
||||
|
||||
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::mem;
|
||||
use std::fs;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::ops::Deref;
|
||||
|
||||
use futures::{Future};
|
||||
use futures::future::join_all;
|
||||
use futures::task::park;
|
||||
use tokio_core::reactor;
|
||||
use tokio_core::reactor::Remote;
|
||||
use tokio_core::reactor::Handle;
|
||||
use tokio_timer::Timer;
|
||||
|
||||
use secp::Secp256k1;
|
||||
use tiny_keccak::Keccak;
|
||||
|
||||
use wallet::WalletConfig;
|
||||
use core::consensus;
|
||||
|
||||
|
||||
/// Just removes all results from previous runs
|
||||
|
||||
pub fn clean_all_output(test_name_dir:&str){
|
||||
let target_dir = format!("target/{}", test_name_dir);
|
||||
fs::remove_dir_all(target_dir);
|
||||
}
|
||||
|
||||
/// Errors that can be returned by LocalServerContainer
|
||||
#[derive(Debug)]
|
||||
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,
|
||||
|
||||
//size of cuckoo graph for mining
|
||||
pub cuckoo_size: u32,
|
||||
|
||||
//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,
|
||||
cuckoo_size: consensus::TEST_SIZESHIFT as u32,
|
||||
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;
|
||||
|
||||
if self.config.seed_addr.len()>0{
|
||||
seeding_type=grin::Seeding::List(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: p2p::P2PConfig{port: self.config.p2p_server_port, ..p2p::P2PConfig::default()},
|
||||
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 mut miner_config = grin::MinerConfig {
|
||||
enable_mining: self.config.start_miner,
|
||||
burn_reward: self.config.burn_mining_rewards,
|
||||
cuckoo_size: self.config.cuckoo_size,
|
||||
wallet_receiver_url : self.config.coinbase_wallet_address.clone(),
|
||||
slow_down_in_millis: 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);
|
||||
|
||||
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 mut sha3 = Keccak::new_sha3_256();
|
||||
sha3.update(seed.as_bytes());
|
||||
let mut seed = [0; 32];
|
||||
sha3.finalize(&mut seed);
|
||||
|
||||
let s = Secp256k1::new();
|
||||
let key = wallet::ExtendedKey::from_seed(&s, &seed[..])
|
||||
.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
|
||||
if server_config.coinbase_wallet_address.len()==0 {
|
||||
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 mut 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
|
||||
///
|
||||
///
|
||||
|
||||
pub fn create_servers(&mut self, number: u16){
|
||||
for n 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(v) => {}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -26,260 +26,170 @@ extern crate futures;
|
|||
extern crate tokio_core;
|
||||
extern crate tokio_timer;
|
||||
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::fs;
|
||||
|
||||
mod framework;
|
||||
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use std::default::Default;
|
||||
use std::mem;
|
||||
|
||||
use futures::{Future, Poll, Async};
|
||||
use futures::task::park;
|
||||
use tokio_core::reactor;
|
||||
use tokio_timer::Timer;
|
||||
|
||||
use secp::Secp256k1;
|
||||
use secp::key::SecretKey;
|
||||
use tiny_keccak::Keccak;
|
||||
|
||||
use core::consensus;
|
||||
use wallet::WalletConfig;
|
||||
|
||||
use framework::{LocalServerContainer, LocalServerContainerConfig,
|
||||
LocalServerContainerPoolConfig, LocalServerContainerPool};
|
||||
|
||||
/// Errors that can be returned by LocalServerContainer
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Internal(String),
|
||||
Argument(String),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
/// A top-level container to hold everything that might be running
|
||||
/// on a server, i.e. server, wallet in send or recieve mode
|
||||
|
||||
struct LocalServerContainer {
|
||||
pub working_dir: String,
|
||||
pub server : grin::Server,
|
||||
|
||||
pub enable_mining: bool,
|
||||
pub enable_wallet: bool,
|
||||
|
||||
pub wallet_port: u16,
|
||||
wallet_is_running: bool,
|
||||
|
||||
apis: api::ApiServer,
|
||||
}
|
||||
|
||||
impl LocalServerContainer {
|
||||
pub fn new(api_addr:String, server_port: u16, event_loop: &reactor::Core) -> Result<LocalServerContainer, Error> {
|
||||
let working_dir = format!("target/test_servers/server-{}", server_port);
|
||||
let mut s = grin::Server::future(
|
||||
grin::ServerConfig{
|
||||
api_http_addr: api_addr,
|
||||
db_root: format!("{}/grin-prop", working_dir),
|
||||
p2p_config: p2p::P2PConfig{port: server_port, ..p2p::P2PConfig::default()},
|
||||
..Default::default()
|
||||
}, &event_loop.handle()).unwrap();
|
||||
Ok((LocalServerContainer {
|
||||
server: s,
|
||||
enable_mining: false,
|
||||
enable_wallet: false,
|
||||
wallet_port: 30000,
|
||||
wallet_is_running: false,
|
||||
working_dir: working_dir,
|
||||
|
||||
apis: api::ApiServer::new("/v1".to_string()),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Starts a wallet daemon to receive and returns the
|
||||
/// listening server url
|
||||
|
||||
pub fn start_wallet(&mut self) -> String {
|
||||
|
||||
//Just use the server address and port number for the wallet seed now
|
||||
let url = format!("{}:{}", self.server.config.p2p_config.host,
|
||||
self.wallet_port);
|
||||
|
||||
let mut sha3 = Keccak::new_sha3_256();
|
||||
sha3.update(url.as_bytes());
|
||||
let mut seed = [0; 32];
|
||||
sha3.finalize(&mut seed);
|
||||
|
||||
let s = Secp256k1::new();
|
||||
let key = wallet::ExtendedKey::from_seed(&s, &seed[..])
|
||||
.expect("Error deriving extended key from seed.");
|
||||
|
||||
println!("Starting the Grin wallet receiving daemon on {} ", self.wallet_port );
|
||||
|
||||
let mut wallet_config = WalletConfig::default();
|
||||
wallet_config.data_file_dir=self.working_dir.clone();
|
||||
|
||||
self.apis.register_endpoint("/receive".to_string(), wallet::WalletReceiver {
|
||||
key: key,
|
||||
config: wallet_config,
|
||||
});
|
||||
|
||||
let return_url = url.clone();
|
||||
self.apis.start(url).unwrap_or_else(|e| {
|
||||
println!("Failed to start Grin wallet receiver: {}.", e);
|
||||
});
|
||||
|
||||
self.wallet_is_running = true;
|
||||
return return_url;
|
||||
}
|
||||
|
||||
/// Stops the wallet daemon
|
||||
pub fn stop_wallet(&mut self){
|
||||
self.apis.stop();
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalServerContainerPool {
|
||||
event_loop: reactor::Core,
|
||||
|
||||
base_http_addr: String,
|
||||
base_port_server: u16,
|
||||
base_port_api: u16,
|
||||
base_port_wallet: u16,
|
||||
server_containers: Vec<LocalServerContainer>,
|
||||
}
|
||||
|
||||
impl LocalServerContainerPool {
|
||||
pub fn new() -> Result<LocalServerContainerPool, Error> {
|
||||
let servers = Vec::new();
|
||||
let mut evtlp = reactor::Core::new().unwrap();
|
||||
|
||||
Ok((LocalServerContainerPool{
|
||||
event_loop: evtlp,
|
||||
base_http_addr : String::from("0.0.0.0"),
|
||||
base_port_server: 15000,
|
||||
base_port_api: 16000,
|
||||
base_port_wallet: 17000,
|
||||
server_containers: servers,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn create_server(&mut self, enable_mining:bool, enable_wallet:bool ) {
|
||||
|
||||
let server_port = self.base_port_server+self.server_containers.len() as u16;
|
||||
let api_port = self.base_port_api+self.server_containers.len() as u16;
|
||||
|
||||
|
||||
let api_addr = format!("{}:{}", self.base_http_addr, api_port);
|
||||
|
||||
let mut server_container = LocalServerContainer::new(api_addr, server_port, &self.event_loop).unwrap();
|
||||
|
||||
server_container.enable_mining = enable_mining;
|
||||
server_container.enable_wallet = enable_wallet;
|
||||
|
||||
//if we want to start a wallet, use this port
|
||||
server_container.wallet_port = self.base_port_wallet+self.server_containers.len() as u16;
|
||||
|
||||
self.server_containers.push(server_container);
|
||||
}
|
||||
|
||||
/// Connects every server to each other as peers
|
||||
///
|
||||
|
||||
pub fn connect_all_peers(&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.server.config.p2p_config.host,
|
||||
s.server.config.p2p_config.port);
|
||||
server_addresses.push(server_address);
|
||||
}
|
||||
|
||||
for a in server_addresses {
|
||||
for s in &self.server_containers {
|
||||
if format!("{}", s.server.config.p2p_config.host) != a {
|
||||
s.server.connect_peer(a.parse().unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///Starts all servers, with or without mining
|
||||
///TODO: This should accept a closure so tests can determine what
|
||||
///to do when the run is finished
|
||||
|
||||
pub fn start_all_servers(&mut self) {
|
||||
|
||||
for s in &mut self.server_containers {
|
||||
let mut wallet_url = String::from("http://localhost:13416");
|
||||
if s.enable_wallet == true {
|
||||
wallet_url=s.start_wallet();
|
||||
//Instead of making all sorts of changes to the api server
|
||||
//to support futures, just going to pause this thread for
|
||||
//half a second for the wallet to start
|
||||
//before continuing
|
||||
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
}
|
||||
let mut miner_config = grin::MinerConfig{
|
||||
enable_mining: true,
|
||||
burn_reward: true,
|
||||
wallet_receiver_url : format!("http://{}", wallet_url),
|
||||
cuckoo_size: consensus::TEST_SIZESHIFT as u32,
|
||||
..Default::default()
|
||||
};
|
||||
if s.enable_wallet == true {
|
||||
miner_config.burn_reward = false;
|
||||
}
|
||||
if s.enable_mining == true {
|
||||
println!("starting Miner on port {}", s.server.config.p2p_config.port);
|
||||
s.server.start_miner(miner_config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//borrow copy to allow access in closure
|
||||
let mut server_containers = mem::replace(&mut self.server_containers, Vec::new());
|
||||
//let &mut server_containers = self.server_containers;
|
||||
|
||||
self.event_loop.run(Timer::default().sleep(time::Duration::from_secs(60)).and_then(|_| {
|
||||
//Stop any assocated wallet servers
|
||||
for s in &mut server_containers {
|
||||
if s.wallet_is_running{
|
||||
s.stop_wallet();
|
||||
}
|
||||
}
|
||||
//for s in &mut self.server_containers {
|
||||
// occasionally 2 peers will connect to each other at the same time
|
||||
//assert!(s.peer_count() >= 4);
|
||||
//}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Just exercises the structures above, creates 5 servers, all starting wallets,
|
||||
/// mining and connecting to each other in their own directories
|
||||
/// Testing the frameworks by starting a fresh server, creating a genesis
|
||||
/// Block and mining into a wallet for a bit
|
||||
|
||||
#[test]
|
||||
fn simulate_parallel_miners(){
|
||||
fn basic_genesis_mine(){
|
||||
env_logger::init();
|
||||
let num_servers=5;
|
||||
|
||||
let mut server_pool = LocalServerContainerPool::new().unwrap();
|
||||
for n in 0..num_servers {
|
||||
server_pool.create_server(true, true);
|
||||
|
||||
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 = 20;
|
||||
|
||||
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=true;
|
||||
|
||||
pool.create_server(&mut server_config);
|
||||
pool.run_all_servers();
|
||||
|
||||
}
|
||||
|
||||
/// Creates 5 servers, first being a seed and check that through peer address
|
||||
/// messages they all end up connected.
|
||||
|
||||
#[test]
|
||||
fn simulate_seeding () {
|
||||
env_logger::init();
|
||||
|
||||
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 select 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 = 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 i in 0..4 {
|
||||
pool.create_server(&mut server_config);
|
||||
}
|
||||
|
||||
server_pool.connect_all_peers();
|
||||
server_pool.start_all_servers();
|
||||
pool.connect_all_peers();
|
||||
|
||||
let result_vec = pool.run_all_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
|
||||
|
||||
#[test]
|
||||
fn simulate_parallel_mining(){
|
||||
env_logger::init();
|
||||
|
||||
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 = 30;
|
||||
|
||||
//have to select 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;
|
||||
|
||||
//This is the default value, can play with this here for convenience
|
||||
server_config.cuckoo_size=consensus::TEST_SIZESHIFT as u32;
|
||||
|
||||
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 0..4 {
|
||||
//fudge in some slowdown
|
||||
server_config.miner_slowdown_in_millis = i*2;
|
||||
pool.create_server(&mut server_config);
|
||||
}
|
||||
|
||||
pool.connect_all_peers();
|
||||
|
||||
let result_vec=pool.run_all_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() {
|
||||
env_logger::init();
|
||||
|
||||
let test_name_dir="test_servers/grin-prop";
|
||||
framework::clean_all_output(test_name_dir);
|
||||
|
||||
let mut evtlp = reactor::Core::new().unwrap();
|
||||
let handle = evtlp.handle();
|
||||
|
||||
|
@ -296,7 +206,7 @@ fn simulate_block_propagation() {
|
|||
let s = grin::Server::future(
|
||||
grin::ServerConfig{
|
||||
api_http_addr: format!("127.0.0.1:{}", 20000+n),
|
||||
db_root: format!("target/grin-prop-{}", n),
|
||||
db_root: format!("target/{}/grin-prop-{}", test_name_dir, n),
|
||||
p2p_config: p2p::P2PConfig{port: 10000+n, ..p2p::P2PConfig::default()},
|
||||
..Default::default()
|
||||
}, &handle).unwrap();
|
||||
|
@ -324,12 +234,19 @@ fn simulate_block_propagation() {
|
|||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// 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() {
|
||||
env_logger::init();
|
||||
|
||||
let test_name_dir="test_servers/grin-sync";
|
||||
framework::clean_all_output(test_name_dir);
|
||||
|
||||
let mut evtlp = reactor::Core::new().unwrap();
|
||||
let handle = evtlp.handle();
|
||||
|
||||
|
@ -345,7 +262,7 @@ fn simulate_full_sync() {
|
|||
for n in 0..2 {
|
||||
let s = grin::Server::future(
|
||||
grin::ServerConfig{
|
||||
db_root: format!("target/grin-sync-{}", n),
|
||||
db_root: format!("target/{}/grin-sync-{}", test_name_dir, n),
|
||||
p2p_config: p2p::P2PConfig{port: 11000+n, ..p2p::P2PConfig::default()},
|
||||
..Default::default()
|
||||
}, &handle).unwrap();
|
||||
|
@ -364,38 +281,6 @@ fn simulate_full_sync() {
|
|||
evtlp.run(change(&servers[1]));
|
||||
}
|
||||
|
||||
/// Creates 5 servers, one being a seed and check that through peer address
|
||||
/// messages they all end up connected.
|
||||
#[test]
|
||||
fn simulate_seeding() {
|
||||
env_logger::init();
|
||||
|
||||
let mut evtlp = reactor::Core::new().unwrap();
|
||||
let handle = evtlp.handle();
|
||||
|
||||
// instantiates 5 servers on different ports, with 0 as a seed
|
||||
let mut servers = vec![];
|
||||
for n in 0..5 {
|
||||
let s = grin::Server::future(
|
||||
grin::ServerConfig{
|
||||
db_root: format!("target/grin-seed-{}", n),
|
||||
p2p_config: p2p::P2PConfig{port: 12000+n, ..p2p::P2PConfig::default()},
|
||||
seeding_type: grin::Seeding::List(vec!["127.0.0.1:12000".to_string()]),
|
||||
..Default::default()
|
||||
}, &handle).unwrap();
|
||||
servers.push(s);
|
||||
}
|
||||
|
||||
// wait a bit and check all servers are now connected
|
||||
evtlp.run(Timer::default().sleep(time::Duration::from_secs(30)).and_then(|_| {
|
||||
for s in servers {
|
||||
// occasionally 2 peers will connect to each other at the same time
|
||||
assert!(s.peer_count() >= 4);
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
// Builds the change future, monitoring for a change of head on the provided server
|
||||
fn change<'a>(s: &'a grin::Server) -> HeadChange<'a> {
|
||||
let start_head = s.head();
|
||||
|
|
|
@ -58,7 +58,12 @@ fn main() {
|
|||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port to start the server on")
|
||||
.help("Port to start the P2P server on")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("api_port")
|
||||
.short("a")
|
||||
.long("api_port")
|
||||
.help("Port on which to start the api server (e.g. transaction pool api)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("seed")
|
||||
.short("s")
|
||||
|
@ -102,15 +107,20 @@ fn main() {
|
|||
.long("pass")
|
||||
.help("Wallet passphrase used to generate the private key seed")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("dir")
|
||||
.short("d")
|
||||
.long("dir")
|
||||
.arg(Arg::with_name("data_dir")
|
||||
.short("dd")
|
||||
.long("data_dir")
|
||||
.help("Directory in which to store wallet files (defaults to current directory)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("port")
|
||||
.short("r")
|
||||
.long("port")
|
||||
.help("Port on which to run the wallet receiver when in receiver mode")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("api_server_address")
|
||||
.short("a")
|
||||
.long("api_server_address")
|
||||
.help("The api address of a running node on which to check inputs and post transactions")
|
||||
.takes_value(true))
|
||||
.subcommand(SubCommand::with_name("receive")
|
||||
.about("Run the wallet in receiving mode. If an input file is provided, will process it, otherwise runs in server mode waiting for send requests.")
|
||||
|
@ -168,9 +178,16 @@ fn server_command(server_args: &ArgMatches) {
|
|||
if let Some(port) = server_args.value_of("port") {
|
||||
server_config.p2p_config.port = port.parse().unwrap();
|
||||
}
|
||||
|
||||
if let Some(api_port) = server_args.value_of("api_port") {
|
||||
let default_ip = "127.0.0.1";
|
||||
server_config.api_http_addr = format!("{}:{}", default_ip, api_port);
|
||||
}
|
||||
|
||||
if server_args.is_present("mine") {
|
||||
server_config.mining_config.enable_mining = true;
|
||||
}
|
||||
|
||||
if let Some(wallet_url) = server_args.value_of("wallet_url") {
|
||||
server_config.mining_config.wallet_receiver_url = wallet_url.to_string();
|
||||
}
|
||||
|
@ -232,6 +249,11 @@ fn wallet_command(wallet_args: &ArgMatches) {
|
|||
if let Some(dir) = wallet_args.value_of("dir") {
|
||||
wallet_config.data_file_dir = dir.to_string().clone();
|
||||
}
|
||||
|
||||
if let Some(sa) = wallet_args.value_of("api_server_address") {
|
||||
wallet_config.check_node_api_http_addr = sa.to_string().clone();
|
||||
}
|
||||
|
||||
|
||||
match wallet_args.subcommand() {
|
||||
|
||||
|
|
|
@ -7,6 +7,13 @@ workspace = ".."
|
|||
[dependencies]
|
||||
byteorder = "^0.5"
|
||||
rocksdb = "^0.6.0"
|
||||
## When using GCC 7, the rust-rocksdb dependency doesn't compile
|
||||
## To get around this (temporarily) clone (beside the 'grin' directory)
|
||||
## https://github.com/spacejam/rust-rocksdb.git
|
||||
## Manually apply the changes in:
|
||||
## https://github.com/facebook/rocksdb/commit/816c1e30ca73615c75fc208ddcc4b05012b30951
|
||||
## And swap the dependency for the one below
|
||||
#rocksdb = { path = "../../rust-rocksdb" }
|
||||
tiny-keccak = "1.1"
|
||||
|
||||
grin_core = { path = "../core" }
|
||||
|
|
|
@ -34,7 +34,6 @@ pub fn refresh_outputs(config: &WalletConfig, ext_key: &ExtendedKey) {
|
|||
// check each output that's not spent
|
||||
for out in &mut wallet_data.outputs {
|
||||
if out.status != OutputStatus::Spent {
|
||||
|
||||
// figure out the commitment
|
||||
let key = ext_key.derive(&secp, out.n_child).unwrap();
|
||||
let commitment = secp.commit(out.value, key.key).unwrap();
|
||||
|
@ -42,6 +41,7 @@ pub fn refresh_outputs(config: &WalletConfig, ext_key: &ExtendedKey) {
|
|||
// TODO check the pool for unconfirmed
|
||||
|
||||
let out_res = get_output_by_commitment(config, commitment);
|
||||
|
||||
if out_res.is_ok() {
|
||||
// output is known, it's a new utxo
|
||||
out.status = OutputStatus::Unspent;
|
||||
|
@ -51,6 +51,9 @@ pub fn refresh_outputs(config: &WalletConfig, ext_key: &ExtendedKey) {
|
|||
if let Err(api::Error::NotFound) = out_res {
|
||||
out.status = OutputStatus::Spent;
|
||||
}
|
||||
} else {
|
||||
//TODO find error with connection and return
|
||||
//error!("Error contacting server node at {}. Is it running?", config.check_node_api_http_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +66,7 @@ fn get_output_by_commitment(config: &WalletConfig,
|
|||
commit: pedersen::Commitment)
|
||||
-> Result<Output, api::Error> {
|
||||
let url = format!("{}/v1/chain/utxo/{}",
|
||||
config.api_http_addr,
|
||||
config.check_node_api_http_addr,
|
||||
util::to_hex(commit.as_ref().to_vec()));
|
||||
api::client::get::<Output>(url.as_str())
|
||||
}
|
||||
|
|
|
@ -38,4 +38,4 @@ mod types;
|
|||
pub use extkey::ExtendedKey;
|
||||
pub use receiver::{WalletReceiver, receive_json_tx};
|
||||
pub use sender::issue_send_tx;
|
||||
pub use types::WalletConfig;
|
||||
pub use types::{WalletConfig, WalletReceiveRequest, CbAmount, CbData};
|
||||
|
|
|
@ -75,25 +75,11 @@ pub fn receive_json_tx(config: &WalletConfig, ext_key: &ExtendedKey, partial_tx_
|
|||
let final_tx = receive_transaction(&config, ext_key, amount, blinding, partial_tx)?;
|
||||
let tx_hex = util::to_hex(ser::ser_vec(&final_tx).unwrap());
|
||||
|
||||
|
||||
let url = format!("{}/v1/pool/push", config.api_http_addr.as_str());
|
||||
let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
|
||||
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Amount in request to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbAmount {
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
/// Response to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbData {
|
||||
output: String,
|
||||
kernel: String,
|
||||
}
|
||||
|
||||
/// Component used to receive coins, implements all the receiving end of the
|
||||
/// wallet REST API as well as some of the command-line operations.
|
||||
#[derive(Clone)]
|
||||
|
@ -105,36 +91,61 @@ pub struct WalletReceiver {
|
|||
impl ApiEndpoint for WalletReceiver {
|
||||
type ID = String;
|
||||
type T = String;
|
||||
type OP_IN = CbAmount;
|
||||
type OP_IN = WalletReceiveRequest;
|
||||
type OP_OUT = CbData;
|
||||
|
||||
fn operations(&self) -> Vec<Operation> {
|
||||
vec![Operation::Custom("coinbase".to_string())]
|
||||
vec![
|
||||
Operation::Custom("coinbase".to_string()),
|
||||
Operation::Custom("receive_json_tx".to_string())
|
||||
]
|
||||
}
|
||||
|
||||
fn operation(&self, op: String, input: CbAmount) -> ApiResult<CbData> {
|
||||
debug!("Operation {} with amount {}", op, input.amount);
|
||||
if input.amount == 0 {
|
||||
return Err(api::Error::Argument(format!("Zero amount not allowed.")));
|
||||
}
|
||||
fn operation(&self, op: String, input: WalletReceiveRequest) -> ApiResult<CbData> {
|
||||
match op.as_str() {
|
||||
"coinbase" => {
|
||||
let (out, kern) =
|
||||
receive_coinbase(&self.config, &self.key, input.amount).map_err(|e| {
|
||||
api::Error::Internal(format!("Error building coinbase: {:?}", e))
|
||||
})?;
|
||||
let out_bin =
|
||||
ser::ser_vec(&out).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing output: {:?}", e))
|
||||
})?;
|
||||
let kern_bin =
|
||||
ser::ser_vec(&kern).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing kernel: {:?}", e))
|
||||
})?;
|
||||
Ok(CbData {
|
||||
output: util::to_hex(out_bin),
|
||||
kernel: util::to_hex(kern_bin),
|
||||
})
|
||||
match input {
|
||||
WalletReceiveRequest::Coinbase(cb_amount) => {
|
||||
debug!("Operation {} with amount {}", op, cb_amount.amount);
|
||||
if cb_amount.amount == 0 {
|
||||
return Err(api::Error::Argument(format!("Zero amount not allowed.")));
|
||||
}
|
||||
let (out, kern) =
|
||||
receive_coinbase(&self.config, &self.key, cb_amount.amount).map_err(|e| {
|
||||
api::Error::Internal(format!("Error building coinbase: {:?}", e))
|
||||
})?;
|
||||
let out_bin =
|
||||
ser::ser_vec(&out).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing output: {:?}", e))
|
||||
})?;
|
||||
let kern_bin =
|
||||
ser::ser_vec(&kern).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing kernel: {:?}", e))
|
||||
})?;
|
||||
Ok(CbData {
|
||||
output: util::to_hex(out_bin),
|
||||
kernel: util::to_hex(kern_bin),
|
||||
})
|
||||
}
|
||||
_ => Err(api::Error::Argument(format!("Incorrect request data: {}", op))),
|
||||
}
|
||||
}
|
||||
"receive_json_tx" => {
|
||||
match input {
|
||||
WalletReceiveRequest::PartialTransaction(partial_tx_str) => {
|
||||
debug!("Operation {} with transaction {}", op, &partial_tx_str);
|
||||
receive_json_tx(&self.config, &self.key, &partial_tx_str).map_err(|e| {
|
||||
api::Error::Internal(format!("Error processing partial transaction: {:?}", e))
|
||||
});
|
||||
|
||||
//TODO: Return emptiness for now, should be a proper enum return type
|
||||
Ok(CbData {
|
||||
output: String::from(""),
|
||||
kernel: String::from(""),
|
||||
})
|
||||
}
|
||||
_ => Err(api::Error::Argument(format!("Incorrect request data: {}", op))),
|
||||
}
|
||||
}
|
||||
_ => Err(api::Error::Argument(format!("Unknown operation: {}", op))),
|
||||
}
|
||||
|
|
|
@ -21,20 +21,29 @@ use core::core::{Transaction, build};
|
|||
use extkey::ExtendedKey;
|
||||
use types::*;
|
||||
|
||||
use api;
|
||||
|
||||
/// Issue a new transaction to the provided sender by spending some of our
|
||||
/// wallet
|
||||
/// UTXOs. The destination can be "stdout" (for command line) or a URL to the
|
||||
/// recipients wallet receiver (to be implemented).
|
||||
pub fn issue_send_tx(config: &WalletConfig, ext_key: &ExtendedKey, amount: u64, dest: String) -> Result<(), Error> {
|
||||
checker::refresh_outputs(&WalletConfig::default(), ext_key);
|
||||
|
||||
checker::refresh_outputs(&config, ext_key);
|
||||
|
||||
let (tx, blind_sum) = build_send_tx(config, ext_key, amount)?;
|
||||
let json_tx = partial_tx_to_json(amount, blind_sum, tx);
|
||||
|
||||
if dest == "stdout" {
|
||||
println!("{}", json_tx);
|
||||
} else if &dest[..4] == "http" {
|
||||
// TODO
|
||||
unimplemented!();
|
||||
let url = format!("{}/v1/receive/receive_json_tx",
|
||||
&dest);
|
||||
debug!("Posting partial transaction to {}", url);
|
||||
let request = WalletReceiveRequest::PartialTransaction(json_tx);
|
||||
let res: CbData = api::client::post(url.as_str(),
|
||||
&request)
|
||||
.expect(&format!("Wallet receiver at {} unreachable, could not send transaction. Is it running?", url));
|
||||
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -78,7 +78,12 @@ impl From<api::Error> for Error {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WalletConfig {
|
||||
//The api address that this api server (i.e. this wallet) will run
|
||||
pub api_http_addr: String,
|
||||
//The api address of a running server node, against which transaction inputs will be checked
|
||||
//during send
|
||||
pub check_node_api_http_addr: String,
|
||||
//The directory in which wallet files are stored
|
||||
pub data_file_dir: String,
|
||||
}
|
||||
|
||||
|
@ -86,6 +91,7 @@ impl Default for WalletConfig {
|
|||
fn default() -> WalletConfig {
|
||||
WalletConfig {
|
||||
api_http_addr: "http://127.0.0.1:13415".to_string(),
|
||||
check_node_api_http_addr: "http://127.0.0.1:13415".to_string(),
|
||||
data_file_dir: ".".to_string(),
|
||||
}
|
||||
}
|
||||
|
@ -278,3 +284,24 @@ pub fn partial_tx_from_json(json_str: &str) -> Result<(u64, SecretKey, Transacti
|
|||
|
||||
Ok((partial_tx.amount, blinding, tx))
|
||||
}
|
||||
|
||||
/// Amount in request to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum WalletReceiveRequest {
|
||||
Coinbase(CbAmount),
|
||||
PartialTransaction(String),
|
||||
Finalize(String),
|
||||
}
|
||||
|
||||
/// Amount in request to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbAmount {
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
/// Response to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbData {
|
||||
pub output: String,
|
||||
pub kernel: String,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue