replace TorProcess with rust-native arti client and upgrade to tokio1

This commit is contained in:
scilio 2024-04-16 11:16:53 -04:00
parent df2cb31258
commit 75f2bdf2ba
21 changed files with 5663 additions and 2432 deletions

3373
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,45 +9,54 @@ edition = "2021"
members = ["onion"] members = ["onion"]
[dependencies] [dependencies]
async-std = { version = "1", features = ["tokio02"] } arti-client = { version = "0.17.0", default-features = false, features = ["async-std", "rustls", "onion-service-client", "onion-service-service"] }
arti-hyper = "0.17.0"
async-std = { version = "1", features = ["tokio1"] }
async-trait = "0.1.74" async-trait = "0.1.74"
blake2 = { package = "blake2-rfc", version = "0.2"} blake2 = { package = "blake2-rfc", version = "0.2" }
byteorder = "1" byteorder = "1"
bytes = "1.5.0" bytes = "1.5.0"
chacha20 = "0.9.1" chacha20 = "0.9.1"
chrono = "0.4.31" chrono = "0.4.31"
clap = { version = "2.33", features = ["yaml"] } clap = { version = "2.33", features = ["yaml"] }
ctrlc = { version = "3.1", features = ["termination"] } ctrlc = { version = "3.1", features = ["termination"] }
curve25519-dalek = "2.1" curve25519-dalek = "4.1.2"
dirs = "2.0" dirs = "2.0"
ed25519-dalek = "1.0.1" ed25519-dalek = "2.1.1"
function_name = "0.3.0" function_name = "0.3.0"
futures = "0.3" futures = "0.3"
hmac = { version = "0.12.0", features = ["std"]} fs-mistrust = "0.7.9"
hyper = "0.13" hmac = { version = "0.12.0", features = ["std"] }
hyper-proxy = "0.9.1" hyper = "0.14.28"
hyper-socks2 = "0.5.0" hyper-tls = "0.6.0"
hyper-timeout = { version = "0.3", features = [] }
itertools = { version = "0.12.0" } itertools = { version = "0.12.0" }
jsonrpc-core = "17.1" jsonrpc-core = "18.0.0"
jsonrpc-derive = "17.1" jsonrpc-derive = "18.0.0"
jsonrpc-http-server = "17.1" jsonrpc-http-server = "18.0.0"
lazy_static = "1" lazy_static = "1"
pbkdf2 = "0.8.0" pbkdf2 = "0.8.0"
rand = "0.7.3" rand = "0.7.3"
remove_dir_all = "0.8.2" remove_dir_all = "0.8.2"
ring = "0.16" ring = "0.16"
rpassword = "4.0" rpassword = "4.0"
serde = { version = "1", features= ["derive"]} serde = { version = "1", features = ["derive"] }
serde_derive = "1" serde_derive = "1"
serde_json = "1" serde_json = "1"
sha2 = "0.10.0" sha2 = "0.10.0"
thiserror = "1.0.30" thiserror = "1.0.30"
tokio = { version = "0.2", features = ["full"] } tls-api = "0.9.0"
tls-api-native-tls = "0.9.0"
tokio = { version = "1.37.0", features = ["full"] }
toml = "0.8.8" toml = "0.8.8"
tor-hscrypto = "0.17.0"
tor-hsrproxy = "0.17.0"
tor-hsservice = "0.17.0"
tor-llcrypto = "0.17.0"
tor-keymgr = "0.17.0"
tor-rtcompat = "0.17.0"
x25519-dalek = "0.6.0" x25519-dalek = "0.6.0"
grin_onion = { path = "./onion" } grin_onion = { path = "./onion" }
grin_secp256k1zkp = { version = "0.7.12", features = ["bullet-proof-sizing"]} grin_secp256k1zkp = { version = "0.7.12", features = ["bullet-proof-sizing"] }
grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" } grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
@ -62,4 +71,4 @@ grin_wallet_controller = { git = "https://github.com/mimblewimble/grin-wallet",
grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.2.0-beta.1" } grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.2.0-beta.1" }
grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.2.0-beta.1" } grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.2.0-beta.1" }
grin_wallet_util = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.2.0-beta.1" } grin_wallet_util = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.2.0-beta.1" }
log = "0.4.20" log = "0.4.20"

View file

@ -1,15 +1,15 @@
pub mod crypto;
pub mod onion;
pub mod util;
use crate::crypto::secp::{random_secret, Commitment, SecretKey};
use crate::onion::{new_stream_cipher, Onion, OnionError, Payload, RawBytes};
use chacha20::cipher::StreamCipher; use chacha20::cipher::StreamCipher;
use grin_core::core::FeeFields; use grin_core::core::FeeFields;
use secp256k1zkp::pedersen::RangeProof; use secp256k1zkp::pedersen::RangeProof;
use x25519_dalek::PublicKey as xPublicKey;
use x25519_dalek::{SharedSecret, StaticSecret}; use x25519_dalek::{SharedSecret, StaticSecret};
use x25519_dalek::PublicKey as xPublicKey;
use crate::crypto::secp::{Commitment, random_secret, SecretKey};
use crate::onion::{new_stream_cipher, Onion, OnionError, Payload, RawBytes};
pub mod crypto;
pub mod onion;
pub mod util;
#[derive(Clone)] #[derive(Clone)]
pub struct Hop { pub struct Hop {
@ -28,7 +28,7 @@ pub fn new_hop(
Hop { Hop {
server_pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())), server_pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())),
excess: hop_excess.clone(), excess: hop_excess.clone(),
fee: FeeFields::from(fee as u32), fee: FeeFields::from(fee),
rangeproof: proof, rangeproof: proof,
} }
} }
@ -71,7 +71,7 @@ pub fn create_onion(commitment: &Commitment, hops: &Vec<Hop>) -> Result<Onion, O
for i in (0..shared_secrets.len()).rev() { for i in (0..shared_secrets.len()).rev() {
let mut cipher = new_stream_cipher(&shared_secrets[i])?; let mut cipher = new_stream_cipher(&shared_secrets[i])?;
for j in i..shared_secrets.len() { for j in i..shared_secrets.len() {
cipher.apply_keystream(&mut enc_payloads[j]); cipher.apply_keystream(enc_payloads[j].as_mut_slice());
} }
} }
@ -84,14 +84,15 @@ pub fn create_onion(commitment: &Commitment, hops: &Vec<Hop>) -> Result<Onion, O
} }
pub mod test_util { pub mod test_util {
use super::*; use grin_core::core::hash::Hash;
use grin_util::ToHex;
use rand::{RngCore, thread_rng};
use secp256k1zkp::Secp256k1;
use crate::crypto::dalek::DalekPublicKey; use crate::crypto::dalek::DalekPublicKey;
use crate::crypto::secp; use crate::crypto::secp;
use grin_core::core::hash::Hash; use super::*;
use grin_util::ToHex;
use rand::{thread_rng, RngCore};
use secp256k1zkp::Secp256k1;
pub fn rand_onion() -> Onion { pub fn rand_onion() -> Onion {
let commit = rand_commit(); let commit = rand_commit();
@ -116,20 +117,20 @@ pub mod test_util {
} }
pub fn rand_commit() -> Commitment { pub fn rand_commit() -> Commitment {
secp::commit(rand::thread_rng().next_u64(), &secp::random_secret()).unwrap() secp::commit(thread_rng().next_u64(), &random_secret()).unwrap()
} }
pub fn rand_hash() -> Hash { pub fn rand_hash() -> Hash {
Hash::from_hex(secp::random_secret().to_hex().as_str()).unwrap() Hash::from_hex(random_secret().to_hex().as_str()).unwrap()
} }
pub fn rand_proof() -> RangeProof { pub fn rand_proof() -> RangeProof {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
secp.bullet_proof( secp.bullet_proof(
rand::thread_rng().next_u64(), thread_rng().next_u64(),
secp::random_secret(), random_secret(),
secp::random_secret(), random_secret(),
secp::random_secret(), random_secret(),
None, None,
None, None,
) )
@ -153,8 +154,8 @@ pub mod test_util {
let rp = secp.bullet_proof( let rp = secp.bullet_proof(
out_value, out_value,
blind.clone(), blind.clone(),
secp::random_secret(), random_secret(),
secp::random_secret(), random_secret(),
None, None,
None, None,
); );

View file

@ -1,22 +1,23 @@
use crate::crypto::secp::{self, Commitment, RangeProof, SecretKey};
use crate::util::{read_optional, vec_to_array, write_optional};
use chacha20::cipher::{NewCipher, StreamCipher};
use chacha20::{ChaCha20, Key, Nonce};
use grin_core::core::FeeFields;
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use grin_util::{self, ToHex};
use hmac::digest::InvalidLength;
use hmac::{Hmac, Mac};
use serde::ser::SerializeStruct;
use serde::Deserialize;
use sha2::Sha256;
use std::fmt; use std::fmt;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::result::Result; use std::result::Result;
use chacha20::{ChaCha20, Key, Nonce};
use chacha20::cipher::{NewCipher, StreamCipher};
use grin_core::core::FeeFields;
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use grin_util::{self, ToHex};
use hmac::{Hmac, Mac};
use hmac::digest::InvalidLength;
use serde::Deserialize;
use serde::ser::SerializeStruct;
use sha2::Sha256;
use thiserror::Error; use thiserror::Error;
use x25519_dalek::{PublicKey as xPublicKey, SharedSecret, StaticSecret}; use x25519_dalek::{PublicKey as xPublicKey, SharedSecret, StaticSecret};
use crate::crypto::secp::{self, Commitment, RangeProof, SecretKey};
use crate::util::{read_optional, vec_to_array, write_optional};
type HmacSha256 = Hmac<Sha256>; type HmacSha256 = Hmac<Sha256>;
pub type RawBytes = Vec<u8>; pub type RawBytes = Vec<u8>;
@ -328,12 +329,13 @@ impl From<ser::Error> for OnionError {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*;
use crate::crypto::secp::random_secret;
use crate::{create_onion, new_hop, Hop};
use grin_core::core::FeeFields; use grin_core::core::FeeFields;
use crate::{create_onion, Hop, new_hop};
use crate::crypto::secp::random_secret;
use super::*;
/// Test end-to-end Onion creation and unwrapping logic. /// Test end-to-end Onion creation and unwrapping logic.
#[test] #[test]
fn onion() { fn onion() {
@ -381,7 +383,7 @@ pub mod tests {
let mut payload = Payload { let mut payload = Payload {
next_ephemeral_pk: onion_packet.ephemeral_pubkey.clone(), next_ephemeral_pk: onion_packet.ephemeral_pubkey.clone(),
excess: random_secret(), excess: random_secret(),
fee: FeeFields::from(fee_per_hop as u32), fee: FeeFields::from(fee_per_hop),
rangeproof: None, rangeproof: None,
}; };
for i in 0..5 { for i in 0..5 {
@ -393,6 +395,6 @@ pub mod tests {
assert!(payload.rangeproof.is_some()); assert!(payload.rangeproof.is_some());
assert_eq!(payload.rangeproof.unwrap(), hops[4].rangeproof.unwrap()); assert_eq!(payload.rangeproof.unwrap(), hops[4].rangeproof.unwrap());
assert_eq!(secp::commit(out_value, &final_blind).unwrap(), final_commit); assert_eq!(secp::commit(out_value, &final_blind).unwrap(), final_commit);
assert_eq!(payload.fee, FeeFields::from(fee_per_hop as u32)); assert_eq!(payload.fee, FeeFields::from(fee_per_hop));
} }
} }

View file

@ -1,372 +1,372 @@
use mwixnet::config::{self, ServerConfig}; #[macro_use]
use mwixnet::node::HttpGrinNode; extern crate clap;
use mwixnet::servers;
use mwixnet::store::SwapStore;
use mwixnet::tor;
use mwixnet::wallet::HttpWallet;
use clap::App;
use grin_core::global;
use grin_core::global::ChainTypes;
use grin_onion::crypto;
use grin_onion::crypto::dalek::DalekPublicKey;
use grin_util::{StopState, ZeroingString};
use mwixnet::client::{MixClient, MixClientImpl};
use mwixnet::node::GrinNode;
use mwixnet::store::StoreError;
use rand::{thread_rng, Rng};
use rpassword;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn}; use std::thread::{sleep, spawn};
use std::time::Duration; use std::time::Duration;
#[macro_use] use clap::App;
extern crate clap; use grin_core::global;
use grin_core::global::ChainTypes;
use grin_util::{StopState, ZeroingString};
use rand::{Rng, thread_rng};
use rpassword;
use tor_rtcompat::PreferredRuntime;
use grin_onion::crypto;
use grin_onion::crypto::dalek::DalekPublicKey;
use mwixnet::config::{self, ServerConfig};
use mwixnet::mix_client::{MixClient, MixClientImpl};
use mwixnet::node::GrinNode;
use mwixnet::node::HttpGrinNode;
use mwixnet::servers;
use mwixnet::store::StoreError;
use mwixnet::store::SwapStore;
use mwixnet::tor;
use mwixnet::wallet::HttpWallet;
const DEFAULT_INTERVAL: u32 = 12 * 60 * 60; const DEFAULT_INTERVAL: u32 = 12 * 60 * 60;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
real_main()?; real_main()?;
std::process::exit(0); std::process::exit(0);
} }
fn real_main() -> Result<(), Box<dyn std::error::Error>> { fn real_main() -> Result<(), Box<dyn std::error::Error>> {
let yml = load_yaml!("mwixnet.yml"); let yml = load_yaml!("mwixnet.yml");
let args = App::from_yaml(yml).get_matches(); let args = App::from_yaml(yml).get_matches();
let chain_type = if args.is_present("testnet") { let chain_type = if args.is_present("testnet") {
ChainTypes::Testnet ChainTypes::Testnet
} else { } else {
ChainTypes::Mainnet ChainTypes::Mainnet
}; };
global::set_local_chain_type(chain_type); global::set_local_chain_type(chain_type);
let config_path = match args.value_of("config_file") { let config_path = match args.value_of("config_file") {
Some(path) => PathBuf::from(path), Some(path) => PathBuf::from(path),
None => { None => {
let mut grin_path = config::get_grin_path(&chain_type); let mut grin_path = config::get_grin_path(&chain_type);
grin_path.push("mwixnet-config.toml"); grin_path.push("mwixnet-config.toml");
grin_path grin_path
} }
}; };
let round_time = args let round_time = args
.value_of("round_time") .value_of("round_time")
.map(|t| t.parse::<u32>().unwrap()); .map(|t| t.parse::<u32>().unwrap());
let bind_addr = args.value_of("bind_addr"); let bind_addr = args.value_of("bind_addr");
let socks_addr = args.value_of("socks_addr"); let grin_node_url = args.value_of("grin_node_url");
let grin_node_url = args.value_of("grin_node_url"); let grin_node_secret_path = args.value_of("grin_node_secret_path");
let grin_node_secret_path = args.value_of("grin_node_secret_path"); let wallet_owner_url = args.value_of("wallet_owner_url");
let wallet_owner_url = args.value_of("wallet_owner_url"); let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path");
let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path"); let prev_server = args
let prev_server = args .value_of("prev_server")
.value_of("prev_server") .map(|p| DalekPublicKey::from_hex(&p).unwrap());
.map(|p| DalekPublicKey::from_hex(&p).unwrap()); let next_server = args
let next_server = args .value_of("next_server")
.value_of("next_server") .map(|p| DalekPublicKey::from_hex(&p).unwrap());
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
// Write a new config file if init-config command is supplied // Write a new config file if init-config command is supplied
if let ("init-config", Some(_)) = args.subcommand() { if let ("init-config", Some(_)) = args.subcommand() {
if config_path.exists() { if config_path.exists() {
panic!( panic!(
"Config file already exists at {}", "Config file already exists at {}",
config_path.to_string_lossy() config_path.to_string_lossy()
); );
} }
let server_config = ServerConfig { let server_config = ServerConfig {
key: crypto::secp::random_secret(), key: crypto::secp::random_secret(),
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL), interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
addr: bind_addr.unwrap_or("127.0.0.1:3000").parse()?, addr: bind_addr.unwrap_or("127.0.0.1:3000").parse()?,
socks_proxy_addr: socks_addr.unwrap_or("127.0.0.1:3001").parse()?, grin_node_url: match grin_node_url {
grin_node_url: match grin_node_url { Some(u) => u.parse()?,
Some(u) => u.parse()?, None => config::grin_node_url(&chain_type),
None => config::grin_node_url(&chain_type), },
}, grin_node_secret_path: match grin_node_secret_path {
grin_node_secret_path: match grin_node_secret_path { Some(p) => Some(p.to_owned()),
Some(p) => Some(p.to_owned()), None => config::node_secret_path(&chain_type)
None => config::node_secret_path(&chain_type) .to_str()
.to_str() .map(|p| p.to_owned()),
.map(|p| p.to_owned()), },
}, wallet_owner_url: match wallet_owner_url {
wallet_owner_url: match wallet_owner_url { Some(u) => u.parse()?,
Some(u) => u.parse()?, None => config::wallet_owner_url(&chain_type),
None => config::wallet_owner_url(&chain_type), },
}, wallet_owner_secret_path: match wallet_owner_secret_path {
wallet_owner_secret_path: match wallet_owner_secret_path { Some(p) => Some(p.to_owned()),
Some(p) => Some(p.to_owned()), None => config::wallet_owner_secret_path(&chain_type)
None => config::wallet_owner_secret_path(&chain_type) .to_str()
.to_str() .map(|p| p.to_owned()),
.map(|p| p.to_owned()), },
}, prev_server,
prev_server, next_server,
next_server, };
};
let password = prompt_password_confirm(); let password = prompt_password_confirm();
config::write_config(&config_path, &server_config, &password)?; config::write_config(&config_path, &server_config, &password)?;
println!( println!(
"Config file written to {:?}. Please back this file up in a safe place.", "Config file written to {:?}. Please back this file up in a safe place.",
config_path config_path
); );
return Ok(()); return Ok(());
} }
let password = prompt_password(); let password = prompt_password();
let mut server_config = config::load_config(&config_path, &password)?; let mut server_config = config::load_config(&config_path, &password)?;
// Override grin_node_url, if supplied // Override grin_node_url, if supplied
if let Some(grin_node_url) = grin_node_url { if let Some(grin_node_url) = grin_node_url {
server_config.grin_node_url = grin_node_url.parse()?; server_config.grin_node_url = grin_node_url.parse()?;
} }
// Override grin_node_secret_path, if supplied // Override grin_node_secret_path, if supplied
if let Some(grin_node_secret_path) = grin_node_secret_path { if let Some(grin_node_secret_path) = grin_node_secret_path {
server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned()); server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned());
} }
// Override wallet_owner_url, if supplied // Override wallet_owner_url, if supplied
if let Some(wallet_owner_url) = wallet_owner_url { if let Some(wallet_owner_url) = wallet_owner_url {
server_config.wallet_owner_url = wallet_owner_url.parse()?; server_config.wallet_owner_url = wallet_owner_url.parse()?;
} }
// Override wallet_owner_secret_path, if supplied // Override wallet_owner_secret_path, if supplied
if let Some(wallet_owner_secret_path) = wallet_owner_secret_path { if let Some(wallet_owner_secret_path) = wallet_owner_secret_path {
server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned()); server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned());
} }
// Override bind_addr, if supplied // Override bind_addr, if supplied
if let Some(bind_addr) = bind_addr { if let Some(bind_addr) = bind_addr {
server_config.addr = bind_addr.parse()?; server_config.addr = bind_addr.parse()?;
} }
// Override socks_addr, if supplied // Override prev_server, if supplied
if let Some(socks_addr) = socks_addr { if let Some(prev_server) = prev_server {
server_config.socks_proxy_addr = socks_addr.parse()?; server_config.prev_server = Some(prev_server);
} }
// Override prev_server, if supplied // Override next_server, if supplied
if let Some(prev_server) = prev_server { if let Some(next_server) = next_server {
server_config.prev_server = Some(prev_server); server_config.next_server = Some(next_server);
} }
// Override next_server, if supplied // Create GrinNode
if let Some(next_server) = next_server { let node = HttpGrinNode::new(
server_config.next_server = Some(next_server); &server_config.grin_node_url,
} &server_config.node_api_secret(),
);
// Create GrinNode // Node API health check
let node = HttpGrinNode::new( let rt = tokio::runtime::Builder::new_multi_thread()
&server_config.grin_node_url, .enable_all()
&server_config.node_api_secret(), .build()?;
); if let Err(e) = rt.block_on(node.async_get_chain_tip()) {
eprintln!("Node communication failure. Is node listening?");
return Err(e.into());
};
// Node API health check // Open wallet
let mut rt = tokio::runtime::Builder::new() let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass"));
.threaded_scheduler() let wallet = rt.block_on(HttpWallet::async_open_wallet(
.enable_all() &server_config.wallet_owner_url,
.build()?; &server_config.wallet_owner_api_secret(),
if let Err(e) = rt.block_on(node.async_get_chain_tip()) { &wallet_pass,
eprintln!("Node communication failure. Is node listening?"); ));
return Err(e.into()); let wallet = match wallet {
}; Ok(w) => w,
Err(e) => {
eprintln!("Wallet communication failure. Is wallet listening?");
return Err(e.into());
}
};
// Open wallet let tor_instance = rt.block_on(tor::async_init_tor(
let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass")); PreferredRuntime::current().unwrap(),
let wallet = rt.block_on(HttpWallet::async_open_wallet( &config::get_grin_path(&chain_type).to_str().unwrap(),
&server_config.wallet_owner_url, &server_config,
&server_config.wallet_owner_api_secret(), ))?;
&wallet_pass, let tor_instance = Arc::new(grin_util::Mutex::new(tor_instance));
)); let tor_clone = tor_instance.clone();
let wallet = match wallet {
Ok(w) => w,
Err(e) => {
eprintln!("Wallet communication failure. Is wallet listening?");
return Err(e.into());
}
};
let mut tor_process = tor::init_tor_listener( let stop_state = Arc::new(StopState::new());
&config::get_grin_path(&chain_type).to_str().unwrap(), let stop_state_clone = stop_state.clone();
&server_config,
)?;
let stop_state = Arc::new(StopState::new()); rt.spawn(async move {
let stop_state_clone = stop_state.clone(); futures::executor::block_on(build_signals_fut());
tor_clone.lock().stop();
stop_state_clone.stop();
});
rt.spawn(async move { let next_mixer: Option<Arc<dyn MixClient>> = server_config.next_server.clone().map(|pk| {
futures::executor::block_on(build_signals_fut()); let client: Arc<dyn MixClient> = Arc::new(MixClientImpl::new(
let _ = tor_process.kill(); server_config.clone(),
stop_state_clone.stop(); tor_instance.clone(),
}); pk.clone(),
));
client
});
let next_mixer: Option<Arc<dyn MixClient>> = server_config.next_server.clone().map(|pk| { if server_config.prev_server.is_some() {
let client: Arc<dyn MixClient> = // Start the JSON-RPC HTTP 'mix' server
Arc::new(MixClientImpl::new(server_config.clone(), pk.clone())); println!(
client "Starting MIX server with public key {:?}",
}); server_config.server_pubkey().to_hex()
);
if server_config.prev_server.is_some() { let (_, http_server) = servers::mix_rpc::listen(
// Start the JSON-RPC HTTP 'mix' server rt.handle(),
println!( server_config,
"Starting MIX server with public key {:?}", next_mixer,
server_config.server_pubkey().to_hex() Arc::new(wallet),
); Arc::new(node),
)?;
let (_, http_server) = servers::mix_rpc::listen( let close_handle = http_server.close_handle();
rt.handle(), let round_handle = spawn(move || loop {
server_config, if stop_state.is_stopped() {
next_mixer, close_handle.close();
Arc::new(wallet), break;
Arc::new(node), }
)?;
let close_handle = http_server.close_handle(); sleep(Duration::from_millis(100));
let round_handle = spawn(move || loop { });
if stop_state.is_stopped() {
close_handle.close();
break;
}
sleep(Duration::from_millis(100)); http_server.wait();
}); round_handle.join().unwrap();
} else {
println!(
"Starting SWAP server with public key {:?}",
server_config.server_pubkey().to_hex()
);
http_server.wait(); // Open SwapStore
round_handle.join().unwrap(); let store = SwapStore::new(
} else { config::get_grin_path(&chain_type)
println!( .join("db")
"Starting SWAP server with public key {:?}", .to_str()
server_config.server_pubkey().to_hex() .ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr(
); "db_root path error".to_string(),
)))?,
)?;
// Open SwapStore // Start the mwixnet JSON-RPC HTTP 'swap' server
let store = SwapStore::new( let (swap_server, http_server) = servers::swap_rpc::listen(
config::get_grin_path(&chain_type) rt.handle(),
.join("db") &server_config,
.to_str() next_mixer,
.ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr( Arc::new(wallet),
"db_root path error".to_string(), Arc::new(node),
)))?, store,
)?; )?;
// Start the mwixnet JSON-RPC HTTP 'swap' server let close_handle = http_server.close_handle();
let (swap_server, http_server) = servers::swap_rpc::listen( let round_handle = spawn(move || {
rt.handle(), let mut rng = thread_rng();
&server_config, let mut secs = 0u32;
next_mixer, let mut reorg_secs = 0u32;
Arc::new(wallet), let mut reorg_window = rng.gen_range(900u32, 3600u32);
Arc::new(node), let prev_tx = Arc::new(Mutex::new(None));
store, let server = swap_server.clone();
)?;
let close_handle = http_server.close_handle(); loop {
let round_handle = spawn(move || { if stop_state.is_stopped() {
let mut rng = thread_rng(); close_handle.close();
let mut secs = 0u32; break;
let mut reorg_secs = 0u32; }
let mut reorg_window = rng.gen_range(900u32, 3600u32);
let prev_tx = Arc::new(Mutex::new(None));
let server = swap_server.clone();
loop { sleep(Duration::from_secs(1));
if stop_state.is_stopped() { secs = (secs + 1) % server_config.interval_s;
close_handle.close(); reorg_secs = (reorg_secs + 1) % reorg_window;
break;
}
sleep(Duration::from_secs(1)); if secs == 0 {
secs = (secs + 1) % server_config.interval_s; let prev_tx_clone = prev_tx.clone();
reorg_secs = (reorg_secs + 1) % reorg_window; let server_clone = server.clone();
rt.spawn(async move {
let result = server_clone.lock().await.execute_round().await;
let mut prev_tx_lock = prev_tx_clone.lock().unwrap();
*prev_tx_lock = match result {
Ok(Some(tx)) => Some(tx),
_ => None,
};
});
reorg_secs = 0;
reorg_window = rng.gen_range(900u32, 3600u32);
} else if reorg_secs == 0 {
let prev_tx_clone = prev_tx.clone();
let server_clone = server.clone();
rt.spawn(async move {
let tx_option = {
let prev_tx_lock = prev_tx_clone.lock().unwrap();
prev_tx_lock.clone()
}; // Lock is dropped here
if secs == 0 { if let Some(tx) = tx_option {
let prev_tx_clone = prev_tx.clone(); let result = server_clone.lock().await.check_reorg(&tx).await;
let server_clone = server.clone(); let mut prev_tx_lock = prev_tx_clone.lock().unwrap();
rt.spawn(async move { *prev_tx_lock = match result {
let result = server_clone.lock().await.execute_round().await; Ok(Some(tx)) => Some(tx),
let mut prev_tx_lock = prev_tx_clone.lock().unwrap(); _ => None,
*prev_tx_lock = match result { };
Ok(Some(tx)) => Some(tx), }
_ => None, });
}; reorg_window = rng.gen_range(900u32, 3600u32);
}); }
reorg_secs = 0; }
reorg_window = rng.gen_range(900u32, 3600u32); });
} else if reorg_secs == 0 {
let prev_tx_clone = prev_tx.clone();
let server_clone = server.clone();
rt.spawn(async move {
let tx_option = {
let prev_tx_lock = prev_tx_clone.lock().unwrap();
prev_tx_lock.clone()
}; // Lock is dropped here
if let Some(tx) = tx_option { http_server.wait();
let result = server_clone.lock().await.check_reorg(tx).await; round_handle.join().unwrap();
let mut prev_tx_lock = prev_tx_clone.lock().unwrap(); }
*prev_tx_lock = match result {
Ok(Some(tx)) => Some(tx),
_ => None,
};
}
});
reorg_window = rng.gen_range(900u32, 3600u32);
}
}
});
http_server.wait(); Ok(())
round_handle.join().unwrap();
}
Ok(())
} }
#[cfg(unix)] #[cfg(unix)]
async fn build_signals_fut() { async fn build_signals_fut() {
use tokio::signal::unix::{signal, SignalKind}; use tokio::signal::unix::{signal, SignalKind};
// Listen for SIGINT, SIGQUIT, and SIGTERM // Listen for SIGINT, SIGQUIT, and SIGTERM
let mut terminate_signal = let mut terminate_signal =
signal(SignalKind::terminate()).expect("failed to create terminate signal"); signal(SignalKind::terminate()).expect("failed to create terminate signal");
let mut quit_signal = signal(SignalKind::quit()).expect("failed to create quit signal"); let mut quit_signal = signal(SignalKind::quit()).expect("failed to create quit signal");
let mut interrupt_signal = let mut interrupt_signal =
signal(SignalKind::interrupt()).expect("failed to create interrupt signal"); signal(SignalKind::interrupt()).expect("failed to create interrupt signal");
futures::future::select_all(vec![ futures::future::select_all(vec![
Box::pin(terminate_signal.recv()), Box::pin(terminate_signal.recv()),
Box::pin(quit_signal.recv()), Box::pin(quit_signal.recv()),
Box::pin(interrupt_signal.recv()), Box::pin(interrupt_signal.recv()),
]) ])
.await; .await;
} }
#[cfg(not(unix))] #[cfg(not(unix))]
async fn build_signals_fut() { async fn build_signals_fut() {
tokio::signal::ctrl_c() tokio::signal::ctrl_c()
.await .await
.expect("failed to install CTRL+C signal handler"); .expect("failed to install CTRL+C signal handler");
} }
fn prompt_password() -> ZeroingString { fn prompt_password() -> ZeroingString {
ZeroingString::from(rpassword::prompt_password_stdout("Server password: ").unwrap()) ZeroingString::from(rpassword::prompt_password_stdout("Server password: ").unwrap())
} }
fn prompt_password_confirm() -> ZeroingString { fn prompt_password_confirm() -> ZeroingString {
let mut first = "first".to_string(); let mut first = "first".to_string();
let mut second = "second".to_string(); let mut second = "second".to_string();
while first != second { while first != second {
first = rpassword::prompt_password_stdout("Server password: ").unwrap(); first = rpassword::prompt_password_stdout("Server password: ").unwrap();
second = rpassword::prompt_password_stdout("Confirm server password: ").unwrap(); second = rpassword::prompt_password_stdout("Confirm server password: ").unwrap();
} }
ZeroingString::from(first) ZeroingString::from(first)
} }
fn prompt_wallet_password(wallet_pass: &Option<&str>) -> ZeroingString { fn prompt_wallet_password(wallet_pass: &Option<&str>) -> ZeroingString {
match *wallet_pass { match *wallet_pass {
Some(wallet_pass) => ZeroingString::from(wallet_pass), Some(wallet_pass) => ZeroingString::from(wallet_pass),
None => { None => {
ZeroingString::from(rpassword::prompt_password_stdout("Wallet password: ").unwrap()) ZeroingString::from(rpassword::prompt_password_stdout("Wallet password: ").unwrap())
} }
} }
} }

View file

@ -38,10 +38,6 @@ args:
help: Address to bind the rpc server to (e.g. 127.0.0.1:3000) help: Address to bind the rpc server to (e.g. 127.0.0.1:3000)
long: bind_addr long: bind_addr
takes_value: true takes_value: true
- socks_addr:
help: Address to bind the SOCKS5 tor proxy to (e.g. 127.0.0.1:3001)
long: socks_addr
takes_value: true
- prev_server: - prev_server:
help: Hex public key of the previous swap/mix server help: Hex public key of the previous swap/mix server
long: prev_server long: prev_server

View file

@ -1,20 +1,21 @@
use grin_onion::crypto::dalek::DalekPublicKey;
use grin_onion::crypto::secp::SecretKey;
use core::num::NonZeroU32; use core::num::NonZeroU32;
use grin_core::global::ChainTypes;
use grin_util::{file, ToHex, ZeroingString};
use grin_wallet_util::OnionV3Address;
use rand::{thread_rng, Rng};
use ring::{aead, pbkdf2};
use serde_derive::{Deserialize, Serialize};
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::result::Result; use std::result::Result;
use grin_core::global::ChainTypes;
use grin_util::{file, ToHex, ZeroingString};
use grin_wallet_util::OnionV3Address;
use rand::{Rng, thread_rng};
use ring::{aead, pbkdf2};
use serde_derive::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use grin_onion::crypto::dalek::DalekPublicKey;
use grin_onion::crypto::secp::SecretKey;
const GRIN_HOME: &str = ".grin"; const GRIN_HOME: &str = ".grin";
const NODE_API_SECRET_FILE_NAME: &str = ".api_secret"; const NODE_API_SECRET_FILE_NAME: &str = ".api_secret";
const WALLET_OWNER_API_SECRET_FILE_NAME: &str = ".owner_api_secret"; const WALLET_OWNER_API_SECRET_FILE_NAME: &str = ".owner_api_secret";
@ -28,8 +29,6 @@ pub struct ServerConfig {
pub interval_s: u32, pub interval_s: u32,
/// socket address the server listener should bind to /// socket address the server listener should bind to
pub addr: SocketAddr, pub addr: SocketAddr,
/// socket address the tor sender should bind to
pub socks_proxy_addr: SocketAddr,
/// foreign api address of the grin node /// foreign api address of the grin node
pub grin_node_url: SocketAddr, pub grin_node_url: SocketAddr,
/// path to file containing api secret for the grin node /// path to file containing api secret for the grin node
@ -181,7 +180,6 @@ struct RawConfig {
nonce: String, nonce: String,
interval_s: u32, interval_s: u32,
addr: SocketAddr, addr: SocketAddr,
socks_proxy_addr: SocketAddr,
grin_node_url: SocketAddr, grin_node_url: SocketAddr,
grin_node_secret_path: Option<String>, grin_node_secret_path: Option<String>,
wallet_owner_url: SocketAddr, wallet_owner_url: SocketAddr,
@ -206,7 +204,6 @@ pub fn write_config(
nonce: encrypted.nonce, nonce: encrypted.nonce,
interval_s: server_config.interval_s, interval_s: server_config.interval_s,
addr: server_config.addr, addr: server_config.addr,
socks_proxy_addr: server_config.socks_proxy_addr,
grin_node_url: server_config.grin_node_url, grin_node_url: server_config.grin_node_url,
grin_node_secret_path: server_config.grin_node_secret_path.clone(), grin_node_secret_path: server_config.grin_node_secret_path.clone(),
wallet_owner_url: server_config.wallet_owner_url, wallet_owner_url: server_config.wallet_owner_url,
@ -243,7 +240,6 @@ pub fn load_config(
key: secret_key, key: secret_key,
interval_s: raw_config.interval_s, interval_s: raw_config.interval_s,
addr: raw_config.addr, addr: raw_config.addr,
socks_proxy_addr: raw_config.socks_proxy_addr,
grin_node_url: raw_config.grin_node_url, grin_node_url: raw_config.grin_node_url,
grin_node_secret_path: raw_config.grin_node_secret_path, grin_node_secret_path: raw_config.grin_node_secret_path,
wallet_owner_url: raw_config.wallet_owner_url, wallet_owner_url: raw_config.wallet_owner_url,
@ -254,10 +250,7 @@ pub fn load_config(
} }
pub fn get_grin_path(chain_type: &ChainTypes) -> PathBuf { pub fn get_grin_path(chain_type: &ChainTypes) -> PathBuf {
let mut grin_path = match dirs::home_dir() { let mut grin_path = dirs::home_dir().unwrap_or_else(|| PathBuf::new());
Some(p) => p,
None => PathBuf::new(),
};
grin_path.push(GRIN_HOME); grin_path.push(GRIN_HOME);
grin_path.push(chain_type.shortname()); grin_path.push(chain_type.shortname());
grin_path grin_path
@ -289,10 +282,12 @@ pub fn wallet_owner_url(_chain_type: &ChainTypes) -> SocketAddr {
#[cfg(test)] #[cfg(test)]
pub mod test_util { pub mod test_util {
use crate::config::ServerConfig; use std::net::TcpListener;
use grin_onion::crypto::dalek::DalekPublicKey; use grin_onion::crypto::dalek::DalekPublicKey;
use secp256k1zkp::SecretKey; use secp256k1zkp::SecretKey;
use std::net::TcpListener;
use crate::config::ServerConfig;
pub fn local_config( pub fn local_config(
server_key: &SecretKey, server_key: &SecretKey,
@ -303,7 +298,6 @@ pub mod test_util {
key: server_key.clone(), key: server_key.clone(),
interval_s: 1, interval_s: 1,
addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?, addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
grin_node_url: "127.0.0.1:3413".parse()?, grin_node_url: "127.0.0.1:3413".parse()?,
grin_node_secret_path: None, grin_node_secret_path: None,
wallet_owner_url: "127.0.0.1:3420".parse()?, wallet_owner_url: "127.0.0.1:3420".parse()?,
@ -317,9 +311,10 @@ pub mod test_util {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use grin_onion::crypto::secp; use grin_onion::crypto::secp;
use super::*;
#[test] #[test]
fn server_key_encrypt() { fn server_key_encrypt() {
let password = ZeroingString::from("password"); let password = ZeroingString::from("password");

127
src/http.rs Normal file
View file

@ -0,0 +1,127 @@
use std::time::Duration;
use grin_api::json_rpc;
use grin_util::to_base64;
use grin_wallet_api::{EncryptedRequest, EncryptedResponse, JsonId};
use hyper::body::Body as HyperBody;
use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use hyper::Request;
use serde_json::json;
use thiserror::Error;
use secp256k1zkp::SecretKey;
/// Error types for HTTP client connections
#[derive(Error, Debug)]
pub enum HttpError {
#[error("Error decrypting response")]
DecryptResponseError(),
#[error("Hyper HTTP error: {0:?}")]
HyperHttpError(hyper::http::Error),
#[error("Hyper request failed with error: {0:?}")]
RequestFailed(hyper::Error),
#[error("Error with response body: {0:?}")]
ResponseBodyError(hyper::Error),
#[error("Error deserializing JSON response: {0:?}")]
ResponseJsonError(serde_json::Error),
#[error("Error decoding JSON-RPC response: {0:?}")]
ResponseParseError(json_rpc::Error),
#[error("Wrong response code: {0}")]
ResponseStatusError(hyper::StatusCode),
}
pub async fn async_send_enc_request<D: serde::de::DeserializeOwned>(
url: &String,
api_secret: &Option<String>,
method: &str,
params: &serde_json::Value,
shared_key: &SecretKey,
) -> Result<D, HttpError> {
let req = json!({
"method": method,
"params": params,
"id": JsonId::IntId(1),
"jsonrpc": "2.0",
});
let enc_req = EncryptedRequest::from_json(&JsonId::IntId(1), &req, &shared_key).unwrap();
let req = build_request(&url, &api_secret, serde_json::to_string(&enc_req).unwrap())?;
let response_str = send_request_async(req).await?;
let enc_res: EncryptedResponse =
serde_json::from_str(&response_str).map_err(HttpError::ResponseJsonError)?;
let decrypted = enc_res
.decrypt(&shared_key)
.map_err(|_| HttpError::DecryptResponseError())?;
let response: json_rpc::Response =
serde_json::from_value(decrypted).map_err(HttpError::ResponseJsonError)?;
let parsed = response
.clone()
.into_result()
.map_err(HttpError::ResponseParseError)?;
Ok(parsed)
}
pub async fn async_send_json_request<D: serde::de::DeserializeOwned>(
url: &String,
api_secret: &Option<String>,
method: &str,
params: &serde_json::Value,
) -> Result<D, HttpError> {
let req_body = json!({
"method": method,
"params": params,
"id": 1,
"jsonrpc": "2.0",
});
let req = build_request(&url, &api_secret, serde_json::to_string(&req_body).unwrap())?;
let data = send_request_async(req).await?;
let ser: json_rpc::Response =
serde_json::from_str(&data).map_err(HttpError::ResponseJsonError)?;
let parsed = ser
.clone()
.into_result()
.map_err(HttpError::ResponseParseError)?;
Ok(parsed)
}
pub fn build_request(
url: &String,
api_secret: &Option<String>,
req_body: String,
) -> Result<Request<HyperBody>, HttpError> {
let mut req_builder = hyper::Request::builder();
if let Some(api_secret) = api_secret {
let basic_auth = format!("Basic {}", to_base64(&format!("grin:{}", api_secret)));
req_builder = req_builder.header(AUTHORIZATION, basic_auth);
}
req_builder
.method(hyper::Method::POST)
.uri(url)
.header(USER_AGENT, "grin-client")
.header(ACCEPT, "application/json")
.header(CONTENT_TYPE, "application/json")
.body(HyperBody::from(req_body))
.map_err(HttpError::HyperHttpError)
}
async fn send_request_async(req: Request<HyperBody>) -> Result<String, HttpError> {
let client = hyper::Client::builder()
.pool_idle_timeout(Duration::from_secs(30))
.build_http();
let resp = client
.request(req)
.await
.map_err(HttpError::RequestFailed)?;
if !resp.status().is_success() {
return Err(HttpError::ResponseStatusError(resp.status()));
}
let raw = hyper::body::to_bytes(resp)
.await
.map_err(HttpError::ResponseBodyError)?;
Ok(String::from_utf8_lossy(&raw).to_string())
}

View file

@ -1,8 +1,9 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
pub mod client;
pub mod config; pub mod config;
pub mod http;
pub mod mix_client;
pub mod node; pub mod node;
pub mod servers; pub mod servers;
pub mod store; pub mod store;
@ -10,8 +11,8 @@ pub mod tor;
pub mod tx; pub mod tx;
pub mod wallet; pub mod wallet;
pub use client::MixClient;
pub use config::ServerConfig; pub use config::ServerConfig;
pub use mix_client::MixClient;
pub use node::{GrinNode, HttpGrinNode, NodeError}; pub use node::{GrinNode, HttpGrinNode, NodeError};
pub use servers::mix::{MixError, MixServer}; pub use servers::mix::{MixError, MixServer};
pub use servers::mix_rpc::listen as mix_listen; pub use servers::mix_rpc::listen as mix_listen;

View file

@ -1,27 +1,30 @@
use crate::config::ServerConfig; use std::sync::Arc;
use crate::servers::mix_rpc::{MixReq, MixResp};
use crate::tor;
use grin_onion::crypto::dalek::{self, DalekPublicKey};
use grin_onion::onion::Onion;
use async_trait::async_trait; use async_trait::async_trait;
use grin_api::json_rpc::{build_request, Response}; use grin_api::json_rpc::{build_request, Response};
use grin_core::ser; use grin_core::ser;
use grin_core::ser::ProtocolVersion; use grin_core::ser::ProtocolVersion;
use grin_wallet_util::OnionV3Address; use grin_wallet_util::OnionV3Address;
use hyper::client::HttpConnector;
use hyper::header::{ACCEPT, CONTENT_TYPE, USER_AGENT};
use hyper_socks2::SocksConnector;
use serde_json; use serde_json;
use serde_json::json;
use thiserror::Error; use thiserror::Error;
use tor_rtcompat::Runtime;
use grin_onion::crypto::dalek::{self, DalekPublicKey};
use grin_onion::onion::Onion;
use crate::config::ServerConfig;
use crate::servers::mix_rpc::{MixReq, MixResp};
use crate::tor::TorService;
use crate::{http, tor};
/// Error types for interacting with nodes /// Error types for interacting with nodes
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ClientError { pub enum MixClientError {
#[error("Tor Error: {0:?}")] #[error("Tor Error: {0:?}")]
Tor(tor::TorError), Tor(tor::TorError),
#[error("API Error: {0:?}")] #[error("Communication Error: {0:?}")]
API(grin_api::Error), CommError(http::HttpError),
#[error("Dalek Error: {0:?}")] #[error("Dalek Error: {0:?}")]
Dalek(dalek::DalekError), Dalek(dalek::DalekError),
#[error("Error decoding JSON response: {0:?}")] #[error("Error decoding JSON response: {0:?}")]
@ -36,18 +39,23 @@ pub enum ClientError {
#[async_trait] #[async_trait]
pub trait MixClient: Send + Sync { pub trait MixClient: Send + Sync {
/// Swaps the outputs provided and returns the final swapped outputs and kernels. /// Swaps the outputs provided and returns the final swapped outputs and kernels.
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, ClientError>; async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError>;
} }
pub struct MixClientImpl { pub struct MixClientImpl<R: Runtime> {
config: ServerConfig, config: ServerConfig,
tor: Arc<grin_util::Mutex<TorService<R>>>,
addr: OnionV3Address, addr: OnionV3Address,
} }
impl MixClientImpl { impl<R: Runtime> MixClientImpl<R> {
pub fn new(config: ServerConfig, next_pubkey: DalekPublicKey) -> Self { pub fn new(
config: ServerConfig,
tor: Arc<grin_util::Mutex<TorService<R>>>,
next_pubkey: DalekPublicKey,
) -> Self {
let addr = OnionV3Address::from_bytes(next_pubkey.as_ref().to_bytes()); let addr = OnionV3Address::from_bytes(next_pubkey.as_ref().to_bytes());
MixClientImpl { config, addr } MixClientImpl { config, tor, addr }
} }
async fn async_send_json_request<D: serde::de::DeserializeOwned>( async fn async_send_json_request<D: serde::de::DeserializeOwned>(
@ -55,61 +63,29 @@ impl MixClientImpl {
addr: &OnionV3Address, addr: &OnionV3Address,
method: &str, method: &str,
params: &serde_json::Value, params: &serde_json::Value,
) -> Result<D, ClientError> { ) -> Result<D, MixClientError> {
let proxy = {
let proxy_uri = format!(
"socks5://{}:{}",
self.config.socks_proxy_addr.ip(),
self.config.socks_proxy_addr.port()
)
.parse()
.unwrap();
let mut connector = HttpConnector::new();
connector.enforce_http(false);
let proxy_connector = SocksConnector {
proxy_addr: proxy_uri,
auth: None,
connector,
};
proxy_connector
};
let url = format!("{}/v1", addr.to_http_str()); let url = format!("{}/v1", addr.to_http_str());
let request_str = serde_json::to_string(&build_request(method, params)).unwrap();
let hyper_request =
http::build_request(&url, &None, request_str).map_err(MixClientError::CommError)?;
let body = let hyper_client = self.tor.lock().new_hyper_client();
hyper::body::Body::from(serde_json::to_string(&build_request(method, params)).unwrap()); let res = hyper_client.request(hyper_request).await.unwrap();
let req = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(url)
.header(USER_AGENT, "grin-client")
.header(ACCEPT, "application/json")
.header(CONTENT_TYPE, "application/json")
.body(body)
.map_err(|e| {
ClientError::API(grin_api::Error::RequestError(format!(
"Cannot make request: {}",
e
)))
})?;
let client = hyper::Client::builder().build::<_, hyper::Body>(proxy);
let res = client.request(req).await.unwrap();
let body_bytes = hyper::body::to_bytes(res.into_body()).await.unwrap(); let body_bytes = hyper::body::to_bytes(res.into_body()).await.unwrap();
let res = String::from_utf8(body_bytes.to_vec()).unwrap(); let res = String::from_utf8(body_bytes.to_vec()).unwrap();
let response: Response = let response: Response =
serde_json::from_str(&res).map_err(ClientError::DecodeResponseError)?; serde_json::from_str(&res).map_err(MixClientError::DecodeResponseError)?;
if let Some(ref e) = response.error { if let Some(ref e) = response.error {
return Err(ClientError::ResponseError(e.clone())); return Err(MixClientError::ResponseError(e.clone()));
} }
let result = match response.result.clone() { let result = match response.result.clone() {
Some(r) => serde_json::from_value(r).map_err(ClientError::DecodeResponseError), Some(r) => serde_json::from_value(r).map_err(MixClientError::DecodeResponseError),
None => serde_json::from_value(serde_json::Value::Null) None => serde_json::from_value(serde_json::Value::Null)
.map_err(ClientError::DecodeResponseError), .map_err(MixClientError::DecodeResponseError),
}?; }?;
Ok(result) Ok(result)
@ -117,34 +93,29 @@ impl MixClientImpl {
} }
#[async_trait] #[async_trait]
impl MixClient for MixClientImpl { impl<R: Runtime> MixClient for MixClientImpl<R> {
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, ClientError> { async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError> {
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap(); let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
let sig = let sig =
dalek::sign(&self.config.key, serialized.as_slice()).map_err(ClientError::Dalek)?; dalek::sign(&self.config.key, serialized.as_slice()).map_err(MixClientError::Dalek)?;
// println!(
// "Created sig ({:?}) with public key ({}) for server ({})",
// &sig,
// DalekPublicKey::from_secret(&self.config.key).to_hex(),
// self.config.next_server.as_ref().unwrap().to_hex()
// );
let mix = MixReq::new(onions.clone(), sig); let mix = MixReq::new(onions.clone(), sig);
let params = serde_json::json!([mix]); self.async_send_json_request::<MixResp>(&self.addr, "mix", &json!([mix]))
self.async_send_json_request::<MixResp>(&self.addr, "mix", &params)
.await .await
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod mock { pub mod mock {
use super::{ClientError, MixClient}; use std::collections::HashMap;
use async_trait::async_trait;
use grin_onion::onion::Onion; use grin_onion::onion::Onion;
use crate::servers::mix_rpc::MixResp; use crate::servers::mix_rpc::MixResp;
use async_trait::async_trait;
use std::collections::HashMap; use super::{MixClient, MixClientError};
pub struct MockMixClient { pub struct MockMixClient {
results: HashMap<Vec<Onion>, MixResp>, results: HashMap<Vec<Onion>, MixResp>,
@ -164,27 +135,33 @@ pub mod mock {
#[async_trait] #[async_trait]
impl MixClient for MockMixClient { impl MixClient for MockMixClient {
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, ClientError> { async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError> {
self.results self.results
.get(onions) .get(onions)
.map(|r| Ok(r.clone())) .map(|r| Ok(r.clone()))
.unwrap_or(Err(ClientError::Custom("No response set for input".into()))) .unwrap_or(Err(MixClientError::Custom(
"No response set for input".into(),
)))
} }
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod test_util { pub mod test_util {
use super::{ClientError, MixClient}; use std::sync::Arc;
use crate::servers::mix::MixServer;
use crate::servers::mix_rpc::MixResp;
use async_trait::async_trait; use async_trait::async_trait;
use grin_core::ser; use grin_core::ser;
use grin_core::ser::ProtocolVersion; use grin_core::ser::ProtocolVersion;
use grin_onion::crypto::dalek::{self, DalekPublicKey}; use grin_onion::crypto::dalek::{self, DalekPublicKey};
use grin_onion::crypto::secp::SecretKey; use grin_onion::crypto::secp::SecretKey;
use grin_onion::onion::Onion; use grin_onion::onion::Onion;
use std::sync::Arc;
use crate::servers::mix::MixServer;
use crate::servers::mix_rpc::MixResp;
use super::{MixClient, MixClientError};
/// Implementation of the 'MixClient' trait that calls a mix server implementation directly. /// Implementation of the 'MixClient' trait that calls a mix server implementation directly.
/// No JSON-RPC serialization or socket communication occurs. /// No JSON-RPC serialization or socket communication occurs.
@ -196,9 +173,10 @@ pub mod test_util {
#[async_trait] #[async_trait]
impl MixClient for DirectMixClient { impl MixClient for DirectMixClient {
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, ClientError> { async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError> {
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap(); let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
let sig = dalek::sign(&self.key, serialized.as_slice()).map_err(ClientError::Dalek)?; let sig =
dalek::sign(&self.key, serialized.as_slice()).map_err(MixClientError::Dalek)?;
sig.verify( sig.verify(
&DalekPublicKey::from_secret(&self.key), &DalekPublicKey::from_secret(&self.key),

View file

@ -1,19 +1,20 @@
use grin_onion::crypto::secp::Commitment;
use grin_api::json_rpc::{build_request, Request, Response};
use grin_api::{client, LocatedTxKernel};
use grin_api::{OutputPrintable, OutputType, Tip};
use grin_core::consensus::COINBASE_MATURITY;
use grin_core::core::{Committed, Input, OutputFeatures, Transaction};
use grin_util::ToHex;
use async_trait::async_trait;
use serde_json::json;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use grin_api::LocatedTxKernel;
use grin_api::{OutputPrintable, OutputType, Tip};
use grin_core::consensus::COINBASE_MATURITY;
use grin_core::core::hash::Hash; use grin_core::core::hash::Hash;
use grin_core::core::{Committed, Input, OutputFeatures, Transaction};
use grin_util::ToHex;
use serde_json::json;
use thiserror::Error; use thiserror::Error;
use grin_onion::crypto::secp::Commitment;
use crate::http;
#[async_trait] #[async_trait]
pub trait GrinNode: Send + Sync { pub trait GrinNode: Send + Sync {
/// Retrieves the unspent output with a matching commitment /// Retrieves the unspent output with a matching commitment
@ -22,8 +23,8 @@ pub trait GrinNode: Send + Sync {
output_commit: &Commitment, output_commit: &Commitment,
) -> Result<Option<OutputPrintable>, NodeError>; ) -> Result<Option<OutputPrintable>, NodeError>;
/// Gets the height and hash of the chain tip /// Gets the height and hash of the chain tip
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError>; async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError>;
/// Posts a transaction to the grin node /// Posts a transaction to the grin node
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError>; async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError>;
@ -47,6 +48,8 @@ pub enum NodeError {
DecodeResponseError(serde_json::Error), DecodeResponseError(serde_json::Error),
#[error("JSON-RPC API communication error: {0:?}")] #[error("JSON-RPC API communication error: {0:?}")]
ApiCommError(grin_api::Error), ApiCommError(grin_api::Error),
#[error("Client error: {0:?}")]
NodeCommError(http::HttpError),
#[error("Error decoding JSON-RPC response: {0:?}")] #[error("Error decoding JSON-RPC response: {0:?}")]
ResponseParseError(grin_api::json_rpc::Error), ResponseParseError(grin_api::json_rpc::Error),
} }
@ -109,21 +112,24 @@ pub async fn async_build_input(
Ok(None) Ok(None)
} }
pub async fn async_is_tx_valid(node: &Arc<dyn GrinNode>, tx: &Transaction) -> Result<bool, NodeError> { pub async fn async_is_tx_valid(
let next_block_height = node.async_get_chain_tip().await?.0 + 1; node: &Arc<dyn GrinNode>,
for input_commit in &tx.inputs_committed() { tx: &Transaction,
if !async_is_spendable(&node, &input_commit, next_block_height).await? { ) -> Result<bool, NodeError> {
return Ok(false); let next_block_height = node.async_get_chain_tip().await?.0 + 1;
} for input_commit in &tx.inputs_committed() {
} if !async_is_spendable(&node, &input_commit, next_block_height).await? {
return Ok(false);
}
}
for output_commit in &tx.outputs_committed() { for output_commit in &tx.outputs_committed() {
if async_is_unspent(&node, &output_commit).await? { if async_is_unspent(&node, &output_commit).await? {
return Ok(false); return Ok(false);
} }
} }
Ok(true) Ok(true)
} }
/// HTTP (JSON-RPC) implementation of the 'GrinNode' trait /// HTTP (JSON-RPC) implementation of the 'GrinNode' trait
@ -149,18 +155,9 @@ impl HttpGrinNode {
params: &serde_json::Value, params: &serde_json::Value,
) -> Result<D, NodeError> { ) -> Result<D, NodeError> {
let url = format!("http://{}{}", self.node_url, ENDPOINT); let url = format!("http://{}{}", self.node_url, ENDPOINT);
let req = build_request(method, params); let parsed = http::async_send_json_request(&url, &self.node_api_secret, &method, &params)
let res = client::post_async::<Request, Response>( .await
url.as_str(), .map_err(NodeError::NodeCommError)?;
&req,
self.node_api_secret.clone(),
)
.await
.map_err(NodeError::ApiCommError)?;
let parsed = res
.clone()
.into_result()
.map_err(NodeError::ResponseParseError)?;
Ok(parsed) Ok(parsed)
} }
} }
@ -194,7 +191,7 @@ impl GrinNode for HttpGrinNode {
Ok(Some(outputs[0].clone())) Ok(Some(outputs[0].clone()))
} }
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> { async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> {
let params = json!([]); let params = json!([]);
let tip_json = self let tip_json = self
.async_send_request::<serde_json::Value>("get_tip", &params) .async_send_request::<serde_json::Value>("get_tip", &params)
@ -202,7 +199,10 @@ impl GrinNode for HttpGrinNode {
let tip = let tip =
serde_json::from_value::<Tip>(tip_json).map_err(NodeError::DecodeResponseError)?; serde_json::from_value::<Tip>(tip_json).map_err(NodeError::DecodeResponseError)?;
Ok((tip.height, Hash::from_hex(tip.last_block_pushed.as_str()).unwrap())) Ok((
tip.height,
Hash::from_hex(tip.last_block_pushed.as_str()).unwrap(),
))
} }
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError> { async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError> {
@ -236,15 +236,17 @@ impl GrinNode for HttpGrinNode {
#[cfg(test)] #[cfg(test)]
pub mod mock { pub mod mock {
use super::{GrinNode, NodeError}; use std::collections::HashMap;
use std::sync::RwLock;
use async_trait::async_trait; use async_trait::async_trait;
use grin_api::{LocatedTxKernel, OutputPrintable, OutputType}; use grin_api::{LocatedTxKernel, OutputPrintable, OutputType};
use grin_core::core::hash::Hash;
use grin_core::core::Transaction; use grin_core::core::Transaction;
use grin_onion::crypto::secp::Commitment; use grin_onion::crypto::secp::Commitment;
use std::collections::HashMap;
use std::sync::RwLock; use super::{GrinNode, NodeError};
use grin_core::core::hash::Hash;
/// Implementation of 'GrinNode' trait that mocks a grin node instance. /// Implementation of 'GrinNode' trait that mocks a grin node instance.
/// Use only for testing purposes. /// Use only for testing purposes.
@ -318,8 +320,8 @@ pub mod mock {
Ok(None) Ok(None)
} }
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> { async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> {
Ok((100, Hash::default())) Ok((100, Hash::default()))
} }
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError> { async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError> {

View file

@ -1,24 +1,26 @@
use crate::client::MixClient; use std::collections::{HashMap, HashSet};
use crate::config::ServerConfig; use std::sync::Arc;
use crate::node::{self, GrinNode};
use crate::tx::{self, TxComponents};
use crate::wallet::Wallet;
use crate::servers::mix_rpc::MixResp;
use async_trait::async_trait; use async_trait::async_trait;
use futures::stream::{self, StreamExt}; use futures::stream::{self, StreamExt};
use grin_core::core::{Output, OutputFeatures, TransactionBody}; use grin_core::core::{Output, OutputFeatures, TransactionBody};
use grin_core::global::DEFAULT_ACCEPT_FEE_BASE; use grin_core::global::DEFAULT_ACCEPT_FEE_BASE;
use grin_core::ser; use grin_core::ser;
use grin_core::ser::ProtocolVersion; use grin_core::ser::ProtocolVersion;
use itertools::Itertools;
use thiserror::Error;
use grin_onion::crypto::dalek::{self, DalekSignature}; use grin_onion::crypto::dalek::{self, DalekSignature};
use grin_onion::onion::{Onion, OnionError, PeeledOnion}; use grin_onion::onion::{Onion, OnionError, PeeledOnion};
use itertools::Itertools;
use secp256k1zkp::key::ZERO_KEY; use secp256k1zkp::key::ZERO_KEY;
use secp256k1zkp::Secp256k1; use secp256k1zkp::Secp256k1;
use std::collections::{HashMap, HashSet};
use std::sync::Arc; use crate::config::ServerConfig;
use thiserror::Error; use crate::mix_client::MixClient;
use crate::node::{self, GrinNode};
use crate::servers::mix_rpc::MixResp;
use crate::tx::{self, TxComponents};
use crate::wallet::Wallet;
/// Mixer error types /// Mixer error types
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -46,7 +48,7 @@ pub enum MixError {
#[error("Wallet error: {0:?}")] #[error("Wallet error: {0:?}")]
WalletError(crate::wallet::WalletError), WalletError(crate::wallet::WalletError),
#[error("Client comm error: {0:?}")] #[error("Client comm error: {0:?}")]
Client(crate::client::ClientError), Client(crate::mix_client::MixClientError),
} }
/// An internal MWixnet server - a "Mixer" /// An internal MWixnet server - a "Mixer"
@ -298,16 +300,17 @@ impl MixServer for MixServerImpl {
#[cfg(test)] #[cfg(test)]
mod test_util { mod test_util {
use crate::client::test_util::DirectMixClient; use std::sync::Arc;
use crate::client::MixClient;
use crate::config;
use crate::node::mock::MockGrinNode;
use crate::servers::mix::MixServerImpl;
use crate::wallet::mock::MockWallet;
use grin_onion::crypto::dalek::DalekPublicKey; use grin_onion::crypto::dalek::DalekPublicKey;
use secp256k1zkp::SecretKey; use secp256k1zkp::SecretKey;
use std::sync::Arc;
use crate::config;
use crate::mix_client::MixClient;
use crate::mix_client::test_util::DirectMixClient;
use crate::node::mock::MockGrinNode;
use crate::servers::mix::MixServerImpl;
use crate::wallet::mock::MockWallet;
pub fn new_mixer( pub fn new_mixer(
server_key: &SecretKey, server_key: &SecretKey,
@ -340,18 +343,20 @@ mod test_util {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::client::MixClient; use std::collections::HashSet;
use crate::node::mock::MockGrinNode; use std::sync::Arc;
use ::function_name::named; use ::function_name::named;
use grin_onion::{create_onion, Hop, new_hop};
use grin_onion::crypto::dalek::DalekPublicKey; use grin_onion::crypto::dalek::DalekPublicKey;
use grin_onion::crypto::secp::{self, Commitment}; use grin_onion::crypto::secp::{self, Commitment};
use grin_onion::test_util as onion_test_util; use grin_onion::test_util as onion_test_util;
use grin_onion::{create_onion, new_hop, Hop};
use secp256k1zkp::pedersen::RangeProof; use secp256k1zkp::pedersen::RangeProof;
use secp256k1zkp::SecretKey; use secp256k1zkp::SecretKey;
use std::collections::HashSet;
use std::sync::Arc; use crate::mix_client::MixClient;
use crate::node::mock::MockGrinNode;
macro_rules! init_test { macro_rules! init_test {
() => {{ () => {{

View file

@ -1,19 +1,20 @@
use crate::client::MixClient; use std::sync::Arc;
use crate::config::ServerConfig;
use crate::node::GrinNode;
use crate::servers::mix::{MixError, MixServer, MixServerImpl};
use crate::wallet::Wallet;
use crate::tx::TxComponents;
use futures::FutureExt; use futures::FutureExt;
use jsonrpc_derive::rpc;
use jsonrpc_http_server::{DomainsValidation, ServerBuilder};
use jsonrpc_http_server::jsonrpc_core::{self, BoxFuture, IoHandler};
use serde::{Deserialize, Serialize};
use grin_onion::crypto::dalek::{self, DalekSignature}; use grin_onion::crypto::dalek::{self, DalekSignature};
use grin_onion::onion::Onion; use grin_onion::onion::Onion;
use jsonrpc_core::BoxFuture;
use jsonrpc_derive::rpc; use crate::config::ServerConfig;
use jsonrpc_http_server::jsonrpc_core::{self as jsonrpc, IoHandler}; use crate::mix_client::MixClient;
use jsonrpc_http_server::{DomainsValidation, ServerBuilder}; use crate::node::GrinNode;
use serde::{Deserialize, Serialize}; use crate::servers::mix::{MixError, MixServer, MixServerImpl};
use std::sync::Arc; use crate::tx::TxComponents;
use crate::wallet::Wallet;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct MixReq { pub struct MixReq {
@ -37,7 +38,7 @@ impl MixReq {
#[rpc(server)] #[rpc(server)]
pub trait MixAPI { pub trait MixAPI {
#[rpc(name = "mix")] #[rpc(name = "mix")]
fn mix(&self, mix: MixReq) -> BoxFuture<jsonrpc::Result<MixResp>>; fn mix(&self, mix: MixReq) -> BoxFuture<jsonrpc_core::Result<MixResp>>;
} }
#[derive(Clone)] #[derive(Clone)]
@ -67,14 +68,14 @@ impl RPCMixServer {
} }
} }
impl From<MixError> for jsonrpc::Error { impl From<MixError> for jsonrpc_core::Error {
fn from(e: MixError) -> Self { fn from(e: MixError) -> Self {
jsonrpc::Error::invalid_params(e.to_string()) jsonrpc_core::Error::invalid_params(e.to_string())
} }
} }
impl MixAPI for RPCMixServer { impl MixAPI for RPCMixServer {
fn mix(&self, mix: MixReq) -> BoxFuture<jsonrpc::Result<MixResp>> { fn mix(&self, mix: MixReq) -> BoxFuture<jsonrpc_core::Result<MixResp>> {
let server = self.server.clone(); let server = self.server.clone();
async move { async move {
let response = server let response = server

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,21 @@
use crate::client::MixClient; use std::sync::Arc;
use futures::FutureExt;
use jsonrpc_core::{BoxFuture, Value};
use jsonrpc_derive::rpc;
use jsonrpc_http_server::{DomainsValidation, ServerBuilder};
use serde::{Deserialize, Serialize};
use grin_onion::crypto::comsig::{self, ComSignature};
use grin_onion::onion::Onion;
use crate::config::ServerConfig; use crate::config::ServerConfig;
use crate::mix_client::MixClient;
use crate::node::GrinNode; use crate::node::GrinNode;
use crate::servers::swap::{SwapError, SwapServer, SwapServerImpl}; use crate::servers::swap::{SwapError, SwapServer, SwapServerImpl};
use crate::store::SwapStore; use crate::store::SwapStore;
use crate::wallet::Wallet; use crate::wallet::Wallet;
use futures::FutureExt;
use grin_onion::crypto::comsig::{self, ComSignature};
use grin_onion::onion::Onion;
use jsonrpc_core::Value;
use jsonrpc_derive::rpc;
use jsonrpc_http_server::jsonrpc_core::*;
use jsonrpc_http_server::{DomainsValidation, ServerBuilder};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SwapReq { pub struct SwapReq {
onion: Onion, onion: Onion,
@ -37,7 +38,7 @@ struct RPCSwapServer {
impl RPCSwapServer { impl RPCSwapServer {
/// Spin up an instance of the JSON-RPC HTTP server. /// Spin up an instance of the JSON-RPC HTTP server.
fn start_http(&self, runtime_handle: tokio::runtime::Handle) -> jsonrpc_http_server::Server { fn start_http(&self, runtime_handle: tokio::runtime::Handle) -> jsonrpc_http_server::Server {
let mut io = IoHandler::new(); let mut io = jsonrpc_core::IoHandler::new();
io.extend_with(RPCSwapServer::to_delegate(self.clone())); io.extend_with(RPCSwapServer::to_delegate(self.clone()));
ServerBuilder::new(io) ServerBuilder::new(io)
@ -55,15 +56,15 @@ impl RPCSwapServer {
} }
} }
impl From<SwapError> for Error { impl From<SwapError> for jsonrpc_core::Error {
fn from(e: SwapError) -> Self { fn from(e: SwapError) -> Self {
match e { match e {
SwapError::UnknownError(_) => Error { SwapError::UnknownError(_) => jsonrpc_core::Error {
message: e.to_string(), message: e.to_string(),
code: ErrorCode::InternalError, code: jsonrpc_core::ErrorCode::InternalError,
data: None, data: None,
}, },
_ => Error::invalid_params(e.to_string()), _ => jsonrpc_core::Error::invalid_params(e.to_string()),
} }
} }
} }
@ -115,20 +116,21 @@ pub fn listen(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::config::ServerConfig;
use crate::servers::swap::mock::MockSwapServer;
use crate::servers::swap::{SwapError, SwapServer};
use crate::servers::swap_rpc::{RPCSwapServer, SwapReq};
use grin_onion::create_onion;
use grin_onion::crypto::comsig::ComSignature;
use grin_onion::crypto::secp;
use std::net::TcpListener; use std::net::TcpListener;
use std::sync::Arc; use std::sync::Arc;
use hyper::{Body, Client, Request, Response}; use hyper::{Body, Client, Request, Response};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use grin_onion::create_onion;
use grin_onion::crypto::comsig::ComSignature;
use grin_onion::crypto::secp;
use crate::config::ServerConfig;
use crate::servers::swap::{SwapError, SwapServer};
use crate::servers::swap::mock::MockSwapServer;
use crate::servers::swap_rpc::{RPCSwapServer, SwapReq};
async fn body_to_string(req: Response<Body>) -> String { async fn body_to_string(req: Response<Body>) -> String {
let body_bytes = hyper::body::to_bytes(req.into_body()).await.unwrap(); let body_bytes = hyper::body::to_bytes(req.into_body()).await.unwrap();
String::from_utf8(body_bytes.to_vec()).unwrap() String::from_utf8(body_bytes.to_vec()).unwrap()
@ -144,7 +146,6 @@ mod tests {
key: secp::random_secret(), key: secp::random_secret(),
interval_s: 1, interval_s: 1,
addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?, addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
grin_node_url: "127.0.0.1:3413".parse()?, grin_node_url: "127.0.0.1:3413".parse()?,
grin_node_secret_path: None, grin_node_secret_path: None,
wallet_owner_url: "127.0.0.1:3420".parse()?, wallet_owner_url: "127.0.0.1:3420".parse()?,
@ -186,8 +187,7 @@ mod tests {
/// Demonstrates a successful swap response /// Demonstrates a successful swap response
#[test] #[test]
fn swap_success() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { fn swap_success() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut rt = tokio::runtime::Builder::new() let rt = tokio::runtime::Builder::new_multi_thread()
.threaded_scheduler()
.enable_all() .enable_all()
.build()?; .build()?;
let commitment = secp::commit(1234, &secp::random_secret())?; let commitment = secp::commit(1234, &secp::random_secret())?;
@ -214,8 +214,7 @@ mod tests {
#[test] #[test]
fn swap_bad_request() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { fn swap_bad_request() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut rt = tokio::runtime::Builder::new() let rt = tokio::runtime::Builder::new_multi_thread()
.threaded_scheduler()
.enable_all() .enable_all()
.build()?; .build()?;
let server: Arc<Mutex<dyn SwapServer>> = Arc::new(Mutex::new(MockSwapServer::new())); let server: Arc<Mutex<dyn SwapServer>> = Arc::new(Mutex::new(MockSwapServer::new()));
@ -235,8 +234,7 @@ mod tests {
/// Returns "Commitment not found" when there's no matching output in the UTXO set. /// Returns "Commitment not found" when there's no matching output in the UTXO set.
#[test] #[test]
fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut rt = tokio::runtime::Builder::new() let rt = tokio::runtime::Builder::new_multi_thread()
.threaded_scheduler()
.enable_all() .enable_all()
.build()?; .build()?;

View file

@ -1,16 +1,16 @@
use grin_core::core::hash::Hash;
use grin_onion::crypto::secp::{self, Commitment, RangeProof, SecretKey};
use grin_onion::onion::Onion;
use grin_onion::util::{read_optional, write_optional};
use grin_core::core::{Input, Transaction}; use grin_core::core::{Input, Transaction};
use grin_core::core::hash::Hash;
use grin_core::ser::{ use grin_core::ser::{
self, DeserializationMode, ProtocolVersion, Readable, Reader, Writeable, Writer, self, DeserializationMode, ProtocolVersion, Readable, Reader, Writeable, Writer,
}; };
use grin_store::{self as store, Store}; use grin_store::{self as store, Store};
use grin_util::ToHex; use grin_util::ToHex;
use thiserror::Error; use thiserror::Error;
use grin_onion::crypto::secp::{self, Commitment, RangeProof, SecretKey};
use grin_onion::onion::Onion;
use grin_onion::util::{read_optional, write_optional};
const DB_NAME: &str = "swap"; const DB_NAME: &str = "swap";
const STORE_SUBPATH: &str = "swaps"; const STORE_SUBPATH: &str = "swaps";
@ -23,380 +23,389 @@ const TX_PREFIX: u8 = b'T';
/// Swap statuses /// Swap statuses
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum SwapStatus { pub enum SwapStatus {
Unprocessed, Unprocessed,
InProcess { InProcess {
kernel_commit: Commitment, kernel_commit: Commitment,
}, },
Completed { Completed {
kernel_commit: Commitment, kernel_commit: Commitment,
block_hash: Hash, block_hash: Hash,
}, },
Failed, Failed,
} }
impl Writeable for SwapStatus { impl Writeable for SwapStatus {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
match self { match self {
SwapStatus::Unprocessed => { SwapStatus::Unprocessed => {
writer.write_u8(0)?; writer.write_u8(0)?;
} }
SwapStatus::InProcess { kernel_commit } => { SwapStatus::InProcess { kernel_commit } => {
writer.write_u8(1)?; writer.write_u8(1)?;
kernel_commit.write(writer)?; kernel_commit.write(writer)?;
} }
SwapStatus::Completed { SwapStatus::Completed {
kernel_commit, kernel_commit,
block_hash, block_hash,
} => { } => {
writer.write_u8(2)?; writer.write_u8(2)?;
kernel_commit.write(writer)?; kernel_commit.write(writer)?;
block_hash.write(writer)?; block_hash.write(writer)?;
} }
SwapStatus::Failed => { SwapStatus::Failed => {
writer.write_u8(3)?; writer.write_u8(3)?;
} }
}; };
Ok(()) Ok(())
} }
} }
impl Readable for SwapStatus { impl Readable for SwapStatus {
fn read<R: Reader>(reader: &mut R) -> Result<SwapStatus, ser::Error> { fn read<R: Reader>(reader: &mut R) -> Result<SwapStatus, ser::Error> {
let status = match reader.read_u8()? { let status = match reader.read_u8()? {
0 => SwapStatus::Unprocessed, 0 => SwapStatus::Unprocessed,
1 => { 1 => {
let kernel_commit = Commitment::read(reader)?; let kernel_commit = Commitment::read(reader)?;
SwapStatus::InProcess { kernel_commit } SwapStatus::InProcess { kernel_commit }
} }
2 => { 2 => {
let kernel_commit = Commitment::read(reader)?; let kernel_commit = Commitment::read(reader)?;
let block_hash = Hash::read(reader)?; let block_hash = Hash::read(reader)?;
SwapStatus::Completed { SwapStatus::Completed {
kernel_commit, kernel_commit,
block_hash, block_hash,
} }
} }
3 => SwapStatus::Failed, 3 => SwapStatus::Failed,
_ => { _ => {
return Err(ser::Error::CorruptedData); return Err(ser::Error::CorruptedData);
} }
}; };
Ok(status) Ok(status)
} }
} }
/// Data needed to swap a single output. /// Data needed to swap a single output.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct SwapData { pub struct SwapData {
/// The total excess for the output commitment /// The total excess for the output commitment
pub excess: SecretKey, pub excess: SecretKey,
/// The derived output commitment after applying excess and fee /// The derived output commitment after applying excess and fee
pub output_commit: Commitment, pub output_commit: Commitment,
/// The rangeproof, included only for the final hop (node N) /// The rangeproof, included only for the final hop (node N)
pub rangeproof: Option<RangeProof>, pub rangeproof: Option<RangeProof>,
/// Transaction input being spent /// Transaction input being spent
pub input: Input, pub input: Input,
/// Transaction fee /// Transaction fee
pub fee: u64, pub fee: u64,
/// The remaining onion after peeling off our layer /// The remaining onion after peeling off our layer
pub onion: Onion, pub onion: Onion,
/// The status of the swap /// The status of the swap
pub status: SwapStatus, pub status: SwapStatus,
} }
impl Writeable for SwapData { impl Writeable for SwapData {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(CURRENT_SWAP_VERSION)?; writer.write_u8(CURRENT_SWAP_VERSION)?;
writer.write_fixed_bytes(&self.excess)?; writer.write_fixed_bytes(&self.excess)?;
writer.write_fixed_bytes(&self.output_commit)?; writer.write_fixed_bytes(&self.output_commit)?;
write_optional(writer, &self.rangeproof)?; write_optional(writer, &self.rangeproof)?;
self.input.write(writer)?; self.input.write(writer)?;
writer.write_u64(self.fee.into())?; writer.write_u64(self.fee.into())?;
self.onion.write(writer)?; self.onion.write(writer)?;
self.status.write(writer)?; self.status.write(writer)?;
Ok(()) Ok(())
} }
} }
impl Readable for SwapData { impl Readable for SwapData {
fn read<R: Reader>(reader: &mut R) -> Result<SwapData, ser::Error> { fn read<R: Reader>(reader: &mut R) -> Result<SwapData, ser::Error> {
let version = reader.read_u8()?; let version = reader.read_u8()?;
if version != CURRENT_SWAP_VERSION { if version != CURRENT_SWAP_VERSION {
return Err(ser::Error::UnsupportedProtocolVersion); return Err(ser::Error::UnsupportedProtocolVersion);
} }
let excess = secp::read_secret_key(reader)?; let excess = secp::read_secret_key(reader)?;
let output_commit = Commitment::read(reader)?; let output_commit = Commitment::read(reader)?;
let rangeproof = read_optional(reader)?; let rangeproof = read_optional(reader)?;
let input = Input::read(reader)?; let input = Input::read(reader)?;
let fee = reader.read_u64()?; let fee = reader.read_u64()?;
let onion = Onion::read(reader)?; let onion = Onion::read(reader)?;
let status = SwapStatus::read(reader)?; let status = SwapStatus::read(reader)?;
Ok(SwapData { Ok(SwapData {
excess, excess,
output_commit, output_commit,
rangeproof, rangeproof,
input, input,
fee, fee,
onion, onion,
status, status,
}) })
} }
} }
/// A transaction created as part of a swap round. /// A transaction created as part of a swap round.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct SwapTx { pub struct SwapTx {
pub tx: Transaction, pub tx: Transaction,
pub chain_tip: (u64, Hash), pub chain_tip: (u64, Hash),
// TODO: Include status // TODO: Include status
} }
impl Writeable for SwapTx { impl Writeable for SwapTx {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(CURRENT_TX_VERSION)?; writer.write_u8(CURRENT_TX_VERSION)?;
self.tx.write(writer)?; self.tx.write(writer)?;
writer.write_u64(self.chain_tip.0)?; writer.write_u64(self.chain_tip.0)?;
self.chain_tip.1.write(writer)?; self.chain_tip.1.write(writer)?;
Ok(()) Ok(())
} }
} }
impl Readable for SwapTx { impl Readable for SwapTx {
fn read<R: Reader>(reader: &mut R) -> Result<SwapTx, ser::Error> { fn read<R: Reader>(reader: &mut R) -> Result<SwapTx, ser::Error> {
let version = reader.read_u8()?; let version = reader.read_u8()?;
if version != CURRENT_TX_VERSION { if version != CURRENT_TX_VERSION {
return Err(ser::Error::UnsupportedProtocolVersion); return Err(ser::Error::UnsupportedProtocolVersion);
} }
let tx = Transaction::read(reader)?; let tx = Transaction::read(reader)?;
let height = reader.read_u64()?; let height = reader.read_u64()?;
let block_hash = Hash::read(reader)?; let block_hash = Hash::read(reader)?;
Ok(SwapTx { Ok(SwapTx {
tx, tx,
chain_tip: (height, block_hash), chain_tip: (height, block_hash),
}) })
} }
} }
/// Storage facility for swap data. /// Storage facility for swap data.
pub struct SwapStore { pub struct SwapStore {
db: Store, db: Store,
} }
/// Store error types /// Store error types
#[derive(Clone, Error, Debug, PartialEq)] #[derive(Clone, Error, Debug, PartialEq)]
pub enum StoreError { pub enum StoreError {
#[error("Swap entry already exists for '{0:?}'")] #[error("Swap entry already exists for '{0:?}'")]
AlreadyExists(Commitment), AlreadyExists(Commitment),
#[error("Error occurred while attempting to open db: {0}")] #[error("Entry does not exist for '{0:?}'")]
OpenError(store::lmdb::Error), NotFound(Commitment),
#[error("Serialization error occurred: {0}")] #[error("Error occurred while attempting to open db: {0}")]
SerializationError(ser::Error), OpenError(store::lmdb::Error),
#[error("Error occurred while attempting to read from db: {0}")] #[error("Serialization error occurred: {0}")]
ReadError(store::lmdb::Error), SerializationError(ser::Error),
#[error("Error occurred while attempting to write to db: {0}")] #[error("Error occurred while attempting to read from db: {0}")]
WriteError(store::lmdb::Error), ReadError(store::lmdb::Error),
#[error("Error occurred while attempting to write to db: {0}")]
WriteError(store::lmdb::Error),
} }
impl From<ser::Error> for StoreError { impl From<ser::Error> for StoreError {
fn from(e: ser::Error) -> StoreError { fn from(e: ser::Error) -> StoreError {
StoreError::SerializationError(e) StoreError::SerializationError(e)
} }
} }
impl SwapStore { impl SwapStore {
/// Create new chain store /// Create new chain store
pub fn new(db_root: &str) -> Result<SwapStore, StoreError> { pub fn new(db_root: &str) -> Result<SwapStore, StoreError> {
let db = Store::new(db_root, Some(DB_NAME), Some(STORE_SUBPATH), None) let db = Store::new(db_root, Some(DB_NAME), Some(STORE_SUBPATH), None)
.map_err(StoreError::OpenError)?; .map_err(StoreError::OpenError)?;
Ok(SwapStore { db }) Ok(SwapStore { db })
} }
/// Writes a single key-value pair to the database /// Writes a single key-value pair to the database
fn write<K: AsRef<[u8]>>( fn write<K: AsRef<[u8]>>(
&self, &self,
prefix: u8, prefix: u8,
k: K, k: K,
value: &Vec<u8>, value: &Vec<u8>,
overwrite: bool, overwrite: bool,
) -> Result<bool, store::lmdb::Error> { ) -> Result<bool, store::lmdb::Error> {
let batch = self.db.batch()?; let batch = self.db.batch()?;
let key = store::to_key(prefix, k); let key = store::to_key(prefix, k);
if !overwrite && batch.exists(&key[..])? { if !overwrite && batch.exists(&key[..])? {
Ok(false) Ok(false)
} else { } else {
batch.put(&key[..], &value[..])?; batch.put(&key[..], &value[..])?;
batch.commit()?; batch.commit()?;
Ok(true) Ok(true)
} }
} }
/// Reads a single value by key /// Reads a single value by key
fn read<K: AsRef<[u8]> + Copy, V: Readable>(&self, prefix: u8, k: K) -> Result<V, StoreError> { fn read<K: AsRef<[u8]> + Copy, V: Readable>(&self, prefix: u8, k: K) -> Result<V, StoreError> {
store::option_to_not_found(self.db.get_ser(&store::to_key(prefix, k), None), || { store::option_to_not_found(self.db.get_ser(&store::to_key(prefix, k), None), || {
format!("{}:{}", prefix, k.to_hex()) format!("{}:{}", prefix, k.to_hex())
}) })
.map_err(StoreError::ReadError) .map_err(StoreError::ReadError)
} }
/// Saves a swap to the database /// Saves a swap to the database
pub fn save_swap(&self, s: &SwapData, overwrite: bool) -> Result<(), StoreError> { pub fn save_swap(&self, s: &SwapData, overwrite: bool) -> Result<(), StoreError> {
let data = ser::ser_vec(&s, ProtocolVersion::local())?; let data = ser::ser_vec(&s, ProtocolVersion::local())?;
let saved = self let saved = self
.write(SWAP_PREFIX, &s.input.commit, &data, overwrite) .write(SWAP_PREFIX, &s.input.commit, &data, overwrite)
.map_err(StoreError::WriteError)?; .map_err(StoreError::WriteError)?;
if !saved { if !saved {
Err(StoreError::AlreadyExists(s.input.commit.clone())) Err(StoreError::AlreadyExists(s.input.commit.clone()))
} else { } else {
Ok(()) Ok(())
} }
} }
/// Iterator over all swaps. /// Iterator over all swaps.
pub fn swaps_iter(&self) -> Result<impl Iterator<Item=SwapData>, StoreError> { pub fn swaps_iter(&self) -> Result<impl Iterator<Item = SwapData>, StoreError> {
let key = store::to_key(SWAP_PREFIX, ""); let key = store::to_key(SWAP_PREFIX, "");
let protocol_version = self.db.protocol_version(); let protocol_version = self.db.protocol_version();
self.db self.db
.iter(&key[..], move |_, mut v| { .iter(&key[..], move |_, mut v| {
ser::deserialize(&mut v, protocol_version, DeserializationMode::default()) ser::deserialize(&mut v, protocol_version, DeserializationMode::default())
.map_err(From::from) .map_err(From::from)
}) })
.map_err(|e| StoreError::ReadError(e)) .map_err(|e| StoreError::ReadError(e))
} }
/// Checks if a matching swap exists in the database /// Checks if a matching swap exists in the database
#[allow(dead_code)] #[allow(dead_code)]
pub fn swap_exists(&self, input_commit: &Commitment) -> Result<bool, StoreError> { pub fn swap_exists(&self, input_commit: &Commitment) -> Result<bool, StoreError> {
let key = store::to_key(SWAP_PREFIX, input_commit); let key = store::to_key(SWAP_PREFIX, input_commit);
self.db self.db
.batch() .batch()
.map_err(StoreError::ReadError)? .map_err(StoreError::ReadError)?
.exists(&key[..]) .exists(&key[..])
.map_err(StoreError::ReadError) .map_err(StoreError::ReadError)
} }
/// Reads a swap from the database /// Reads a swap from the database
pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> { pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> {
self.read(SWAP_PREFIX, input_commit) self.read(SWAP_PREFIX, input_commit)
} }
/// Saves a swap transaction to the database /// Saves a swap transaction to the database
pub fn save_swap_tx(&self, s: &SwapTx) -> Result<(), StoreError> { pub fn save_swap_tx(&self, s: &SwapTx) -> Result<(), StoreError> {
let data = ser::ser_vec(&s, ProtocolVersion::local())?; let data = ser::ser_vec(&s, ProtocolVersion::local())?;
self self.write(
.write(TX_PREFIX, &s.tx.kernels().first().unwrap().excess, &data, true) TX_PREFIX,
.map_err(StoreError::WriteError)?; &s.tx.kernels().first().unwrap().excess,
&data,
true,
)
.map_err(StoreError::WriteError)?;
Ok(()) Ok(())
} }
/// Reads a swap tx from the database /// Reads a swap tx from the database
pub fn get_swap_tx(&self, kernel_excess: &Commitment) -> Result<SwapTx, StoreError> { pub fn get_swap_tx(&self, kernel_excess: &Commitment) -> Result<SwapTx, StoreError> {
self.read(TX_PREFIX, kernel_excess) self.read(TX_PREFIX, kernel_excess)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::store::{StoreError, SwapData, SwapStatus, SwapStore}; use std::cmp::Ordering;
use grin_core::core::{Input, OutputFeatures};
use grin_core::global::{self, ChainTypes};
use grin_onion::crypto::secp;
use grin_onion::test_util as onion_test_util;
use rand::RngCore;
use std::cmp::Ordering;
fn new_store(test_name: &str) -> SwapStore { use grin_core::core::{Input, OutputFeatures};
global::set_local_chain_type(ChainTypes::AutomatedTesting); use grin_core::global::{self, ChainTypes};
let db_root = format!("./target/tmp/.{}", test_name); use rand::RngCore;
let _ = std::fs::remove_dir_all(db_root.as_str());
SwapStore::new(db_root.as_str()).unwrap()
}
fn rand_swap_with_status(status: SwapStatus) -> SwapData { use grin_onion::crypto::secp;
SwapData { use grin_onion::test_util as onion_test_util;
excess: secp::random_secret(),
output_commit: onion_test_util::rand_commit(),
rangeproof: Some(onion_test_util::rand_proof()),
input: Input::new(OutputFeatures::Plain, onion_test_util::rand_commit()),
fee: rand::thread_rng().next_u64(),
onion: onion_test_util::rand_onion(),
status,
}
}
fn rand_swap() -> SwapData { use crate::store::{StoreError, SwapData, SwapStatus, SwapStore};
let s = rand::thread_rng().next_u64() % 3;
let status = if s == 0 {
SwapStatus::Unprocessed
} else if s == 1 {
SwapStatus::InProcess {
kernel_commit: onion_test_util::rand_commit(),
}
} else {
SwapStatus::Completed {
kernel_commit: onion_test_util::rand_commit(),
block_hash: onion_test_util::rand_hash(),
}
};
rand_swap_with_status(status)
}
#[test] fn new_store(test_name: &str) -> SwapStore {
fn swap_iter() -> Result<(), Box<dyn std::error::Error>> { global::set_local_chain_type(ChainTypes::AutomatedTesting);
let store = new_store("swap_iter"); let db_root = format!("./target/tmp/.{}", test_name);
let mut swaps: Vec<SwapData> = Vec::new(); let _ = std::fs::remove_dir_all(db_root.as_str());
for _ in 0..5 { SwapStore::new(db_root.as_str()).unwrap()
let swap = rand_swap(); }
store.save_swap(&swap, false)?;
swaps.push(swap);
}
swaps.sort_by(|a, b| { fn rand_swap_with_status(status: SwapStatus) -> SwapData {
if a.input.commit < b.input.commit { SwapData {
Ordering::Less excess: secp::random_secret(),
} else if a.input.commit == b.input.commit { output_commit: onion_test_util::rand_commit(),
Ordering::Equal rangeproof: Some(onion_test_util::rand_proof()),
} else { input: Input::new(OutputFeatures::Plain, onion_test_util::rand_commit()),
Ordering::Greater fee: rand::thread_rng().next_u64(),
} onion: onion_test_util::rand_onion(),
}); status,
}
}
let mut i: usize = 0; fn rand_swap() -> SwapData {
for swap in store.swaps_iter()? { let s = rand::thread_rng().next_u64() % 3;
assert_eq!(swap, *swaps.get(i).unwrap()); let status = if s == 0 {
i += 1; SwapStatus::Unprocessed
} } else if s == 1 {
SwapStatus::InProcess {
kernel_commit: onion_test_util::rand_commit(),
}
} else {
SwapStatus::Completed {
kernel_commit: onion_test_util::rand_commit(),
block_hash: onion_test_util::rand_hash(),
}
};
rand_swap_with_status(status)
}
Ok(()) #[test]
} fn swap_iter() -> Result<(), Box<dyn std::error::Error>> {
let store = new_store("swap_iter");
let mut swaps: Vec<SwapData> = Vec::new();
for _ in 0..5 {
let swap = rand_swap();
store.save_swap(&swap, false)?;
swaps.push(swap);
}
#[test] swaps.sort_by(|a, b| {
fn save_swap() -> Result<(), Box<dyn std::error::Error>> { if a.input.commit < b.input.commit {
let store = new_store("save_swap"); Ordering::Less
} else if a.input.commit == b.input.commit {
Ordering::Equal
} else {
Ordering::Greater
}
});
let mut swap = rand_swap_with_status(SwapStatus::Unprocessed); let mut i: usize = 0;
assert!(!store.swap_exists(&swap.input.commit)?); for swap in store.swaps_iter()? {
assert_eq!(swap, *swaps.get(i).unwrap());
i += 1;
}
store.save_swap(&swap, false)?; Ok(())
assert_eq!(swap, store.get_swap(&swap.input.commit)?); }
assert!(store.swap_exists(&swap.input.commit)?);
swap.status = SwapStatus::InProcess { #[test]
kernel_commit: onion_test_util::rand_commit(), fn save_swap() -> Result<(), Box<dyn std::error::Error>> {
}; let store = new_store("save_swap");
let result = store.save_swap(&swap, false);
assert_eq!(
Err(StoreError::AlreadyExists(swap.input.commit.clone())),
result
);
store.save_swap(&swap, true)?; let mut swap = rand_swap_with_status(SwapStatus::Unprocessed);
assert_eq!(swap, store.get_swap(&swap.input.commit)?); assert!(!store.swap_exists(&swap.input.commit)?);
Ok(()) store.save_swap(&swap, false)?;
} assert_eq!(swap, store.get_swap(&swap.input.commit)?);
assert!(store.swap_exists(&swap.input.commit)?);
swap.status = SwapStatus::InProcess {
kernel_commit: onion_test_util::rand_commit(),
};
let result = store.save_swap(&swap, false);
assert_eq!(
Err(StoreError::AlreadyExists(swap.input.commit.clone())),
result
);
store.save_swap(&swap, true)?;
assert_eq!(swap, store.get_swap(&swap.input.commit)?);
Ok(())
}
} }

View file

@ -1,9 +1,32 @@
use crate::config::ServerConfig; use std::sync::Arc;
use grin_wallet_impls::tor::config as tor_config; use arti_client::config::TorClientConfigBuilder;
use grin_wallet_impls::tor::process::TorProcess; use arti_client::{TorClient, TorClientConfig};
use std::collections::HashMap; use arti_hyper::ArtiHttpConnector;
use curve25519_dalek::digest::Digest;
use ed25519_dalek::hazmat::ExpandedSecretKey;
use futures::task::SpawnExt;
use sha2::Sha512;
use thiserror::Error; use thiserror::Error;
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
use tls_api_native_tls::TlsConnector;
use tor_hscrypto::pk::{HsIdKey, HsIdKeypair};
use tor_hsrproxy::config::{
Encapsulation, ProxyAction, ProxyConfigBuilder, ProxyPattern, ProxyRule, TargetAddr,
};
use tor_hsrproxy::OnionServiceReverseProxy;
use tor_hsservice::config::OnionServiceConfigBuilder;
use tor_hsservice::{
HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname, RunningOnionService,
};
use tor_keymgr::key_specifier_derive::internal;
use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector};
use tor_llcrypto::pk::ed25519::ExpandedKeypair;
use tor_rtcompat::Runtime;
use secp256k1zkp::SecretKey;
use crate::config::ServerConfig;
/// Tor error types /// Tor error types
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -14,93 +37,163 @@ pub enum TorError {
ProcessError(grin_wallet_impls::tor::process::Error), ProcessError(grin_wallet_impls::tor::process::Error),
} }
pub fn init_tor_listener( pub struct TorService<R: Runtime> {
data_dir: &str, tor_client: Option<TorClient<R>>,
server_config: &ServerConfig, hidden_services: Vec<Arc<RunningOnionService>>,
) -> Result<TorProcess, TorError> { }
warn!("Initializing tor listener");
let tor_dir = format!("{}/tor/listener", &data_dir); impl<R: Runtime> TorService<R> {
trace!( /// Builds a hyper::Client with an ArtiHttpConnector over the TorClient.
"Dir: {}, Proxy: {}", /// The returned Client makes HTTP requests through the TorClient directly, eliminating the need for a socks proxy.
&tor_dir, pub fn new_hyper_client(
server_config.socks_proxy_addr.to_string() &self,
); ) -> hyper::Client<ArtiHttpConnector<R, TlsConnector>, hyper::Body> {
let tls_connector = TlsConnector::builder().unwrap().build().unwrap();
let tor_connector = ArtiHttpConnector::new(self.tor_client.clone().unwrap(), tls_connector);
// create data directory if it doesn't exist hyper::Client::builder().build::<_, hyper::Body>(tor_connector)
std::fs::create_dir_all(&format!("{}/data", tor_dir)).unwrap();
let service_dir = tor_config::output_onion_service_config(tor_dir.as_str(), &server_config.key)
.map_err(|e| TorError::ConfigError(e))?;
let service_dirs = vec![service_dir.to_string()];
tor_config::output_torrc(
tor_dir.as_str(),
server_config.addr.to_string().as_str(),
&server_config.socks_proxy_addr.to_string(),
&service_dirs,
HashMap::new(),
HashMap::new(),
)
.map_err(|e| TorError::ConfigError(e))?;
// Start TOR process
let mut process = TorProcess::new();
process
.torrc_path("./torrc")
.working_dir(tor_dir.as_str())
.timeout(30)
.completion_percent(100);
let mut attempts = 0;
let max_attempts = 3;
let mut result;
loop {
attempts += 1;
info!("Launching TorProcess... Attempt {}", attempts);
result = process.launch();
if result.is_ok() || attempts >= max_attempts {
break;
}
} }
result.map_err(TorError::ProcessError)?; pub fn stop(&mut self) {
self.tor_client = None;
self.hidden_services.clear();
}
}
pub async fn async_init_tor<R>(
runtime: R,
data_dir: &str,
server_config: &ServerConfig,
) -> Result<TorService<R>, TorError>
where
R: Runtime,
{
warn!("Initializing TOR");
let state_dir = format!("{}/tor/state", &data_dir);
let cache_dir = format!("{}/tor/cache", &data_dir);
let hs_nickname = HsNickname::new("listener".to_string()).unwrap();
let mut client_config_builder =
TorClientConfigBuilder::from_directories(state_dir.clone(), cache_dir.clone());
client_config_builder
.address_filter()
.allow_onion_addrs(true);
let client_config = client_config_builder.build().unwrap();
add_key_to_store(&client_config, &state_dir, &server_config.key, &hs_nickname)?;
let tor_client = TorClient::with_runtime(runtime)
.config(client_config)
.create_bootstrapped()
.await
.unwrap();
let service =
async_launch_hidden_service(hs_nickname.clone(), &tor_client, &server_config).await?;
let tor_instance = TorService {
tor_client: Some(tor_client),
hidden_services: vec![service],
};
Ok(tor_instance)
}
async fn async_launch_hidden_service<R>(
hs_nickname: HsNickname,
tor_client: &TorClient<R>,
server_config: &ServerConfig,
) -> Result<Arc<RunningOnionService>, TorError>
where
R: Runtime,
{
let svc_cfg = OnionServiceConfigBuilder::default()
.nickname(hs_nickname.clone())
.build()
.unwrap();
let (service, request_stream) = tor_client.launch_onion_service(svc_cfg).unwrap();
let proxy_rule = ProxyRule::new(
ProxyPattern::one_port(80).unwrap(),
ProxyAction::Forward(Encapsulation::Simple, TargetAddr::Inet(server_config.addr)),
);
let mut proxy_cfg_builder = ProxyConfigBuilder::default();
proxy_cfg_builder.set_proxy_ports(vec![proxy_rule]);
let proxy = OnionServiceReverseProxy::new(proxy_cfg_builder.build().unwrap());
{
let proxy = proxy.clone();
let runtime_clone = tor_client.runtime().clone();
tor_client
.runtime()
.spawn(async move {
match proxy
.handle_requests(runtime_clone, hs_nickname.clone(), request_stream)
.await
{
Ok(()) => {
debug!("Onion service {} exited cleanly.", hs_nickname);
}
Err(e) => {
warn!("Onion service {} exited with an error: {}", hs_nickname, e);
}
}
})
.unwrap();
}
warn!( warn!(
"Server listening at http://{}.onion", "Server listening at http://{}.onion",
server_config.onion_address().to_ov3_str() server_config.onion_address().to_ov3_str()
); );
Ok(process) Ok(service)
} }
pub fn init_tor_sender( // TODO: Add proper error handling
data_dir: &str, fn add_key_to_store(
server_config: &ServerConfig, tor_config: &TorClientConfig,
) -> Result<TorProcess, TorError> { state_dir: &String,
warn!( secret_key: &SecretKey,
"Starting TOR Process for send at {:?}", hs_nickname: &HsNickname,
server_config.socks_proxy_addr ) -> Result<(), TorError> {
let key_store_dir = format!("{}/keystore", &state_dir);
let arti_store =
ArtiNativeKeystore::from_path_and_mistrust(&key_store_dir, &tor_config.fs_mistrust())
.unwrap();
info!("Using keystore from {key_store_dir:?}");
let key_manager = KeyMgrBuilder::default()
.default_store(Box::new(arti_store))
.build()
.map_err(|_| internal!("failed to build keymgr"))
.unwrap();
let expanded_sk = ExpandedSecretKey::from_bytes(
Sha512::default()
.chain_update(secret_key)
.finalize()
.as_ref(),
); );
let tor_dir = format!("{}/tor/sender", data_dir); let mut sk_bytes = [0_u8; 64];
tor_config::output_tor_sender_config( sk_bytes[0..32].copy_from_slice(&expanded_sk.scalar.to_bytes());
tor_dir.as_str(), sk_bytes[32..64].copy_from_slice(&expanded_sk.hash_prefix);
&server_config.socks_proxy_addr.to_string(), let expanded_kp = ExpandedKeypair::from_secret_key_bytes(sk_bytes).unwrap();
HashMap::new(),
HashMap::new(),
)
.map_err(|e| TorError::ConfigError(e))?;
// Start TOR process key_manager
let mut tor_process = TorProcess::new(); .insert(
tor_process HsIdKey::from(expanded_kp.public().clone()),
.torrc_path("./torrc") &HsIdPublicKeySpecifier::new(hs_nickname.clone()),
.working_dir(tor_dir.as_str()) KeystoreSelector::Default,
.timeout(40) )
.completion_percent(100) .unwrap();
.launch()
.map_err(TorError::ProcessError)?; key_manager
Ok(tor_process) .insert(
HsIdKeypair::from(expanded_kp),
&HsIdKeypairSpecifier::new(hs_nickname.clone()),
KeystoreSelector::Default,
)
.unwrap();
Ok(())
} }

View file

@ -1,18 +1,20 @@
use std::net::SocketAddr;
use async_trait::async_trait; use async_trait::async_trait;
use grin_api::client;
use grin_api::json_rpc::{build_request, Request, Response, RpcError};
use grin_core::core::Output; use grin_core::core::Output;
use grin_core::libtx::secp_ser; use grin_core::libtx::secp_ser;
use grin_keychain::BlindingFactor; use grin_keychain::BlindingFactor;
use grin_onion::crypto::secp;
use grin_util::{ToHex, ZeroingString}; use grin_util::{ToHex, ZeroingString};
use grin_wallet_api::{EncryptedRequest, EncryptedResponse, JsonId, Token}; use grin_wallet_api::Token;
use secp256k1zkp::{PublicKey, Secp256k1, SecretKey};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use std::net::SocketAddr;
use thiserror::Error; use thiserror::Error;
use grin_onion::crypto::secp;
use secp256k1zkp::{PublicKey, Secp256k1, SecretKey};
use crate::http;
#[async_trait] #[async_trait]
pub trait Wallet: Send + Sync { pub trait Wallet: Send + Sync {
/// Builds an output for the wallet with the provided amount. /// Builds an output for the wallet with the provided amount.
@ -25,18 +27,8 @@ pub trait Wallet: Send + Sync {
/// Error types for interacting with wallets /// Error types for interacting with wallets
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum WalletError { pub enum WalletError {
#[error("Error encrypting request: {0:?}")] #[error("Error communication with wallet: {0:?}")]
EncryptRequestError(grin_wallet_libwallet::Error), WalletCommError(http::HttpError),
#[error("Error decrypting response: {0:?}")]
DecryptResponseError(grin_wallet_libwallet::Error),
#[error("Error decoding JSON response: {0:?}")]
DecodeResponseError(serde_json::Error),
#[error("JSON-RPC API communication error: {0:?}")]
ApiCommError(grin_api::Error),
#[error("Error decoding JSON-RPC response: {0:?}")]
ResponseParseError(grin_api::json_rpc::Error),
#[error("Unsucessful response returned: {0:?}")]
ResponseRpcError(Option<RpcError>),
} }
/// HTTP (JSONRPC) implementation of the 'Wallet' trait. /// HTTP (JSONRPC) implementation of the 'Wallet' trait.
@ -73,14 +65,16 @@ impl HttpWallet {
"name": null, "name": null,
"password": wallet_pass.to_string() "password": wallet_pass.to_string()
}); });
let token: Token = HttpWallet::async_send_enc_request( let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
&wallet_owner_url, let token: Token = http::async_send_enc_request(
&url,
&wallet_owner_secret, &wallet_owner_secret,
"open_wallet", "open_wallet",
&open_wallet_params, &open_wallet_params,
&shared_key, &shared_key,
) )
.await?; .await
.map_err(WalletError::WalletCommError)?;
info!("Connected to wallet"); info!("Connected to wallet");
Ok(HttpWallet { Ok(HttpWallet {
@ -98,17 +92,20 @@ impl HttpWallet {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let ephemeral_sk = secp::random_secret(); let ephemeral_sk = secp::random_secret();
let ephemeral_pk = PublicKey::from_secret_key(&secp, &ephemeral_sk).unwrap(); let ephemeral_pk = PublicKey::from_secret_key(&secp, &ephemeral_sk).unwrap();
let ephemeral_pk_bytes = ephemeral_pk.serialize_vec(&secp, true);
let init_params = json!({ let init_params = json!({
"ecdh_pubkey": ephemeral_pk.serialize_vec(&secp, true).to_hex() "ecdh_pubkey": ephemeral_pk_bytes.to_hex()
}); });
let response_pk: ECDHPubkey = HttpWallet::async_send_json_request( let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
&wallet_owner_url, let response_pk: ECDHPubkey = http::async_send_json_request(
&url,
&wallet_owner_secret, &wallet_owner_secret,
"init_secure_api", "init_secure_api",
&init_params, &init_params,
) )
.await?; .await
.map_err(WalletError::WalletCommError)?;
let shared_key = { let shared_key = {
let mut shared_pubkey = response_pk.ecdh_pubkey.clone(); let mut shared_pubkey = response_pk.ecdh_pubkey.clone();
@ -126,75 +123,16 @@ impl HttpWallet {
method: &str, method: &str,
params: &serde_json::Value, params: &serde_json::Value,
) -> Result<D, WalletError> { ) -> Result<D, WalletError> {
HttpWallet::async_send_enc_request( let url = format!("http://{}{}", self.wallet_owner_url, ENDPOINT);
&self.wallet_owner_url, http::async_send_enc_request(
&url,
&self.wallet_owner_secret, &self.wallet_owner_secret,
method, method,
params, params,
&self.shared_key, &self.shared_key,
) )
.await .await
} .map_err(WalletError::WalletCommError)
async fn async_send_enc_request<D: serde::de::DeserializeOwned>(
wallet_owner_url: &SocketAddr,
wallet_owner_secret: &Option<String>,
method: &str,
params: &serde_json::Value,
shared_key: &SecretKey,
) -> Result<D, WalletError> {
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
let req = json!({
"method": method,
"params": params,
"id": JsonId::IntId(1),
"jsonrpc": "2.0",
});
let enc_req = EncryptedRequest::from_json(&JsonId::IntId(1), &req, &shared_key)
.map_err(WalletError::EncryptRequestError)?;
let res = client::post_async::<EncryptedRequest, EncryptedResponse>(
url.as_str(),
&enc_req,
wallet_owner_secret.clone(),
)
.await
.map_err(WalletError::ApiCommError)?;
let decrypted = res
.decrypt(&shared_key)
.map_err(WalletError::DecryptResponseError)?;
let response: Response =
serde_json::from_value(decrypted).map_err(WalletError::DecodeResponseError)?;
let result = response
.result
.ok_or(WalletError::ResponseRpcError(response.error.clone()))?;
let ok = result
.get("Ok")
.ok_or(WalletError::ResponseRpcError(response.error.clone()))?;
let parsed =
serde_json::from_value(ok.clone()).map_err(WalletError::DecodeResponseError)?;
Ok(parsed)
}
async fn async_send_json_request<D: serde::de::DeserializeOwned>(
wallet_owner_url: &SocketAddr,
wallet_owner_secret: &Option<String>,
method: &str,
params: &serde_json::Value,
) -> Result<D, WalletError> {
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
let req = build_request(method, params);
let res = client::post_async::<Request, Response>(
url.as_str(),
&req,
wallet_owner_secret.clone(),
)
.await
.map_err(WalletError::ApiCommError)?;
let parsed = res
.clone()
.into_result()
.map_err(WalletError::ResponseParseError)?;
Ok(parsed)
} }
pub fn get_token(&self) -> Token { pub fn get_token(&self) -> Token {
@ -219,35 +157,40 @@ impl Wallet for HttpWallet {
&self, &self,
amount: u64, amount: u64,
) -> Result<(BlindingFactor, Output), WalletError> { ) -> Result<(BlindingFactor, Output), WalletError> {
let req_json = json!({ let params = json!({
"token": self.token, "token": self.token,
"features": "Plain", "features": "Plain",
"amount": amount "amount": amount
}); });
let output: OutputWithBlind = HttpWallet::async_send_enc_request(
&self.wallet_owner_url, let url = format!("http://{}{}", self.wallet_owner_url, ENDPOINT);
let output: OutputWithBlind = http::async_send_enc_request(
&url,
&self.wallet_owner_secret, &self.wallet_owner_secret,
"build_output", "build_output",
&req_json, &params,
&self.shared_key, &self.shared_key,
) )
.await?; .await
.map_err(WalletError::WalletCommError)?;
Ok((output.blind, output.output)) Ok((output.blind, output.output))
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod mock { pub mod mock {
use super::{Wallet, WalletError};
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use std::sync::{Arc, Mutex};
use async_trait::async_trait; use async_trait::async_trait;
use grin_core::core::{Output, OutputFeatures}; use grin_core::core::{Output, OutputFeatures};
use grin_keychain::BlindingFactor; use grin_keychain::BlindingFactor;
use grin_onion::crypto::secp; use grin_onion::crypto::secp;
use secp256k1zkp::pedersen::Commitment; use secp256k1zkp::pedersen::Commitment;
use secp256k1zkp::Secp256k1; use secp256k1zkp::Secp256k1;
use std::sync::{Arc, Mutex};
use super::{Wallet, WalletError};
/// Mock implementation of the 'Wallet' trait for unit-tests. /// Mock implementation of the 'Wallet' trait for unit-tests.
#[derive(Clone)] #[derive(Clone)]

View file

@ -1,27 +1,31 @@
use crate::common::node::IntegrationGrinNode;
use crate::common::wallet::{GrinWalletManager, IntegrationGrinWallet};
use grin_core::core::Transaction;
use grin_onion::crypto::comsig::ComSignature;
use grin_onion::crypto::dalek::DalekPublicKey;
use grin_onion::onion::Onion;
use grin_wallet_impls::tor::process::TorProcess;
use mwixnet::client::MixClientImpl;
use mwixnet::{tor, SwapError, SwapServer, SwapStore};
use secp256k1zkp::SecretKey;
use std::iter; use std::iter;
use std::net::TcpListener; use std::net::TcpListener;
use std::sync::Arc; use std::sync::Arc;
use grin_core::core::Transaction;
use tor_rtcompat::PreferredRuntime;
use x25519_dalek::{PublicKey as xPublicKey, StaticSecret}; use x25519_dalek::{PublicKey as xPublicKey, StaticSecret};
pub struct IntegrationSwapServer { use grin_onion::crypto::comsig::ComSignature;
use grin_onion::crypto::dalek::DalekPublicKey;
use grin_onion::onion::Onion;
use mwixnet::mix_client::MixClientImpl;
use mwixnet::tor::TorService;
use mwixnet::{tor, SwapError, SwapServer, SwapStore};
use secp256k1zkp::SecretKey;
use crate::common::node::IntegrationGrinNode;
use crate::common::wallet::{GrinWalletManager, IntegrationGrinWallet};
pub struct IntegrationSwapServer<R: tor_rtcompat::Runtime> {
server_key: SecretKey, server_key: SecretKey,
tor_process: TorProcess, tor_instance: Arc<grin_util::Mutex<TorService<R>>>,
swap_server: Arc<tokio::sync::Mutex<dyn SwapServer>>, swap_server: Arc<tokio::sync::Mutex<dyn SwapServer>>,
rpc_server: jsonrpc_http_server::Server, rpc_server: jsonrpc_http_server::Server,
_wallet: Arc<grin_util::Mutex<IntegrationGrinWallet>>, _wallet: Arc<grin_util::Mutex<IntegrationGrinWallet>>,
} }
impl IntegrationSwapServer { impl<R: tor_rtcompat::Runtime> IntegrationSwapServer<R> {
pub async fn async_swap(&self, onion: &Onion, comsig: &ComSignature) -> Result<(), SwapError> { pub async fn async_swap(&self, onion: &Onion, comsig: &ComSignature) -> Result<(), SwapError> {
self.swap_server.lock().await.swap(&onion, &comsig).await self.swap_server.lock().await.swap(&onion, &comsig).await
} }
@ -31,21 +35,25 @@ impl IntegrationSwapServer {
} }
} }
pub struct IntegrationMixServer { pub struct IntegrationMixServer<R: tor_rtcompat::Runtime> {
server_key: SecretKey, server_key: SecretKey,
tor_process: TorProcess, tor_instance: Arc<grin_util::Mutex<TorService<R>>>,
rpc_server: jsonrpc_http_server::Server, rpc_server: jsonrpc_http_server::Server,
_wallet: Arc<grin_util::Mutex<IntegrationGrinWallet>>, _wallet: Arc<grin_util::Mutex<IntegrationGrinWallet>>,
} }
async fn async_new_swap_server( async fn async_new_swap_server<R>(
data_dir: &str, data_dir: &str,
rt_handle: &tokio::runtime::Handle, rt_handle: &tokio::runtime::Handle,
tor_runtime: R,
wallets: &mut GrinWalletManager, wallets: &mut GrinWalletManager,
server_key: &SecretKey, server_key: &SecretKey,
node: &Arc<grin_util::Mutex<IntegrationGrinNode>>, node: &Arc<grin_util::Mutex<IntegrationGrinNode>>,
next_server: Option<&IntegrationMixServer>, next_server: Option<&IntegrationMixServer<R>>,
) -> IntegrationSwapServer { ) -> IntegrationSwapServer<R>
where
R: tor_rtcompat::Runtime,
{
let wallet = wallets.async_new_wallet(&node.lock().api_address()).await; let wallet = wallets.async_new_wallet(&node.lock().api_address()).await;
let server_config = mwixnet::ServerConfig { let server_config = mwixnet::ServerConfig {
@ -55,10 +63,6 @@ async fn async_new_swap_server(
.unwrap() .unwrap()
.local_addr() .local_addr()
.unwrap(), .unwrap(),
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap(),
grin_node_url: node.lock().api_address(), grin_node_url: node.lock().api_address(),
grin_node_secret_path: None, grin_node_secret_path: None,
wallet_owner_url: wallet.lock().owner_address(), wallet_owner_url: wallet.lock().owner_address(),
@ -72,7 +76,10 @@ async fn async_new_swap_server(
// Open SwapStore // Open SwapStore
let store = SwapStore::new(format!("{}/db", data_dir).as_str()).unwrap(); let store = SwapStore::new(format!("{}/db", data_dir).as_str()).unwrap();
let tor_process = tor::init_tor_listener(&data_dir, &server_config).unwrap(); let tor_instance = tor::async_init_tor(tor_runtime, &data_dir, &server_config)
.await
.unwrap();
let tor_instance = Arc::new(grin_util::Mutex::new(tor_instance));
let (swap_server, rpc_server) = mwixnet::swap_listen( let (swap_server, rpc_server) = mwixnet::swap_listen(
rt_handle, rt_handle,
@ -80,6 +87,7 @@ async fn async_new_swap_server(
match next_server { match next_server {
Some(s) => Some(Arc::new(MixClientImpl::new( Some(s) => Some(Arc::new(MixClientImpl::new(
server_config.clone(), server_config.clone(),
tor_instance.clone(),
DalekPublicKey::from_secret(&s.server_key), DalekPublicKey::from_secret(&s.server_key),
))), ))),
None => None, None => None,
@ -92,22 +100,26 @@ async fn async_new_swap_server(
IntegrationSwapServer { IntegrationSwapServer {
server_key: server_key.clone(), server_key: server_key.clone(),
tor_process, tor_instance,
swap_server, swap_server,
rpc_server, rpc_server,
_wallet: wallet, _wallet: wallet,
} }
} }
async fn async_new_mix_server( async fn async_new_mix_server<R>(
data_dir: &str, data_dir: &str,
rt_handle: &tokio::runtime::Handle, rt_handle: &tokio::runtime::Handle,
tor_runtime: R,
wallets: &mut GrinWalletManager, wallets: &mut GrinWalletManager,
server_key: &SecretKey, server_key: &SecretKey,
node: &Arc<grin_util::Mutex<IntegrationGrinNode>>, node: &Arc<grin_util::Mutex<IntegrationGrinNode>>,
prev_server: DalekPublicKey, prev_server: DalekPublicKey,
next_server: Option<&IntegrationMixServer>, next_server: Option<&IntegrationMixServer<R>>,
) -> IntegrationMixServer { ) -> IntegrationMixServer<R>
where
R: tor_rtcompat::Runtime,
{
let wallet = wallets.async_new_wallet(&node.lock().api_address()).await; let wallet = wallets.async_new_wallet(&node.lock().api_address()).await;
let server_config = mwixnet::ServerConfig { let server_config = mwixnet::ServerConfig {
key: server_key.clone(), key: server_key.clone(),
@ -116,10 +128,6 @@ async fn async_new_mix_server(
.unwrap() .unwrap()
.local_addr() .local_addr()
.unwrap(), .unwrap(),
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap(),
grin_node_url: node.lock().api_address(), grin_node_url: node.lock().api_address(),
grin_node_secret_path: None, grin_node_secret_path: None,
wallet_owner_url: wallet.lock().owner_address(), wallet_owner_url: wallet.lock().owner_address(),
@ -131,7 +139,10 @@ async fn async_new_mix_server(
}, },
}; };
let tor_process = tor::init_tor_listener(&data_dir, &server_config).unwrap(); let tor_instance = tor::async_init_tor(tor_runtime, &data_dir, &server_config)
.await
.unwrap();
let tor_instance = Arc::new(grin_util::Mutex::new(tor_instance));
let (_, rpc_server) = mwixnet::mix_listen( let (_, rpc_server) = mwixnet::mix_listen(
rt_handle, rt_handle,
@ -139,6 +150,7 @@ async fn async_new_mix_server(
match next_server { match next_server {
Some(s) => Some(Arc::new(MixClientImpl::new( Some(s) => Some(Arc::new(MixClientImpl::new(
server_config.clone(), server_config.clone(),
tor_instance.clone(),
DalekPublicKey::from_secret(&s.server_key), DalekPublicKey::from_secret(&s.server_key),
))), ))),
None => None, None => None,
@ -150,16 +162,16 @@ async fn async_new_mix_server(
IntegrationMixServer { IntegrationMixServer {
server_key: server_key.clone(), server_key: server_key.clone(),
tor_process, tor_instance,
rpc_server, rpc_server,
_wallet: wallet, _wallet: wallet,
} }
} }
pub struct Servers { pub struct Servers {
pub swapper: IntegrationSwapServer, pub swapper: IntegrationSwapServer<PreferredRuntime>,
pub mixers: Vec<IntegrationMixServer>, pub mixers: Vec<IntegrationMixServer<PreferredRuntime>>,
} }
impl Servers { impl Servers {
@ -176,12 +188,16 @@ impl Servers {
.take(num_mixers + 1) .take(num_mixers + 1)
.collect(); .collect();
// Setup mock tor network
let tor_runtime = PreferredRuntime::current().unwrap();
// Build mixers in reverse order // Build mixers in reverse order
let mut mixers = Vec::new(); let mut mixers = Vec::new();
for i in (0..num_mixers).rev() { for i in (0..num_mixers).rev() {
let mix_server = async_new_mix_server( let mix_server = async_new_mix_server(
format!("{}/mixers/{}", test_dir, i).as_str(), format!("{}/mixers/{}", test_dir, i).as_str(),
rt_handle, rt_handle,
tor_runtime.clone(),
wallets, wallets,
&server_keys[i + 1], &server_keys[i + 1],
&node, &node,
@ -206,6 +222,7 @@ impl Servers {
let swapper = async_new_swap_server( let swapper = async_new_swap_server(
format!("{}/swapper", test_dir).as_str(), format!("{}/swapper", test_dir).as_str(),
rt_handle, rt_handle,
tor_runtime.clone(),
wallets, wallets,
&server_keys[0], &server_keys[0],
&node, &node,
@ -234,11 +251,11 @@ impl Servers {
pub fn stop_all(&mut self) { pub fn stop_all(&mut self) {
self.swapper.rpc_server.close_handle().close(); self.swapper.rpc_server.close_handle().close();
self.swapper.tor_process.kill().unwrap(); self.swapper.tor_instance.lock().stop();
self.mixers.iter_mut().for_each(|mixer| { self.mixers.iter_mut().for_each(|mixer| {
mixer.rpc_server.close_handle().close(); mixer.rpc_server.close_handle().close();
mixer.tor_process.kill().unwrap(); mixer.tor_instance.lock().stop();
}); });
} }
} }

View file

@ -1,13 +1,11 @@
use crate::common::types::BlockFees; use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use grin_api::client; use std::sync::Arc;
use grin_api::json_rpc::Response; use std::thread;
use grin_core::core::{FeeFields, Output, OutputFeatures, Transaction, TxKernel}; use grin_core::core::{FeeFields, Output, OutputFeatures, Transaction, TxKernel};
use grin_core::global::ChainTypes; use grin_core::global::ChainTypes;
use grin_core::libtx::tx_fee; use grin_core::libtx::tx_fee;
use grin_keychain::{BlindingFactor, ExtKeychain, Identifier, Keychain, SwitchCommitmentType}; use grin_keychain::{BlindingFactor, ExtKeychain, Identifier, Keychain, SwitchCommitmentType};
use grin_onion::crypto::comsig::ComSignature;
use grin_onion::onion::Onion;
use grin_onion::Hop;
use grin_util::{Mutex, ZeroingString}; use grin_util::{Mutex, ZeroingString};
use grin_wallet_api::Owner; use grin_wallet_api::Owner;
use grin_wallet_config::WalletConfig; use grin_wallet_config::WalletConfig;
@ -15,417 +13,401 @@ use grin_wallet_controller::controller;
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
use grin_wallet_libwallet::{InitTxArgs, Slate, VersionedSlate, WalletInfo, WalletInst}; use grin_wallet_libwallet::{InitTxArgs, Slate, VersionedSlate, WalletInfo, WalletInst};
use log::error; use log::error;
use serde_derive::{Deserialize, Serialize};
use serde_json::json;
use x25519_dalek::PublicKey as xPublicKey;
use grin_onion::crypto::comsig::ComSignature;
use grin_onion::onion::Onion;
use grin_onion::Hop;
use mwixnet::http;
use mwixnet::wallet::HttpWallet; use mwixnet::wallet::HttpWallet;
use secp256k1zkp::pedersen::Commitment; use secp256k1zkp::pedersen::Commitment;
use secp256k1zkp::{Secp256k1, SecretKey}; use secp256k1zkp::{Secp256k1, SecretKey};
use serde_derive::{Deserialize, Serialize};
use serde_json::json; use crate::common::types::BlockFees;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::thread;
use x25519_dalek::PublicKey as xPublicKey;
/// Response to build a coinbase output. /// Response to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CbData { pub struct CbData {
/// Output /// Output
pub output: Output, pub output: Output,
/// Kernel /// Kernel
pub kernel: TxKernel, pub kernel: TxKernel,
/// Key Id /// Key Id
pub key_id: Option<Identifier>, pub key_id: Option<Identifier>,
} }
pub struct IntegrationGrinWallet { pub struct IntegrationGrinWallet {
wallet: Arc< wallet: Arc<
Mutex< Mutex<
Box< Box<
dyn WalletInst< dyn WalletInst<
'static, 'static,
DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>,
HTTPNodeClient, HTTPNodeClient,
ExtKeychain, ExtKeychain,
>, >,
>, >,
>, >,
>, >,
api_listen_port: u16, api_listen_port: u16,
owner_api: Arc< owner_api: Arc<
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>, Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
>, >,
http_client: Arc<HttpWallet>, http_client: Arc<HttpWallet>,
} }
impl IntegrationGrinWallet { impl IntegrationGrinWallet {
pub async fn async_new_wallet( pub async fn async_new_wallet(
wallet_dir: String, wallet_dir: String,
api_listen_port: u16, api_listen_port: u16,
node_api: String, node_api: String,
) -> IntegrationGrinWallet { ) -> IntegrationGrinWallet {
let node_client = HTTPNodeClient::new(&node_api, None).unwrap(); let node_client = HTTPNodeClient::new(&node_api, None).unwrap();
let mut wallet = Box::new( let mut wallet = Box::new(
DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(), DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(),
) )
as Box< as Box<
dyn WalletInst< dyn WalletInst<
'static, 'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>, DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
HTTPNodeClient, HTTPNodeClient,
ExtKeychain, ExtKeychain,
>, >,
>; >;
// Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc... // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc...
let lc = wallet.lc_provider().unwrap(); let lc = wallet.lc_provider().unwrap();
let mut wallet_config = WalletConfig::default(); let mut wallet_config = WalletConfig::default();
wallet_config.check_node_api_http_addr = node_api.clone(); wallet_config.check_node_api_http_addr = node_api.clone();
wallet_config.owner_api_listen_port = Some(api_listen_port); wallet_config.owner_api_listen_port = Some(api_listen_port);
wallet_config.api_secret_path = None; wallet_config.api_secret_path = None;
wallet_config.data_file_dir = wallet_dir.clone(); wallet_config.data_file_dir = wallet_dir.clone();
// The top level wallet directory should be set manually (in the reference implementation, // The top level wallet directory should be set manually (in the reference implementation,
// this is provided in the WalletConfig) // this is provided in the WalletConfig)
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir); let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
lc.create_config( lc.create_config(
&ChainTypes::AutomatedTesting, &ChainTypes::AutomatedTesting,
"grin-wallet.toml", "grin-wallet.toml",
Some(wallet_config.clone()), Some(wallet_config.clone()),
None, None,
None, None,
) )
.unwrap(); .unwrap();
lc.create_wallet(None, None, 12, ZeroingString::from("pass"), false) lc.create_wallet(None, None, 12, ZeroingString::from("pass"), false)
.unwrap(); .unwrap();
// Start owner API // Start owner API
let km = Arc::new(Mutex::new(None)); let km = Arc::new(Mutex::new(None));
let wallet = Arc::new(Mutex::new(wallet)); let wallet = Arc::new(Mutex::new(wallet));
let owner_api = Arc::new(Owner::new(wallet.clone(), None)); let owner_api = Arc::new(Owner::new(wallet.clone(), None));
let address_str = format!("127.0.0.1:{}", api_listen_port); let address_str = format!("127.0.0.1:{}", api_listen_port);
let owner_addr: SocketAddr = address_str.parse().unwrap(); let owner_addr: SocketAddr = address_str.parse().unwrap();
let thr_wallet = wallet.clone(); let thr_wallet = wallet.clone();
let _thread_handle = thread::spawn(move || { let _thread_handle = thread::spawn(move || {
controller::owner_listener( controller::owner_listener(
thr_wallet, thr_wallet,
km, km,
address_str.as_str(), address_str.as_str(),
None, None,
None, None,
Some(true), Some(true),
None, None,
false, false,
) )
.unwrap() .unwrap()
}); });
let http_client = Arc::new( let http_client = Arc::new(
HttpWallet::async_open_wallet(&owner_addr, &None, &ZeroingString::from("pass")) HttpWallet::async_open_wallet(&owner_addr, &None, &ZeroingString::from("pass"))
.await .await
.unwrap(), .unwrap(),
); );
IntegrationGrinWallet { IntegrationGrinWallet {
wallet, wallet,
api_listen_port, api_listen_port,
owner_api, owner_api,
http_client, http_client,
} }
} }
pub async fn async_retrieve_summary_info(&self) -> Result<WalletInfo, mwixnet::WalletError> { pub async fn async_retrieve_summary_info(&self) -> Result<WalletInfo, mwixnet::WalletError> {
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"refresh_from_node": true, "refresh_from_node": true,
"minimum_confirmations": 1 "minimum_confirmations": 1
}); });
let (_, wallet_info): (bool, WalletInfo) = self let (_, wallet_info): (bool, WalletInfo) = self
.http_client .http_client
.clone() .clone()
.async_perform_request("retrieve_summary_info", &params) .async_perform_request("retrieve_summary_info", &params)
.await?; .await?;
Ok(wallet_info) Ok(wallet_info)
} }
pub async fn async_send( pub async fn async_send(
&self, &self,
receiving_wallet: &IntegrationGrinWallet, receiving_wallet: &IntegrationGrinWallet,
amount: u64, amount: u64,
) -> Result<Transaction, mwixnet::WalletError> { ) -> Result<Transaction, mwixnet::WalletError> {
let slate = self.async_init_send_tx(amount).await.unwrap(); let slate = self.async_init_send_tx(amount).await.unwrap();
let slate = receiving_wallet.async_receive_tx(&slate).await.unwrap(); let slate = receiving_wallet.async_receive_tx(&slate).await.unwrap();
let slate = self.async_finalize_tx(&slate).await.unwrap(); let slate = self.async_finalize_tx(&slate).await.unwrap();
let tx = Slate::from(slate).tx_or_err().unwrap().clone(); let tx = Slate::from(slate).tx_or_err().unwrap().clone();
Ok(tx) Ok(tx)
} }
async fn async_init_send_tx( async fn async_init_send_tx(
&self, &self,
amount: u64, amount: u64,
) -> Result<VersionedSlate, mwixnet::WalletError> { ) -> Result<VersionedSlate, mwixnet::WalletError> {
let args = InitTxArgs { let args = InitTxArgs {
src_acct_name: None, src_acct_name: None,
amount, amount,
minimum_confirmations: 0, minimum_confirmations: 0,
max_outputs: 10, max_outputs: 10,
num_change_outputs: 1, num_change_outputs: 1,
selection_strategy_is_use_all: false, selection_strategy_is_use_all: false,
..Default::default() ..Default::default()
}; };
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"args": args "args": args
}); });
let slate: VersionedSlate = self let slate: VersionedSlate = self
.http_client .http_client
.clone() .clone()
.async_perform_request("init_send_tx", &params) .async_perform_request("init_send_tx", &params)
.await?; .await?;
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"slate": &slate "slate": &slate
}); });
self.http_client self.http_client
.clone() .clone()
.async_perform_request("tx_lock_outputs", &params) .async_perform_request("tx_lock_outputs", &params)
.await?; .await?;
Ok(slate) Ok(slate)
} }
pub async fn async_receive_tx( pub async fn async_receive_tx(
&self, &self,
slate: &VersionedSlate, slate: &VersionedSlate,
) -> Result<VersionedSlate, grin_servers::common::types::Error> { ) -> Result<VersionedSlate, grin_servers::common::types::Error> {
let req_body = json!({ let params = json!([slate, null, null]);
"jsonrpc": "2.0", let response =
"method": "receive_tx", http::async_send_json_request(&self.foreign_api(), &None, "receive_tx", &params)
"id": 1, .await
"params": [slate, null, null] .map_err(|e| {
}); let report = format!("Failed to receive tx. Is the wallet listening? {}", e);
error!("{}", report);
grin_servers::common::types::Error::WalletComm(report)
})?;
let res: Response = client::post_async(self.foreign_api().as_str(), &req_body, None) Ok(response)
.await }
.map_err(|e| {
let report = format!("Failed to receive tx. Is the wallet listening? {}", e);
error!("{}", report);
grin_servers::common::types::Error::WalletComm(report)
})?;
let parsed: VersionedSlate = res.clone().into_result().map_err(|e| { async fn async_finalize_tx(
let report = format!("Error parsing result: {}", e); &self,
error!("{}", report); slate: &VersionedSlate,
grin_servers::common::types::Error::WalletComm(report) ) -> Result<VersionedSlate, mwixnet::WalletError> {
})?; let params = json!({
Ok(parsed)
}
async fn async_finalize_tx(
&self,
slate: &VersionedSlate,
) -> Result<VersionedSlate, mwixnet::WalletError> {
let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"slate": slate "slate": slate
}); });
self.http_client self.http_client
.clone() .clone()
.async_perform_request("finalize_tx", &params) .async_perform_request("finalize_tx", &params)
.await .await
} }
async fn async_post_tx( #[allow(dead_code)]
&self, async fn async_post_tx(
finalized_slate: &VersionedSlate, &self,
fluff: bool, finalized_slate: &VersionedSlate,
) -> Result<VersionedSlate, mwixnet::WalletError> { fluff: bool,
let params = json!({ ) -> Result<VersionedSlate, mwixnet::WalletError> {
let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"slate": finalized_slate, "slate": finalized_slate,
"fluff": fluff "fluff": fluff
}); });
self.http_client self.http_client
.clone() .clone()
.async_perform_request("post_tx", &params) .async_perform_request("post_tx", &params)
.await .await
} }
/// Call the wallet API to create a coinbase output for the given block_fees. /// Call the wallet API to create a coinbase output for the given block_fees.
/// Will retry based on default "retry forever with backoff" behavior. /// Will retry based on default "retry forever with backoff" behavior.
pub async fn async_create_coinbase( pub async fn async_create_coinbase(
&self, &self,
block_fees: &BlockFees, block_fees: &BlockFees,
) -> Result<CbData, grin_servers::common::types::Error> { ) -> Result<CbData, grin_servers::common::types::Error> {
let req_body = json!({ let params = json!({
"jsonrpc": "2.0", "block_fees": block_fees
"method": "build_coinbase",
"id": 1,
"params": {
"block_fees": block_fees
}
}); });
let response =
http::async_send_json_request(&self.foreign_api(), &None, "build_coinbase", &params)
.await
.map_err(|e| {
let report = format!("Failed to get coinbase. Is the wallet listening? {}", e);
error!("{}", report);
grin_servers::common::types::Error::WalletComm(report)
})?;
let res: Response = client::post_async(self.foreign_api().as_str(), &req_body, None) Ok(response)
.await }
.map_err(|e| {
let report = format!("Failed to get coinbase. Is the wallet listening? {}", e);
error!("{}", report);
grin_servers::common::types::Error::WalletComm(report)
})?;
let parsed: CbData = res.clone().into_result().map_err(|e| {
let report = format!("Error parsing result: {}", e);
error!("{}", report);
grin_servers::common::types::Error::WalletComm(report)
})?;
Ok(parsed) pub fn build_onion(
} &self,
commitment: &Commitment,
server_pubkeys: &Vec<xPublicKey>,
) -> Result<(Onion, ComSignature), grin_wallet_libwallet::Error> {
let keychain = self
.wallet
.lock()
.lc_provider()?
.wallet_inst()?
.keychain(self.keychain_mask().as_ref())?;
let (_, outputs) =
self.owner_api
.retrieve_outputs(self.keychain_mask().as_ref(), false, false, None)?;
pub fn build_onion( let mut output = None;
&self, for o in &outputs {
commitment: &Commitment, if o.commit == *commitment {
server_pubkeys: &Vec<xPublicKey>, output = Some(o.output.clone());
) -> Result<(Onion, ComSignature), grin_wallet_libwallet::Error> { break;
let keychain = self }
.wallet }
.lock()
.lc_provider()?
.wallet_inst()?
.keychain(self.keychain_mask().as_ref())?;
let (_, outputs) =
self.owner_api
.retrieve_outputs(self.keychain_mask().as_ref(), false, false, None)?;
let mut output = None; if output.is_none() {
for o in &outputs { return Err(grin_wallet_libwallet::Error::GenericError(String::from(
if o.commit == *commitment { "output not found",
output = Some(o.output.clone()); )));
break; }
}
}
if output.is_none() { let amount = output.clone().unwrap().value;
return Err(grin_wallet_libwallet::Error::GenericError(String::from( let input_blind = keychain.derive_key(
"output not found", amount,
))); &output.clone().unwrap().key_id,
} SwitchCommitmentType::Regular,
)?;
let amount = output.clone().unwrap().value; let fee = tx_fee(1, 1, 1);
let input_blind = keychain.derive_key( let new_amount = amount - (fee * server_pubkeys.len() as u64);
amount, let new_output = self.owner_api.build_output(
&output.clone().unwrap().key_id, self.keychain_mask().as_ref(),
SwitchCommitmentType::Regular, OutputFeatures::Plain,
)?; new_amount,
)?;
let fee = tx_fee(1, 1, 1); let secp = Secp256k1::new();
let new_amount = amount - (fee * server_pubkeys.len() as u64); let mut blind_sum = new_output
let new_output = self.owner_api.build_output( .blind
self.keychain_mask().as_ref(), .split(&BlindingFactor::from_secret_key(input_blind.clone()), &secp)?;
OutputFeatures::Plain,
new_amount,
)?;
let secp = Secp256k1::new(); let hops = server_pubkeys
let mut blind_sum = new_output .iter()
.blind .enumerate()
.split(&BlindingFactor::from_secret_key(input_blind.clone()), &secp)?; .map(|(i, &p)| {
if (i + 1) == server_pubkeys.len() {
Hop {
server_pubkey: p.clone(),
excess: blind_sum.secret_key(&secp).unwrap(),
fee: FeeFields::from(fee as u32),
rangeproof: Some(new_output.output.proof.clone()),
}
} else {
let hop_excess = BlindingFactor::rand(&secp);
blind_sum = blind_sum.split(&hop_excess, &secp).unwrap();
Hop {
server_pubkey: p.clone(),
excess: hop_excess.secret_key(&secp).unwrap(),
fee: FeeFields::from(fee as u32),
rangeproof: None,
}
}
})
.collect();
let hops = server_pubkeys let onion = grin_onion::create_onion(&commitment, &hops).unwrap();
.iter() let comsig = ComSignature::sign(amount, &input_blind, &onion.serialize().unwrap()).unwrap();
.enumerate()
.map(|(i, &p)| {
if (i + 1) == server_pubkeys.len() {
Hop {
server_pubkey: p.clone(),
excess: blind_sum.secret_key(&secp).unwrap(),
fee: FeeFields::from(fee as u32),
rangeproof: Some(new_output.output.proof.clone()),
}
} else {
let hop_excess = BlindingFactor::rand(&secp);
blind_sum = blind_sum.split(&hop_excess, &secp).unwrap();
Hop {
server_pubkey: p.clone(),
excess: hop_excess.secret_key(&secp).unwrap(),
fee: FeeFields::from(fee as u32),
rangeproof: None,
}
}
})
.collect();
let onion = grin_onion::create_onion(&commitment, &hops).unwrap(); Ok((onion, comsig))
let comsig = ComSignature::sign(amount, &input_blind, &onion.serialize().unwrap()).unwrap(); }
Ok((onion, comsig)) pub fn owner_api(
} &self,
) -> Arc<
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
> {
self.owner_api.clone()
}
pub fn owner_api( pub fn foreign_api(&self) -> String {
&self, format!("http://127.0.0.1:{}/v2/foreign", self.api_listen_port)
) -> Arc< }
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
> {
self.owner_api.clone()
}
pub fn foreign_api(&self) -> String { pub fn owner_address(&self) -> SocketAddr {
format!("http://127.0.0.1:{}/v2/foreign", self.api_listen_port) SocketAddr::new(
} IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
self.api_listen_port,
)
}
pub fn owner_address(&self) -> SocketAddr { pub fn keychain_mask(&self) -> Option<SecretKey> {
SocketAddr::new( self.http_client.as_ref().get_token().keychain_mask.clone()
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), }
self.api_listen_port,
)
}
pub fn keychain_mask(&self) -> Option<SecretKey> { pub fn get_client(&self) -> Arc<HttpWallet> {
self.http_client.as_ref().get_token().keychain_mask.clone() self.http_client.clone()
} }
pub fn get_client(&self) -> Arc<HttpWallet> {
self.http_client.clone()
}
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct GrinWalletManager { pub struct GrinWalletManager {
// base directory for the server instance // base directory for the server instance
working_dir: String, working_dir: String,
wallets: Vec<Arc<Mutex<IntegrationGrinWallet>>>, wallets: Vec<Arc<Mutex<IntegrationGrinWallet>>>,
} }
impl GrinWalletManager { impl GrinWalletManager {
pub fn new(test_dir: &str) -> GrinWalletManager { pub fn new(test_dir: &str) -> GrinWalletManager {
GrinWalletManager { GrinWalletManager {
working_dir: String::from(test_dir), working_dir: String::from(test_dir),
wallets: vec![], wallets: vec![],
} }
} }
pub async fn async_new_wallet( pub async fn async_new_wallet(
&mut self, &mut self,
node_api_addr: &SocketAddr, node_api_addr: &SocketAddr,
) -> Arc<Mutex<IntegrationGrinWallet>> { ) -> Arc<Mutex<IntegrationGrinWallet>> {
let wallet_dir = format!("{}/wallets/{}", self.working_dir, self.wallets.len()); let wallet_dir = format!("{}/wallets/{}", self.working_dir, self.wallets.len());
let wallet = Arc::new(Mutex::new( let wallet = Arc::new(Mutex::new(
IntegrationGrinWallet::async_new_wallet( IntegrationGrinWallet::async_new_wallet(
wallet_dir, wallet_dir,
21000 + self.wallets.len() as u16, 21000 + self.wallets.len() as u16,
format!("http://{}", node_api_addr), format!("http://{}", node_api_addr),
) )
.await, .await,
)); ));
self.wallets.push(wallet.clone()); self.wallets.push(wallet.clone());
wallet wallet
} }
} }

View file

@ -1,18 +1,20 @@
use crate::common::miner::Miner; #[macro_use]
use crate::common::node::GrinNodeManager; extern crate log;
use crate::common::server::Servers;
use crate::common::wallet::GrinWalletManager; use std::ops::Deref;
use function_name::named; use function_name::named;
use grin_core::global; use grin_core::global;
use grin_util::logger::LoggingConfig; use grin_util::logger::LoggingConfig;
use log::Level; use log::Level;
use std::ops::Deref;
use crate::common::miner::Miner;
use crate::common::node::GrinNodeManager;
use crate::common::server::Servers;
use crate::common::wallet::GrinWalletManager;
mod common; mod common;
#[macro_use]
extern crate log;
/// Just removes all results from previous runs /// Just removes all results from previous runs
fn clean_all_output(test_dir: &str) { fn clean_all_output(test_dir: &str) {
if let Err(e) = remove_dir_all::remove_dir_all(test_dir) { if let Err(e) = remove_dir_all::remove_dir_all(test_dir) {
@ -42,8 +44,7 @@ fn setup_test(test_name: &str) -> (GrinNodeManager, GrinWalletManager, String) {
#[named] #[named]
fn integration_test() -> Result<(), Box<dyn std::error::Error>> { fn integration_test() -> Result<(), Box<dyn std::error::Error>> {
let (mut nodes, mut wallets, test_dir) = setup_test(function_name!()); let (mut nodes, mut wallets, test_dir) = setup_test(function_name!());
let mut rt = tokio::runtime::Builder::new() let rt = tokio::runtime::Builder::new_multi_thread()
.threaded_scheduler()
.enable_all() .enable_all()
.build()?; .build()?;