mirror of
https://github.com/mimblewimble/mwixnet.git
synced 2025-01-20 19:11:09 +03:00
replace TorProcess with rust-native arti client and upgrade to tokio1
This commit is contained in:
parent
df2cb31258
commit
75f2bdf2ba
21 changed files with 5663 additions and 2432 deletions
3373
Cargo.lock
generated
3373
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
39
Cargo.toml
39
Cargo.toml
|
@ -9,45 +9,54 @@ edition = "2021"
|
|||
members = ["onion"]
|
||||
|
||||
[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"
|
||||
blake2 = { package = "blake2-rfc", version = "0.2"}
|
||||
blake2 = { package = "blake2-rfc", version = "0.2" }
|
||||
byteorder = "1"
|
||||
bytes = "1.5.0"
|
||||
chacha20 = "0.9.1"
|
||||
chrono = "0.4.31"
|
||||
clap = { version = "2.33", features = ["yaml"] }
|
||||
ctrlc = { version = "3.1", features = ["termination"] }
|
||||
curve25519-dalek = "2.1"
|
||||
curve25519-dalek = "4.1.2"
|
||||
dirs = "2.0"
|
||||
ed25519-dalek = "1.0.1"
|
||||
ed25519-dalek = "2.1.1"
|
||||
function_name = "0.3.0"
|
||||
futures = "0.3"
|
||||
hmac = { version = "0.12.0", features = ["std"]}
|
||||
hyper = "0.13"
|
||||
hyper-proxy = "0.9.1"
|
||||
hyper-socks2 = "0.5.0"
|
||||
hyper-timeout = { version = "0.3", features = [] }
|
||||
fs-mistrust = "0.7.9"
|
||||
hmac = { version = "0.12.0", features = ["std"] }
|
||||
hyper = "0.14.28"
|
||||
hyper-tls = "0.6.0"
|
||||
itertools = { version = "0.12.0" }
|
||||
jsonrpc-core = "17.1"
|
||||
jsonrpc-derive = "17.1"
|
||||
jsonrpc-http-server = "17.1"
|
||||
jsonrpc-core = "18.0.0"
|
||||
jsonrpc-derive = "18.0.0"
|
||||
jsonrpc-http-server = "18.0.0"
|
||||
lazy_static = "1"
|
||||
pbkdf2 = "0.8.0"
|
||||
rand = "0.7.3"
|
||||
remove_dir_all = "0.8.2"
|
||||
ring = "0.16"
|
||||
rpassword = "4.0"
|
||||
serde = { version = "1", features= ["derive"]}
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_derive = "1"
|
||||
serde_json = "1"
|
||||
sha2 = "0.10.0"
|
||||
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"
|
||||
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"
|
||||
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_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" }
|
||||
|
|
|
@ -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 grin_core::core::FeeFields;
|
||||
use secp256k1zkp::pedersen::RangeProof;
|
||||
use x25519_dalek::PublicKey as xPublicKey;
|
||||
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)]
|
||||
pub struct Hop {
|
||||
|
@ -28,7 +28,7 @@ pub fn new_hop(
|
|||
Hop {
|
||||
server_pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())),
|
||||
excess: hop_excess.clone(),
|
||||
fee: FeeFields::from(fee as u32),
|
||||
fee: FeeFields::from(fee),
|
||||
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() {
|
||||
let mut cipher = new_stream_cipher(&shared_secrets[i])?;
|
||||
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 {
|
||||
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::secp;
|
||||
|
||||
use grin_core::core::hash::Hash;
|
||||
use grin_util::ToHex;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use secp256k1zkp::Secp256k1;
|
||||
use super::*;
|
||||
|
||||
pub fn rand_onion() -> Onion {
|
||||
let commit = rand_commit();
|
||||
|
@ -116,20 +117,20 @@ pub mod test_util {
|
|||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
let secp = Secp256k1::new();
|
||||
secp.bullet_proof(
|
||||
rand::thread_rng().next_u64(),
|
||||
secp::random_secret(),
|
||||
secp::random_secret(),
|
||||
secp::random_secret(),
|
||||
thread_rng().next_u64(),
|
||||
random_secret(),
|
||||
random_secret(),
|
||||
random_secret(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
@ -153,8 +154,8 @@ pub mod test_util {
|
|||
let rp = secp.bullet_proof(
|
||||
out_value,
|
||||
blind.clone(),
|
||||
secp::random_secret(),
|
||||
secp::random_secret(),
|
||||
random_secret(),
|
||||
random_secret(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
|
|
@ -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::hash::{Hash, Hasher};
|
||||
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 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>;
|
||||
pub type RawBytes = Vec<u8>;
|
||||
|
||||
|
@ -328,12 +329,13 @@ impl From<ser::Error> for OnionError {
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::crypto::secp::random_secret;
|
||||
use crate::{create_onion, new_hop, Hop};
|
||||
|
||||
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]
|
||||
fn onion() {
|
||||
|
@ -381,7 +383,7 @@ pub mod tests {
|
|||
let mut payload = Payload {
|
||||
next_ephemeral_pk: onion_packet.ephemeral_pubkey.clone(),
|
||||
excess: random_secret(),
|
||||
fee: FeeFields::from(fee_per_hop as u32),
|
||||
fee: FeeFields::from(fee_per_hop),
|
||||
rangeproof: None,
|
||||
};
|
||||
for i in 0..5 {
|
||||
|
@ -393,6 +395,6 @@ pub mod tests {
|
|||
assert!(payload.rangeproof.is_some());
|
||||
assert_eq!(payload.rangeproof.unwrap(), hops[4].rangeproof.unwrap());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,372 +1,372 @@
|
|||
use mwixnet::config::{self, ServerConfig};
|
||||
use mwixnet::node::HttpGrinNode;
|
||||
use mwixnet::servers;
|
||||
use mwixnet::store::SwapStore;
|
||||
use mwixnet::tor;
|
||||
use mwixnet::wallet::HttpWallet;
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
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::sync::{Arc, Mutex};
|
||||
use std::thread::{sleep, spawn};
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
use clap::App;
|
||||
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;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
real_main()?;
|
||||
std::process::exit(0);
|
||||
real_main()?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn real_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let yml = load_yaml!("mwixnet.yml");
|
||||
let args = App::from_yaml(yml).get_matches();
|
||||
let chain_type = if args.is_present("testnet") {
|
||||
ChainTypes::Testnet
|
||||
} else {
|
||||
ChainTypes::Mainnet
|
||||
};
|
||||
global::set_local_chain_type(chain_type);
|
||||
let yml = load_yaml!("mwixnet.yml");
|
||||
let args = App::from_yaml(yml).get_matches();
|
||||
let chain_type = if args.is_present("testnet") {
|
||||
ChainTypes::Testnet
|
||||
} else {
|
||||
ChainTypes::Mainnet
|
||||
};
|
||||
global::set_local_chain_type(chain_type);
|
||||
|
||||
let config_path = match args.value_of("config_file") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => {
|
||||
let mut grin_path = config::get_grin_path(&chain_type);
|
||||
grin_path.push("mwixnet-config.toml");
|
||||
grin_path
|
||||
}
|
||||
};
|
||||
let config_path = match args.value_of("config_file") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => {
|
||||
let mut grin_path = config::get_grin_path(&chain_type);
|
||||
grin_path.push("mwixnet-config.toml");
|
||||
grin_path
|
||||
}
|
||||
};
|
||||
|
||||
let round_time = args
|
||||
.value_of("round_time")
|
||||
.map(|t| t.parse::<u32>().unwrap());
|
||||
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_secret_path = args.value_of("grin_node_secret_path");
|
||||
let wallet_owner_url = args.value_of("wallet_owner_url");
|
||||
let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path");
|
||||
let prev_server = args
|
||||
.value_of("prev_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
let next_server = args
|
||||
.value_of("next_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
let round_time = args
|
||||
.value_of("round_time")
|
||||
.map(|t| t.parse::<u32>().unwrap());
|
||||
let bind_addr = args.value_of("bind_addr");
|
||||
let grin_node_url = args.value_of("grin_node_url");
|
||||
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_secret_path = args.value_of("wallet_owner_secret_path");
|
||||
let prev_server = args
|
||||
.value_of("prev_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
let next_server = args
|
||||
.value_of("next_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
|
||||
// Write a new config file if init-config command is supplied
|
||||
if let ("init-config", Some(_)) = args.subcommand() {
|
||||
if config_path.exists() {
|
||||
panic!(
|
||||
"Config file already exists at {}",
|
||||
config_path.to_string_lossy()
|
||||
);
|
||||
}
|
||||
// Write a new config file if init-config command is supplied
|
||||
if let ("init-config", Some(_)) = args.subcommand() {
|
||||
if config_path.exists() {
|
||||
panic!(
|
||||
"Config file already exists at {}",
|
||||
config_path.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
let server_config = ServerConfig {
|
||||
key: crypto::secp::random_secret(),
|
||||
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
|
||||
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 {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::grin_node_url(&chain_type),
|
||||
},
|
||||
grin_node_secret_path: match grin_node_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::node_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
wallet_owner_url: match wallet_owner_url {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::wallet_owner_url(&chain_type),
|
||||
},
|
||||
wallet_owner_secret_path: match wallet_owner_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::wallet_owner_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
prev_server,
|
||||
next_server,
|
||||
};
|
||||
let server_config = ServerConfig {
|
||||
key: crypto::secp::random_secret(),
|
||||
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
|
||||
addr: bind_addr.unwrap_or("127.0.0.1:3000").parse()?,
|
||||
grin_node_url: match grin_node_url {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::grin_node_url(&chain_type),
|
||||
},
|
||||
grin_node_secret_path: match grin_node_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::node_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
wallet_owner_url: match wallet_owner_url {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::wallet_owner_url(&chain_type),
|
||||
},
|
||||
wallet_owner_secret_path: match wallet_owner_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::wallet_owner_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
prev_server,
|
||||
next_server,
|
||||
};
|
||||
|
||||
let password = prompt_password_confirm();
|
||||
config::write_config(&config_path, &server_config, &password)?;
|
||||
println!(
|
||||
"Config file written to {:?}. Please back this file up in a safe place.",
|
||||
config_path
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let password = prompt_password_confirm();
|
||||
config::write_config(&config_path, &server_config, &password)?;
|
||||
println!(
|
||||
"Config file written to {:?}. Please back this file up in a safe place.",
|
||||
config_path
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let password = prompt_password();
|
||||
let mut server_config = config::load_config(&config_path, &password)?;
|
||||
let password = prompt_password();
|
||||
let mut server_config = config::load_config(&config_path, &password)?;
|
||||
|
||||
// Override grin_node_url, if supplied
|
||||
if let Some(grin_node_url) = grin_node_url {
|
||||
server_config.grin_node_url = grin_node_url.parse()?;
|
||||
}
|
||||
// Override grin_node_url, if supplied
|
||||
if let Some(grin_node_url) = grin_node_url {
|
||||
server_config.grin_node_url = grin_node_url.parse()?;
|
||||
}
|
||||
|
||||
// Override grin_node_secret_path, if supplied
|
||||
if let Some(grin_node_secret_path) = grin_node_secret_path {
|
||||
server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned());
|
||||
}
|
||||
// Override grin_node_secret_path, if supplied
|
||||
if let Some(grin_node_secret_path) = grin_node_secret_path {
|
||||
server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned());
|
||||
}
|
||||
|
||||
// Override wallet_owner_url, if supplied
|
||||
if let Some(wallet_owner_url) = wallet_owner_url {
|
||||
server_config.wallet_owner_url = wallet_owner_url.parse()?;
|
||||
}
|
||||
// Override wallet_owner_url, if supplied
|
||||
if let Some(wallet_owner_url) = wallet_owner_url {
|
||||
server_config.wallet_owner_url = wallet_owner_url.parse()?;
|
||||
}
|
||||
|
||||
// Override wallet_owner_secret_path, if supplied
|
||||
if let Some(wallet_owner_secret_path) = wallet_owner_secret_path {
|
||||
server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned());
|
||||
}
|
||||
// Override wallet_owner_secret_path, if supplied
|
||||
if let Some(wallet_owner_secret_path) = wallet_owner_secret_path {
|
||||
server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned());
|
||||
}
|
||||
|
||||
// Override bind_addr, if supplied
|
||||
if let Some(bind_addr) = bind_addr {
|
||||
server_config.addr = bind_addr.parse()?;
|
||||
}
|
||||
// Override bind_addr, if supplied
|
||||
if let Some(bind_addr) = bind_addr {
|
||||
server_config.addr = bind_addr.parse()?;
|
||||
}
|
||||
|
||||
// Override socks_addr, if supplied
|
||||
if let Some(socks_addr) = socks_addr {
|
||||
server_config.socks_proxy_addr = socks_addr.parse()?;
|
||||
}
|
||||
// Override prev_server, if supplied
|
||||
if let Some(prev_server) = prev_server {
|
||||
server_config.prev_server = Some(prev_server);
|
||||
}
|
||||
|
||||
// Override prev_server, if supplied
|
||||
if let Some(prev_server) = prev_server {
|
||||
server_config.prev_server = Some(prev_server);
|
||||
}
|
||||
// Override next_server, if supplied
|
||||
if let Some(next_server) = next_server {
|
||||
server_config.next_server = Some(next_server);
|
||||
}
|
||||
|
||||
// Override next_server, if supplied
|
||||
if let Some(next_server) = next_server {
|
||||
server_config.next_server = Some(next_server);
|
||||
}
|
||||
// Create GrinNode
|
||||
let node = HttpGrinNode::new(
|
||||
&server_config.grin_node_url,
|
||||
&server_config.node_api_secret(),
|
||||
);
|
||||
|
||||
// Create GrinNode
|
||||
let node = HttpGrinNode::new(
|
||||
&server_config.grin_node_url,
|
||||
&server_config.node_api_secret(),
|
||||
);
|
||||
// Node API health check
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.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
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
if let Err(e) = rt.block_on(node.async_get_chain_tip()) {
|
||||
eprintln!("Node communication failure. Is node listening?");
|
||||
return Err(e.into());
|
||||
};
|
||||
// Open wallet
|
||||
let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass"));
|
||||
let wallet = rt.block_on(HttpWallet::async_open_wallet(
|
||||
&server_config.wallet_owner_url,
|
||||
&server_config.wallet_owner_api_secret(),
|
||||
&wallet_pass,
|
||||
));
|
||||
let wallet = match wallet {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("Wallet communication failure. Is wallet listening?");
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
// Open wallet
|
||||
let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass"));
|
||||
let wallet = rt.block_on(HttpWallet::async_open_wallet(
|
||||
&server_config.wallet_owner_url,
|
||||
&server_config.wallet_owner_api_secret(),
|
||||
&wallet_pass,
|
||||
));
|
||||
let wallet = match wallet {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("Wallet communication failure. Is wallet listening?");
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
let tor_instance = rt.block_on(tor::async_init_tor(
|
||||
PreferredRuntime::current().unwrap(),
|
||||
&config::get_grin_path(&chain_type).to_str().unwrap(),
|
||||
&server_config,
|
||||
))?;
|
||||
let tor_instance = Arc::new(grin_util::Mutex::new(tor_instance));
|
||||
let tor_clone = tor_instance.clone();
|
||||
|
||||
let mut tor_process = tor::init_tor_listener(
|
||||
&config::get_grin_path(&chain_type).to_str().unwrap(),
|
||||
&server_config,
|
||||
)?;
|
||||
let stop_state = Arc::new(StopState::new());
|
||||
let stop_state_clone = stop_state.clone();
|
||||
|
||||
let stop_state = Arc::new(StopState::new());
|
||||
let stop_state_clone = stop_state.clone();
|
||||
rt.spawn(async move {
|
||||
futures::executor::block_on(build_signals_fut());
|
||||
tor_clone.lock().stop();
|
||||
stop_state_clone.stop();
|
||||
});
|
||||
|
||||
rt.spawn(async move {
|
||||
futures::executor::block_on(build_signals_fut());
|
||||
let _ = tor_process.kill();
|
||||
stop_state_clone.stop();
|
||||
});
|
||||
let next_mixer: Option<Arc<dyn MixClient>> = server_config.next_server.clone().map(|pk| {
|
||||
let client: Arc<dyn MixClient> = Arc::new(MixClientImpl::new(
|
||||
server_config.clone(),
|
||||
tor_instance.clone(),
|
||||
pk.clone(),
|
||||
));
|
||||
client
|
||||
});
|
||||
|
||||
let next_mixer: Option<Arc<dyn MixClient>> = server_config.next_server.clone().map(|pk| {
|
||||
let client: Arc<dyn MixClient> =
|
||||
Arc::new(MixClientImpl::new(server_config.clone(), pk.clone()));
|
||||
client
|
||||
});
|
||||
if server_config.prev_server.is_some() {
|
||||
// Start the JSON-RPC HTTP 'mix' server
|
||||
println!(
|
||||
"Starting MIX server with public key {:?}",
|
||||
server_config.server_pubkey().to_hex()
|
||||
);
|
||||
|
||||
if server_config.prev_server.is_some() {
|
||||
// Start the JSON-RPC HTTP 'mix' server
|
||||
println!(
|
||||
"Starting MIX server with public key {:?}",
|
||||
server_config.server_pubkey().to_hex()
|
||||
);
|
||||
let (_, http_server) = servers::mix_rpc::listen(
|
||||
rt.handle(),
|
||||
server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
)?;
|
||||
|
||||
let (_, http_server) = servers::mix_rpc::listen(
|
||||
rt.handle(),
|
||||
server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
)?;
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || loop {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || loop {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
});
|
||||
|
||||
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();
|
||||
round_handle.join().unwrap();
|
||||
} else {
|
||||
println!(
|
||||
"Starting SWAP server with public key {:?}",
|
||||
server_config.server_pubkey().to_hex()
|
||||
);
|
||||
// Open SwapStore
|
||||
let store = SwapStore::new(
|
||||
config::get_grin_path(&chain_type)
|
||||
.join("db")
|
||||
.to_str()
|
||||
.ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr(
|
||||
"db_root path error".to_string(),
|
||||
)))?,
|
||||
)?;
|
||||
|
||||
// Open SwapStore
|
||||
let store = SwapStore::new(
|
||||
config::get_grin_path(&chain_type)
|
||||
.join("db")
|
||||
.to_str()
|
||||
.ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr(
|
||||
"db_root path error".to_string(),
|
||||
)))?,
|
||||
)?;
|
||||
// Start the mwixnet JSON-RPC HTTP 'swap' server
|
||||
let (swap_server, http_server) = servers::swap_rpc::listen(
|
||||
rt.handle(),
|
||||
&server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
store,
|
||||
)?;
|
||||
|
||||
// Start the mwixnet JSON-RPC HTTP 'swap' server
|
||||
let (swap_server, http_server) = servers::swap_rpc::listen(
|
||||
rt.handle(),
|
||||
&server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
store,
|
||||
)?;
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || {
|
||||
let mut rng = thread_rng();
|
||||
let mut secs = 0u32;
|
||||
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();
|
||||
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || {
|
||||
let mut rng = thread_rng();
|
||||
let mut secs = 0u32;
|
||||
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 {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
|
||||
loop {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_secs(1));
|
||||
secs = (secs + 1) % server_config.interval_s;
|
||||
reorg_secs = (reorg_secs + 1) % reorg_window;
|
||||
|
||||
sleep(Duration::from_secs(1));
|
||||
secs = (secs + 1) % server_config.interval_s;
|
||||
reorg_secs = (reorg_secs + 1) % reorg_window;
|
||||
if secs == 0 {
|
||||
let prev_tx_clone = prev_tx.clone();
|
||||
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 {
|
||||
let prev_tx_clone = prev_tx.clone();
|
||||
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 let Some(tx) = tx_option {
|
||||
let result = server_clone.lock().await.check_reorg(&tx).await;
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(tx) = tx_option {
|
||||
let result = server_clone.lock().await.check_reorg(tx).await;
|
||||
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();
|
||||
round_handle.join().unwrap();
|
||||
}
|
||||
|
||||
http_server.wait();
|
||||
round_handle.join().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn build_signals_fut() {
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
// Listen for SIGINT, SIGQUIT, and SIGTERM
|
||||
let mut 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 interrupt_signal =
|
||||
signal(SignalKind::interrupt()).expect("failed to create interrupt signal");
|
||||
// Listen for SIGINT, SIGQUIT, and SIGTERM
|
||||
let mut 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 interrupt_signal =
|
||||
signal(SignalKind::interrupt()).expect("failed to create interrupt signal");
|
||||
|
||||
futures::future::select_all(vec![
|
||||
Box::pin(terminate_signal.recv()),
|
||||
Box::pin(quit_signal.recv()),
|
||||
Box::pin(interrupt_signal.recv()),
|
||||
])
|
||||
.await;
|
||||
futures::future::select_all(vec![
|
||||
Box::pin(terminate_signal.recv()),
|
||||
Box::pin(quit_signal.recv()),
|
||||
Box::pin(interrupt_signal.recv()),
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn build_signals_fut() {
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install CTRL+C signal handler");
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install CTRL+C signal handler");
|
||||
}
|
||||
|
||||
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 {
|
||||
let mut first = "first".to_string();
|
||||
let mut second = "second".to_string();
|
||||
while first != second {
|
||||
first = rpassword::prompt_password_stdout("Server password: ").unwrap();
|
||||
second = rpassword::prompt_password_stdout("Confirm server password: ").unwrap();
|
||||
}
|
||||
ZeroingString::from(first)
|
||||
let mut first = "first".to_string();
|
||||
let mut second = "second".to_string();
|
||||
while first != second {
|
||||
first = rpassword::prompt_password_stdout("Server password: ").unwrap();
|
||||
second = rpassword::prompt_password_stdout("Confirm server password: ").unwrap();
|
||||
}
|
||||
ZeroingString::from(first)
|
||||
}
|
||||
|
||||
fn prompt_wallet_password(wallet_pass: &Option<&str>) -> ZeroingString {
|
||||
match *wallet_pass {
|
||||
Some(wallet_pass) => ZeroingString::from(wallet_pass),
|
||||
None => {
|
||||
ZeroingString::from(rpassword::prompt_password_stdout("Wallet password: ").unwrap())
|
||||
}
|
||||
}
|
||||
match *wallet_pass {
|
||||
Some(wallet_pass) => ZeroingString::from(wallet_pass),
|
||||
None => {
|
||||
ZeroingString::from(rpassword::prompt_password_stdout("Wallet password: ").unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,10 +38,6 @@ args:
|
|||
help: Address to bind the rpc server to (e.g. 127.0.0.1:3000)
|
||||
long: bind_addr
|
||||
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:
|
||||
help: Hex public key of the previous swap/mix server
|
||||
long: prev_server
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
use grin_onion::crypto::dalek::DalekPublicKey;
|
||||
use grin_onion::crypto::secp::SecretKey;
|
||||
|
||||
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::io::prelude::*;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
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 grin_onion::crypto::dalek::DalekPublicKey;
|
||||
use grin_onion::crypto::secp::SecretKey;
|
||||
|
||||
const GRIN_HOME: &str = ".grin";
|
||||
const NODE_API_SECRET_FILE_NAME: &str = ".api_secret";
|
||||
const WALLET_OWNER_API_SECRET_FILE_NAME: &str = ".owner_api_secret";
|
||||
|
@ -28,8 +29,6 @@ pub struct ServerConfig {
|
|||
pub interval_s: u32,
|
||||
/// socket address the server listener should bind to
|
||||
pub addr: SocketAddr,
|
||||
/// socket address the tor sender should bind to
|
||||
pub socks_proxy_addr: SocketAddr,
|
||||
/// foreign api address of the grin node
|
||||
pub grin_node_url: SocketAddr,
|
||||
/// path to file containing api secret for the grin node
|
||||
|
@ -181,7 +180,6 @@ struct RawConfig {
|
|||
nonce: String,
|
||||
interval_s: u32,
|
||||
addr: SocketAddr,
|
||||
socks_proxy_addr: SocketAddr,
|
||||
grin_node_url: SocketAddr,
|
||||
grin_node_secret_path: Option<String>,
|
||||
wallet_owner_url: SocketAddr,
|
||||
|
@ -206,7 +204,6 @@ pub fn write_config(
|
|||
nonce: encrypted.nonce,
|
||||
interval_s: server_config.interval_s,
|
||||
addr: server_config.addr,
|
||||
socks_proxy_addr: server_config.socks_proxy_addr,
|
||||
grin_node_url: server_config.grin_node_url,
|
||||
grin_node_secret_path: server_config.grin_node_secret_path.clone(),
|
||||
wallet_owner_url: server_config.wallet_owner_url,
|
||||
|
@ -243,7 +240,6 @@ pub fn load_config(
|
|||
key: secret_key,
|
||||
interval_s: raw_config.interval_s,
|
||||
addr: raw_config.addr,
|
||||
socks_proxy_addr: raw_config.socks_proxy_addr,
|
||||
grin_node_url: raw_config.grin_node_url,
|
||||
grin_node_secret_path: raw_config.grin_node_secret_path,
|
||||
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 {
|
||||
let mut grin_path = match dirs::home_dir() {
|
||||
Some(p) => p,
|
||||
None => PathBuf::new(),
|
||||
};
|
||||
let mut grin_path = dirs::home_dir().unwrap_or_else(|| PathBuf::new());
|
||||
grin_path.push(GRIN_HOME);
|
||||
grin_path.push(chain_type.shortname());
|
||||
grin_path
|
||||
|
@ -289,10 +282,12 @@ pub fn wallet_owner_url(_chain_type: &ChainTypes) -> SocketAddr {
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod test_util {
|
||||
use crate::config::ServerConfig;
|
||||
use std::net::TcpListener;
|
||||
|
||||
use grin_onion::crypto::dalek::DalekPublicKey;
|
||||
use secp256k1zkp::SecretKey;
|
||||
use std::net::TcpListener;
|
||||
|
||||
use crate::config::ServerConfig;
|
||||
|
||||
pub fn local_config(
|
||||
server_key: &SecretKey,
|
||||
|
@ -303,7 +298,6 @@ pub mod test_util {
|
|||
key: server_key.clone(),
|
||||
interval_s: 1,
|
||||
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_secret_path: None,
|
||||
wallet_owner_url: "127.0.0.1:3420".parse()?,
|
||||
|
@ -317,9 +311,10 @@ pub mod test_util {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use grin_onion::crypto::secp;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn server_key_encrypt() {
|
||||
let password = ZeroingString::from("password");
|
||||
|
|
127
src/http.rs
Normal file
127
src/http.rs
Normal 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())
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod http;
|
||||
pub mod mix_client;
|
||||
pub mod node;
|
||||
pub mod servers;
|
||||
pub mod store;
|
||||
|
@ -10,8 +11,8 @@ pub mod tor;
|
|||
pub mod tx;
|
||||
pub mod wallet;
|
||||
|
||||
pub use client::MixClient;
|
||||
pub use config::ServerConfig;
|
||||
pub use mix_client::MixClient;
|
||||
pub use node::{GrinNode, HttpGrinNode, NodeError};
|
||||
pub use servers::mix::{MixError, MixServer};
|
||||
pub use servers::mix_rpc::listen as mix_listen;
|
||||
|
|
|
@ -1,27 +1,30 @@
|
|||
use crate::config::ServerConfig;
|
||||
use crate::servers::mix_rpc::{MixReq, MixResp};
|
||||
use crate::tor;
|
||||
use grin_onion::crypto::dalek::{self, DalekPublicKey};
|
||||
use grin_onion::onion::Onion;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use grin_api::json_rpc::{build_request, Response};
|
||||
use grin_core::ser;
|
||||
use grin_core::ser::ProtocolVersion;
|
||||
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::json;
|
||||
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
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientError {
|
||||
pub enum MixClientError {
|
||||
#[error("Tor Error: {0:?}")]
|
||||
Tor(tor::TorError),
|
||||
#[error("API Error: {0:?}")]
|
||||
API(grin_api::Error),
|
||||
#[error("Communication Error: {0:?}")]
|
||||
CommError(http::HttpError),
|
||||
#[error("Dalek Error: {0:?}")]
|
||||
Dalek(dalek::DalekError),
|
||||
#[error("Error decoding JSON response: {0:?}")]
|
||||
|
@ -36,18 +39,23 @@ pub enum ClientError {
|
|||
#[async_trait]
|
||||
pub trait MixClient: Send + Sync {
|
||||
/// 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,
|
||||
tor: Arc<grin_util::Mutex<TorService<R>>>,
|
||||
addr: OnionV3Address,
|
||||
}
|
||||
|
||||
impl MixClientImpl {
|
||||
pub fn new(config: ServerConfig, next_pubkey: DalekPublicKey) -> Self {
|
||||
impl<R: Runtime> MixClientImpl<R> {
|
||||
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());
|
||||
MixClientImpl { config, addr }
|
||||
MixClientImpl { config, tor, addr }
|
||||
}
|
||||
|
||||
async fn async_send_json_request<D: serde::de::DeserializeOwned>(
|
||||
|
@ -55,61 +63,29 @@ impl MixClientImpl {
|
|||
addr: &OnionV3Address,
|
||||
method: &str,
|
||||
params: &serde_json::Value,
|
||||
) -> Result<D, ClientError> {
|
||||
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
|
||||
};
|
||||
|
||||
) -> Result<D, MixClientError> {
|
||||
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 =
|
||||
hyper::body::Body::from(serde_json::to_string(&build_request(method, params)).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 hyper_client = self.tor.lock().new_hyper_client();
|
||||
let res = hyper_client.request(hyper_request).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 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 {
|
||||
return Err(ClientError::ResponseError(e.clone()));
|
||||
return Err(MixClientError::ResponseError(e.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)
|
||||
.map_err(ClientError::DecodeResponseError),
|
||||
.map_err(MixClientError::DecodeResponseError),
|
||||
}?;
|
||||
|
||||
Ok(result)
|
||||
|
@ -117,34 +93,29 @@ impl MixClientImpl {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl MixClient for MixClientImpl {
|
||||
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, ClientError> {
|
||||
impl<R: Runtime> MixClient for MixClientImpl<R> {
|
||||
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError> {
|
||||
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
|
||||
let sig =
|
||||
dalek::sign(&self.config.key, serialized.as_slice()).map_err(ClientError::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()
|
||||
// );
|
||||
dalek::sign(&self.config.key, serialized.as_slice()).map_err(MixClientError::Dalek)?;
|
||||
let mix = MixReq::new(onions.clone(), sig);
|
||||
|
||||
let params = serde_json::json!([mix]);
|
||||
|
||||
self.async_send_json_request::<MixResp>(&self.addr, "mix", ¶ms)
|
||||
self.async_send_json_request::<MixResp>(&self.addr, "mix", &json!([mix]))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod mock {
|
||||
use super::{ClientError, MixClient};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use grin_onion::onion::Onion;
|
||||
|
||||
use crate::servers::mix_rpc::MixResp;
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{MixClient, MixClientError};
|
||||
|
||||
pub struct MockMixClient {
|
||||
results: HashMap<Vec<Onion>, MixResp>,
|
||||
|
@ -164,27 +135,33 @@ pub mod mock {
|
|||
|
||||
#[async_trait]
|
||||
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
|
||||
.get(onions)
|
||||
.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)]
|
||||
pub mod test_util {
|
||||
use super::{ClientError, MixClient};
|
||||
use crate::servers::mix::MixServer;
|
||||
use crate::servers::mix_rpc::MixResp;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use grin_core::ser;
|
||||
use grin_core::ser::ProtocolVersion;
|
||||
|
||||
use grin_onion::crypto::dalek::{self, DalekPublicKey};
|
||||
use grin_onion::crypto::secp::SecretKey;
|
||||
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.
|
||||
/// No JSON-RPC serialization or socket communication occurs.
|
||||
|
@ -196,9 +173,10 @@ pub mod test_util {
|
|||
|
||||
#[async_trait]
|
||||
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 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(
|
||||
&DalekPublicKey::from_secret(&self.key),
|
94
src/node.rs
94
src/node.rs
|
@ -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::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::{Committed, Input, OutputFeatures, Transaction};
|
||||
use grin_util::ToHex;
|
||||
use serde_json::json;
|
||||
use thiserror::Error;
|
||||
|
||||
use grin_onion::crypto::secp::Commitment;
|
||||
|
||||
use crate::http;
|
||||
|
||||
#[async_trait]
|
||||
pub trait GrinNode: Send + Sync {
|
||||
/// Retrieves the unspent output with a matching commitment
|
||||
|
@ -22,8 +23,8 @@ pub trait GrinNode: Send + Sync {
|
|||
output_commit: &Commitment,
|
||||
) -> Result<Option<OutputPrintable>, NodeError>;
|
||||
|
||||
/// Gets the height and hash of the chain tip
|
||||
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError>;
|
||||
/// Gets the height and hash of the chain tip
|
||||
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError>;
|
||||
|
||||
/// Posts a transaction to the grin node
|
||||
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError>;
|
||||
|
@ -47,6 +48,8 @@ pub enum NodeError {
|
|||
DecodeResponseError(serde_json::Error),
|
||||
#[error("JSON-RPC API communication error: {0:?}")]
|
||||
ApiCommError(grin_api::Error),
|
||||
#[error("Client error: {0:?}")]
|
||||
NodeCommError(http::HttpError),
|
||||
#[error("Error decoding JSON-RPC response: {0:?}")]
|
||||
ResponseParseError(grin_api::json_rpc::Error),
|
||||
}
|
||||
|
@ -109,21 +112,24 @@ pub async fn async_build_input(
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn async_is_tx_valid(node: &Arc<dyn GrinNode>, tx: &Transaction) -> Result<bool, NodeError> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
pub async fn async_is_tx_valid(
|
||||
node: &Arc<dyn GrinNode>,
|
||||
tx: &Transaction,
|
||||
) -> Result<bool, NodeError> {
|
||||
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() {
|
||||
if async_is_unspent(&node, &output_commit).await? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
for output_commit in &tx.outputs_committed() {
|
||||
if async_is_unspent(&node, &output_commit).await? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// HTTP (JSON-RPC) implementation of the 'GrinNode' trait
|
||||
|
@ -149,18 +155,9 @@ impl HttpGrinNode {
|
|||
params: &serde_json::Value,
|
||||
) -> Result<D, NodeError> {
|
||||
let url = format!("http://{}{}", self.node_url, ENDPOINT);
|
||||
let req = build_request(method, params);
|
||||
let res = client::post_async::<Request, Response>(
|
||||
url.as_str(),
|
||||
&req,
|
||||
self.node_api_secret.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(NodeError::ApiCommError)?;
|
||||
let parsed = res
|
||||
.clone()
|
||||
.into_result()
|
||||
.map_err(NodeError::ResponseParseError)?;
|
||||
let parsed = http::async_send_json_request(&url, &self.node_api_secret, &method, ¶ms)
|
||||
.await
|
||||
.map_err(NodeError::NodeCommError)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +191,7 @@ impl GrinNode for HttpGrinNode {
|
|||
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 tip_json = self
|
||||
.async_send_request::<serde_json::Value>("get_tip", ¶ms)
|
||||
|
@ -202,7 +199,10 @@ impl GrinNode for HttpGrinNode {
|
|||
let tip =
|
||||
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> {
|
||||
|
@ -236,15 +236,17 @@ impl GrinNode for HttpGrinNode {
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod mock {
|
||||
use super::{GrinNode, NodeError};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use grin_api::{LocatedTxKernel, OutputPrintable, OutputType};
|
||||
use grin_core::core::hash::Hash;
|
||||
use grin_core::core::Transaction;
|
||||
|
||||
use grin_onion::crypto::secp::Commitment;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use grin_core::core::hash::Hash;
|
||||
|
||||
use super::{GrinNode, NodeError};
|
||||
|
||||
/// Implementation of 'GrinNode' trait that mocks a grin node instance.
|
||||
/// Use only for testing purposes.
|
||||
|
@ -318,8 +320,8 @@ pub mod mock {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> {
|
||||
Ok((100, Hash::default()))
|
||||
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> {
|
||||
Ok((100, Hash::default()))
|
||||
}
|
||||
|
||||
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError> {
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
use crate::client::MixClient;
|
||||
use crate::config::ServerConfig;
|
||||
use crate::node::{self, GrinNode};
|
||||
use crate::tx::{self, TxComponents};
|
||||
use crate::wallet::Wallet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::servers::mix_rpc::MixResp;
|
||||
use async_trait::async_trait;
|
||||
use futures::stream::{self, StreamExt};
|
||||
use grin_core::core::{Output, OutputFeatures, TransactionBody};
|
||||
use grin_core::global::DEFAULT_ACCEPT_FEE_BASE;
|
||||
use grin_core::ser;
|
||||
use grin_core::ser::ProtocolVersion;
|
||||
use itertools::Itertools;
|
||||
use thiserror::Error;
|
||||
|
||||
use grin_onion::crypto::dalek::{self, DalekSignature};
|
||||
use grin_onion::onion::{Onion, OnionError, PeeledOnion};
|
||||
use itertools::Itertools;
|
||||
use secp256k1zkp::key::ZERO_KEY;
|
||||
use secp256k1zkp::Secp256k1;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::config::ServerConfig;
|
||||
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
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -46,7 +48,7 @@ pub enum MixError {
|
|||
#[error("Wallet error: {0:?}")]
|
||||
WalletError(crate::wallet::WalletError),
|
||||
#[error("Client comm error: {0:?}")]
|
||||
Client(crate::client::ClientError),
|
||||
Client(crate::mix_client::MixClientError),
|
||||
}
|
||||
|
||||
/// An internal MWixnet server - a "Mixer"
|
||||
|
@ -298,16 +300,17 @@ impl MixServer for MixServerImpl {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test_util {
|
||||
use crate::client::test_util::DirectMixClient;
|
||||
use crate::client::MixClient;
|
||||
use crate::config;
|
||||
use crate::node::mock::MockGrinNode;
|
||||
use crate::servers::mix::MixServerImpl;
|
||||
use crate::wallet::mock::MockWallet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use grin_onion::crypto::dalek::DalekPublicKey;
|
||||
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(
|
||||
server_key: &SecretKey,
|
||||
|
@ -340,18 +343,20 @@ mod test_util {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client::MixClient;
|
||||
use crate::node::mock::MockGrinNode;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::function_name::named;
|
||||
|
||||
use grin_onion::{create_onion, Hop, new_hop};
|
||||
use grin_onion::crypto::dalek::DalekPublicKey;
|
||||
use grin_onion::crypto::secp::{self, Commitment};
|
||||
use grin_onion::test_util as onion_test_util;
|
||||
use grin_onion::{create_onion, new_hop, Hop};
|
||||
use secp256k1zkp::pedersen::RangeProof;
|
||||
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 {
|
||||
() => {{
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
use crate::client::MixClient;
|
||||
use crate::config::ServerConfig;
|
||||
use crate::node::GrinNode;
|
||||
use crate::servers::mix::{MixError, MixServer, MixServerImpl};
|
||||
use crate::wallet::Wallet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::tx::TxComponents;
|
||||
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::onion::Onion;
|
||||
use jsonrpc_core::BoxFuture;
|
||||
use jsonrpc_derive::rpc;
|
||||
use jsonrpc_http_server::jsonrpc_core::{self as jsonrpc, IoHandler};
|
||||
use jsonrpc_http_server::{DomainsValidation, ServerBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::ServerConfig;
|
||||
use crate::mix_client::MixClient;
|
||||
use crate::node::GrinNode;
|
||||
use crate::servers::mix::{MixError, MixServer, MixServerImpl};
|
||||
use crate::tx::TxComponents;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MixReq {
|
||||
|
@ -37,7 +38,7 @@ impl MixReq {
|
|||
#[rpc(server)]
|
||||
pub trait MixAPI {
|
||||
#[rpc(name = "mix")]
|
||||
fn mix(&self, mix: MixReq) -> BoxFuture<jsonrpc::Result<MixResp>>;
|
||||
fn mix(&self, mix: MixReq) -> BoxFuture<jsonrpc_core::Result<MixResp>>;
|
||||
}
|
||||
|
||||
#[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 {
|
||||
jsonrpc::Error::invalid_params(e.to_string())
|
||||
jsonrpc_core::Error::invalid_params(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
async move {
|
||||
let response = server
|
||||
|
|
1642
src/servers/swap.rs
1642
src/servers/swap.rs
File diff suppressed because it is too large
Load diff
|
@ -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::mix_client::MixClient;
|
||||
use crate::node::GrinNode;
|
||||
use crate::servers::swap::{SwapError, SwapServer, SwapServerImpl};
|
||||
use crate::store::SwapStore;
|
||||
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)]
|
||||
pub struct SwapReq {
|
||||
onion: Onion,
|
||||
|
@ -37,7 +38,7 @@ struct RPCSwapServer {
|
|||
impl RPCSwapServer {
|
||||
/// Spin up an instance of the JSON-RPC HTTP 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()));
|
||||
|
||||
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 {
|
||||
match e {
|
||||
SwapError::UnknownError(_) => Error {
|
||||
SwapError::UnknownError(_) => jsonrpc_core::Error {
|
||||
message: e.to_string(),
|
||||
code: ErrorCode::InternalError,
|
||||
code: jsonrpc_core::ErrorCode::InternalError,
|
||||
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)]
|
||||
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::sync::Arc;
|
||||
|
||||
use hyper::{Body, Client, Request, Response};
|
||||
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 {
|
||||
let body_bytes = hyper::body::to_bytes(req.into_body()).await.unwrap();
|
||||
String::from_utf8(body_bytes.to_vec()).unwrap()
|
||||
|
@ -144,7 +146,6 @@ mod tests {
|
|||
key: secp::random_secret(),
|
||||
interval_s: 1,
|
||||
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_secret_path: None,
|
||||
wallet_owner_url: "127.0.0.1:3420".parse()?,
|
||||
|
@ -186,8 +187,7 @@ mod tests {
|
|||
/// Demonstrates a successful swap response
|
||||
#[test]
|
||||
fn swap_success() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
let commitment = secp::commit(1234, &secp::random_secret())?;
|
||||
|
@ -214,8 +214,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn swap_bad_request() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
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.
|
||||
#[test]
|
||||
fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
|
||||
|
|
627
src/store.rs
627
src/store.rs
|
@ -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::hash::Hash;
|
||||
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_util::ToHex;
|
||||
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 STORE_SUBPATH: &str = "swaps";
|
||||
|
||||
|
@ -23,380 +23,389 @@ const TX_PREFIX: u8 = b'T';
|
|||
/// Swap statuses
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SwapStatus {
|
||||
Unprocessed,
|
||||
InProcess {
|
||||
kernel_commit: Commitment,
|
||||
},
|
||||
Completed {
|
||||
kernel_commit: Commitment,
|
||||
block_hash: Hash,
|
||||
},
|
||||
Failed,
|
||||
Unprocessed,
|
||||
InProcess {
|
||||
kernel_commit: Commitment,
|
||||
},
|
||||
Completed {
|
||||
kernel_commit: Commitment,
|
||||
block_hash: Hash,
|
||||
},
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl Writeable for SwapStatus {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
match self {
|
||||
SwapStatus::Unprocessed => {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
SwapStatus::InProcess { kernel_commit } => {
|
||||
writer.write_u8(1)?;
|
||||
kernel_commit.write(writer)?;
|
||||
}
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
} => {
|
||||
writer.write_u8(2)?;
|
||||
kernel_commit.write(writer)?;
|
||||
block_hash.write(writer)?;
|
||||
}
|
||||
SwapStatus::Failed => {
|
||||
writer.write_u8(3)?;
|
||||
}
|
||||
};
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
match self {
|
||||
SwapStatus::Unprocessed => {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
SwapStatus::InProcess { kernel_commit } => {
|
||||
writer.write_u8(1)?;
|
||||
kernel_commit.write(writer)?;
|
||||
}
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
} => {
|
||||
writer.write_u8(2)?;
|
||||
kernel_commit.write(writer)?;
|
||||
block_hash.write(writer)?;
|
||||
}
|
||||
SwapStatus::Failed => {
|
||||
writer.write_u8(3)?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SwapStatus {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapStatus, ser::Error> {
|
||||
let status = match reader.read_u8()? {
|
||||
0 => SwapStatus::Unprocessed,
|
||||
1 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
SwapStatus::InProcess { kernel_commit }
|
||||
}
|
||||
2 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
let block_hash = Hash::read(reader)?;
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
}
|
||||
}
|
||||
3 => SwapStatus::Failed,
|
||||
_ => {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
};
|
||||
Ok(status)
|
||||
}
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapStatus, ser::Error> {
|
||||
let status = match reader.read_u8()? {
|
||||
0 => SwapStatus::Unprocessed,
|
||||
1 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
SwapStatus::InProcess { kernel_commit }
|
||||
}
|
||||
2 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
let block_hash = Hash::read(reader)?;
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
}
|
||||
}
|
||||
3 => SwapStatus::Failed,
|
||||
_ => {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
};
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
/// Data needed to swap a single output.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SwapData {
|
||||
/// The total excess for the output commitment
|
||||
pub excess: SecretKey,
|
||||
/// The derived output commitment after applying excess and fee
|
||||
pub output_commit: Commitment,
|
||||
/// The rangeproof, included only for the final hop (node N)
|
||||
pub rangeproof: Option<RangeProof>,
|
||||
/// Transaction input being spent
|
||||
pub input: Input,
|
||||
/// Transaction fee
|
||||
pub fee: u64,
|
||||
/// The remaining onion after peeling off our layer
|
||||
pub onion: Onion,
|
||||
/// The status of the swap
|
||||
pub status: SwapStatus,
|
||||
/// The total excess for the output commitment
|
||||
pub excess: SecretKey,
|
||||
/// The derived output commitment after applying excess and fee
|
||||
pub output_commit: Commitment,
|
||||
/// The rangeproof, included only for the final hop (node N)
|
||||
pub rangeproof: Option<RangeProof>,
|
||||
/// Transaction input being spent
|
||||
pub input: Input,
|
||||
/// Transaction fee
|
||||
pub fee: u64,
|
||||
/// The remaining onion after peeling off our layer
|
||||
pub onion: Onion,
|
||||
/// The status of the swap
|
||||
pub status: SwapStatus,
|
||||
}
|
||||
|
||||
impl Writeable for SwapData {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_SWAP_VERSION)?;
|
||||
writer.write_fixed_bytes(&self.excess)?;
|
||||
writer.write_fixed_bytes(&self.output_commit)?;
|
||||
write_optional(writer, &self.rangeproof)?;
|
||||
self.input.write(writer)?;
|
||||
writer.write_u64(self.fee.into())?;
|
||||
self.onion.write(writer)?;
|
||||
self.status.write(writer)?;
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_SWAP_VERSION)?;
|
||||
writer.write_fixed_bytes(&self.excess)?;
|
||||
writer.write_fixed_bytes(&self.output_commit)?;
|
||||
write_optional(writer, &self.rangeproof)?;
|
||||
self.input.write(writer)?;
|
||||
writer.write_u64(self.fee.into())?;
|
||||
self.onion.write(writer)?;
|
||||
self.status.write(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SwapData {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapData, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_SWAP_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapData, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_SWAP_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
|
||||
let excess = secp::read_secret_key(reader)?;
|
||||
let output_commit = Commitment::read(reader)?;
|
||||
let rangeproof = read_optional(reader)?;
|
||||
let input = Input::read(reader)?;
|
||||
let fee = reader.read_u64()?;
|
||||
let onion = Onion::read(reader)?;
|
||||
let status = SwapStatus::read(reader)?;
|
||||
Ok(SwapData {
|
||||
excess,
|
||||
output_commit,
|
||||
rangeproof,
|
||||
input,
|
||||
fee,
|
||||
onion,
|
||||
status,
|
||||
})
|
||||
}
|
||||
let excess = secp::read_secret_key(reader)?;
|
||||
let output_commit = Commitment::read(reader)?;
|
||||
let rangeproof = read_optional(reader)?;
|
||||
let input = Input::read(reader)?;
|
||||
let fee = reader.read_u64()?;
|
||||
let onion = Onion::read(reader)?;
|
||||
let status = SwapStatus::read(reader)?;
|
||||
Ok(SwapData {
|
||||
excess,
|
||||
output_commit,
|
||||
rangeproof,
|
||||
input,
|
||||
fee,
|
||||
onion,
|
||||
status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction created as part of a swap round.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SwapTx {
|
||||
pub tx: Transaction,
|
||||
pub chain_tip: (u64, Hash),
|
||||
// TODO: Include status
|
||||
pub tx: Transaction,
|
||||
pub chain_tip: (u64, Hash),
|
||||
// TODO: Include status
|
||||
}
|
||||
|
||||
impl Writeable for SwapTx {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_TX_VERSION)?;
|
||||
self.tx.write(writer)?;
|
||||
writer.write_u64(self.chain_tip.0)?;
|
||||
self.chain_tip.1.write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_TX_VERSION)?;
|
||||
self.tx.write(writer)?;
|
||||
writer.write_u64(self.chain_tip.0)?;
|
||||
self.chain_tip.1.write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SwapTx {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapTx, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_TX_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapTx, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_TX_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
|
||||
let tx = Transaction::read(reader)?;
|
||||
let height = reader.read_u64()?;
|
||||
let block_hash = Hash::read(reader)?;
|
||||
Ok(SwapTx {
|
||||
tx,
|
||||
chain_tip: (height, block_hash),
|
||||
})
|
||||
}
|
||||
let tx = Transaction::read(reader)?;
|
||||
let height = reader.read_u64()?;
|
||||
let block_hash = Hash::read(reader)?;
|
||||
Ok(SwapTx {
|
||||
tx,
|
||||
chain_tip: (height, block_hash),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage facility for swap data.
|
||||
pub struct SwapStore {
|
||||
db: Store,
|
||||
db: Store,
|
||||
}
|
||||
|
||||
/// Store error types
|
||||
#[derive(Clone, Error, Debug, PartialEq)]
|
||||
pub enum StoreError {
|
||||
#[error("Swap entry already exists for '{0:?}'")]
|
||||
AlreadyExists(Commitment),
|
||||
#[error("Error occurred while attempting to open db: {0}")]
|
||||
OpenError(store::lmdb::Error),
|
||||
#[error("Serialization error occurred: {0}")]
|
||||
SerializationError(ser::Error),
|
||||
#[error("Error occurred while attempting to read from db: {0}")]
|
||||
ReadError(store::lmdb::Error),
|
||||
#[error("Error occurred while attempting to write to db: {0}")]
|
||||
WriteError(store::lmdb::Error),
|
||||
#[error("Swap entry already exists for '{0:?}'")]
|
||||
AlreadyExists(Commitment),
|
||||
#[error("Entry does not exist for '{0:?}'")]
|
||||
NotFound(Commitment),
|
||||
#[error("Error occurred while attempting to open db: {0}")]
|
||||
OpenError(store::lmdb::Error),
|
||||
#[error("Serialization error occurred: {0}")]
|
||||
SerializationError(ser::Error),
|
||||
#[error("Error occurred while attempting to read from db: {0}")]
|
||||
ReadError(store::lmdb::Error),
|
||||
#[error("Error occurred while attempting to write to db: {0}")]
|
||||
WriteError(store::lmdb::Error),
|
||||
}
|
||||
|
||||
impl From<ser::Error> for StoreError {
|
||||
fn from(e: ser::Error) -> StoreError {
|
||||
StoreError::SerializationError(e)
|
||||
}
|
||||
fn from(e: ser::Error) -> StoreError {
|
||||
StoreError::SerializationError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl SwapStore {
|
||||
/// Create new chain store
|
||||
pub fn new(db_root: &str) -> Result<SwapStore, StoreError> {
|
||||
let db = Store::new(db_root, Some(DB_NAME), Some(STORE_SUBPATH), None)
|
||||
.map_err(StoreError::OpenError)?;
|
||||
Ok(SwapStore { db })
|
||||
}
|
||||
/// Create new chain store
|
||||
pub fn new(db_root: &str) -> Result<SwapStore, StoreError> {
|
||||
let db = Store::new(db_root, Some(DB_NAME), Some(STORE_SUBPATH), None)
|
||||
.map_err(StoreError::OpenError)?;
|
||||
Ok(SwapStore { db })
|
||||
}
|
||||
|
||||
/// Writes a single key-value pair to the database
|
||||
fn write<K: AsRef<[u8]>>(
|
||||
&self,
|
||||
prefix: u8,
|
||||
k: K,
|
||||
value: &Vec<u8>,
|
||||
overwrite: bool,
|
||||
) -> Result<bool, store::lmdb::Error> {
|
||||
let batch = self.db.batch()?;
|
||||
let key = store::to_key(prefix, k);
|
||||
if !overwrite && batch.exists(&key[..])? {
|
||||
Ok(false)
|
||||
} else {
|
||||
batch.put(&key[..], &value[..])?;
|
||||
batch.commit()?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
/// Writes a single key-value pair to the database
|
||||
fn write<K: AsRef<[u8]>>(
|
||||
&self,
|
||||
prefix: u8,
|
||||
k: K,
|
||||
value: &Vec<u8>,
|
||||
overwrite: bool,
|
||||
) -> Result<bool, store::lmdb::Error> {
|
||||
let batch = self.db.batch()?;
|
||||
let key = store::to_key(prefix, k);
|
||||
if !overwrite && batch.exists(&key[..])? {
|
||||
Ok(false)
|
||||
} else {
|
||||
batch.put(&key[..], &value[..])?;
|
||||
batch.commit()?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a single value by key
|
||||
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), || {
|
||||
format!("{}:{}", prefix, k.to_hex())
|
||||
})
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
/// Reads a single value by key
|
||||
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), || {
|
||||
format!("{}:{}", prefix, k.to_hex())
|
||||
})
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
|
||||
/// Saves a swap to the database
|
||||
pub fn save_swap(&self, s: &SwapData, overwrite: bool) -> Result<(), StoreError> {
|
||||
let data = ser::ser_vec(&s, ProtocolVersion::local())?;
|
||||
let saved = self
|
||||
.write(SWAP_PREFIX, &s.input.commit, &data, overwrite)
|
||||
.map_err(StoreError::WriteError)?;
|
||||
if !saved {
|
||||
Err(StoreError::AlreadyExists(s.input.commit.clone()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
/// Saves a swap to the database
|
||||
pub fn save_swap(&self, s: &SwapData, overwrite: bool) -> Result<(), StoreError> {
|
||||
let data = ser::ser_vec(&s, ProtocolVersion::local())?;
|
||||
let saved = self
|
||||
.write(SWAP_PREFIX, &s.input.commit, &data, overwrite)
|
||||
.map_err(StoreError::WriteError)?;
|
||||
if !saved {
|
||||
Err(StoreError::AlreadyExists(s.input.commit.clone()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over all swaps.
|
||||
pub fn swaps_iter(&self) -> Result<impl Iterator<Item=SwapData>, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, "");
|
||||
let protocol_version = self.db.protocol_version();
|
||||
self.db
|
||||
.iter(&key[..], move |_, mut v| {
|
||||
ser::deserialize(&mut v, protocol_version, DeserializationMode::default())
|
||||
.map_err(From::from)
|
||||
})
|
||||
.map_err(|e| StoreError::ReadError(e))
|
||||
}
|
||||
/// Iterator over all swaps.
|
||||
pub fn swaps_iter(&self) -> Result<impl Iterator<Item = SwapData>, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, "");
|
||||
let protocol_version = self.db.protocol_version();
|
||||
self.db
|
||||
.iter(&key[..], move |_, mut v| {
|
||||
ser::deserialize(&mut v, protocol_version, DeserializationMode::default())
|
||||
.map_err(From::from)
|
||||
})
|
||||
.map_err(|e| StoreError::ReadError(e))
|
||||
}
|
||||
|
||||
/// Checks if a matching swap exists in the database
|
||||
#[allow(dead_code)]
|
||||
pub fn swap_exists(&self, input_commit: &Commitment) -> Result<bool, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, input_commit);
|
||||
self.db
|
||||
.batch()
|
||||
.map_err(StoreError::ReadError)?
|
||||
.exists(&key[..])
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
/// Checks if a matching swap exists in the database
|
||||
#[allow(dead_code)]
|
||||
pub fn swap_exists(&self, input_commit: &Commitment) -> Result<bool, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, input_commit);
|
||||
self.db
|
||||
.batch()
|
||||
.map_err(StoreError::ReadError)?
|
||||
.exists(&key[..])
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
|
||||
/// Reads a swap from the database
|
||||
pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> {
|
||||
self.read(SWAP_PREFIX, input_commit)
|
||||
}
|
||||
/// Reads a swap from the database
|
||||
pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> {
|
||||
self.read(SWAP_PREFIX, input_commit)
|
||||
}
|
||||
|
||||
/// Saves a swap transaction to the database
|
||||
pub fn save_swap_tx(&self, s: &SwapTx) -> Result<(), StoreError> {
|
||||
let data = ser::ser_vec(&s, ProtocolVersion::local())?;
|
||||
self
|
||||
.write(TX_PREFIX, &s.tx.kernels().first().unwrap().excess, &data, true)
|
||||
.map_err(StoreError::WriteError)?;
|
||||
/// Saves a swap transaction to the database
|
||||
pub fn save_swap_tx(&self, s: &SwapTx) -> Result<(), StoreError> {
|
||||
let data = ser::ser_vec(&s, ProtocolVersion::local())?;
|
||||
self.write(
|
||||
TX_PREFIX,
|
||||
&s.tx.kernels().first().unwrap().excess,
|
||||
&data,
|
||||
true,
|
||||
)
|
||||
.map_err(StoreError::WriteError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads a swap tx from the database
|
||||
pub fn get_swap_tx(&self, kernel_excess: &Commitment) -> Result<SwapTx, StoreError> {
|
||||
self.read(TX_PREFIX, kernel_excess)
|
||||
}
|
||||
/// Reads a swap tx from the database
|
||||
pub fn get_swap_tx(&self, kernel_excess: &Commitment) -> Result<SwapTx, StoreError> {
|
||||
self.read(TX_PREFIX, kernel_excess)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::store::{StoreError, SwapData, SwapStatus, SwapStore};
|
||||
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;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
fn new_store(test_name: &str) -> SwapStore {
|
||||
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
||||
let db_root = format!("./target/tmp/.{}", test_name);
|
||||
let _ = std::fs::remove_dir_all(db_root.as_str());
|
||||
SwapStore::new(db_root.as_str()).unwrap()
|
||||
}
|
||||
use grin_core::core::{Input, OutputFeatures};
|
||||
use grin_core::global::{self, ChainTypes};
|
||||
use rand::RngCore;
|
||||
|
||||
fn rand_swap_with_status(status: SwapStatus) -> SwapData {
|
||||
SwapData {
|
||||
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,
|
||||
}
|
||||
}
|
||||
use grin_onion::crypto::secp;
|
||||
use grin_onion::test_util as onion_test_util;
|
||||
|
||||
fn rand_swap() -> SwapData {
|
||||
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)
|
||||
}
|
||||
use crate::store::{StoreError, SwapData, SwapStatus, SwapStore};
|
||||
|
||||
#[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);
|
||||
}
|
||||
fn new_store(test_name: &str) -> SwapStore {
|
||||
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
||||
let db_root = format!("./target/tmp/.{}", test_name);
|
||||
let _ = std::fs::remove_dir_all(db_root.as_str());
|
||||
SwapStore::new(db_root.as_str()).unwrap()
|
||||
}
|
||||
|
||||
swaps.sort_by(|a, b| {
|
||||
if a.input.commit < b.input.commit {
|
||||
Ordering::Less
|
||||
} else if a.input.commit == b.input.commit {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
fn rand_swap_with_status(status: SwapStatus) -> SwapData {
|
||||
SwapData {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
let mut i: usize = 0;
|
||||
for swap in store.swaps_iter()? {
|
||||
assert_eq!(swap, *swaps.get(i).unwrap());
|
||||
i += 1;
|
||||
}
|
||||
fn rand_swap() -> SwapData {
|
||||
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)
|
||||
}
|
||||
|
||||
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]
|
||||
fn save_swap() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let store = new_store("save_swap");
|
||||
swaps.sort_by(|a, b| {
|
||||
if a.input.commit < b.input.commit {
|
||||
Ordering::Less
|
||||
} else if a.input.commit == b.input.commit {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
let mut swap = rand_swap_with_status(SwapStatus::Unprocessed);
|
||||
assert!(!store.swap_exists(&swap.input.commit)?);
|
||||
let mut i: usize = 0;
|
||||
for swap in store.swaps_iter()? {
|
||||
assert_eq!(swap, *swaps.get(i).unwrap());
|
||||
i += 1;
|
||||
}
|
||||
|
||||
store.save_swap(&swap, false)?;
|
||||
assert_eq!(swap, store.get_swap(&swap.input.commit)?);
|
||||
assert!(store.swap_exists(&swap.input.commit)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
#[test]
|
||||
fn save_swap() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let store = new_store("save_swap");
|
||||
|
||||
store.save_swap(&swap, true)?;
|
||||
assert_eq!(swap, store.get_swap(&swap.input.commit)?);
|
||||
let mut swap = rand_swap_with_status(SwapStatus::Unprocessed);
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
251
src/tor.rs
251
src/tor.rs
|
@ -1,9 +1,32 @@
|
|||
use crate::config::ServerConfig;
|
||||
use std::sync::Arc;
|
||||
|
||||
use grin_wallet_impls::tor::config as tor_config;
|
||||
use grin_wallet_impls::tor::process::TorProcess;
|
||||
use std::collections::HashMap;
|
||||
use arti_client::config::TorClientConfigBuilder;
|
||||
use arti_client::{TorClient, TorClientConfig};
|
||||
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 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
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -14,93 +37,163 @@ pub enum TorError {
|
|||
ProcessError(grin_wallet_impls::tor::process::Error),
|
||||
}
|
||||
|
||||
pub fn init_tor_listener(
|
||||
data_dir: &str,
|
||||
server_config: &ServerConfig,
|
||||
) -> Result<TorProcess, TorError> {
|
||||
warn!("Initializing tor listener");
|
||||
pub struct TorService<R: Runtime> {
|
||||
tor_client: Option<TorClient<R>>,
|
||||
hidden_services: Vec<Arc<RunningOnionService>>,
|
||||
}
|
||||
|
||||
let tor_dir = format!("{}/tor/listener", &data_dir);
|
||||
trace!(
|
||||
"Dir: {}, Proxy: {}",
|
||||
&tor_dir,
|
||||
server_config.socks_proxy_addr.to_string()
|
||||
);
|
||||
impl<R: Runtime> TorService<R> {
|
||||
/// Builds a hyper::Client with an ArtiHttpConnector over the TorClient.
|
||||
/// The returned Client makes HTTP requests through the TorClient directly, eliminating the need for a socks proxy.
|
||||
pub fn new_hyper_client(
|
||||
&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
|
||||
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;
|
||||
}
|
||||
hyper::Client::builder().build::<_, hyper::Body>(tor_connector)
|
||||
}
|
||||
|
||||
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!(
|
||||
"Server listening at http://{}.onion",
|
||||
server_config.onion_address().to_ov3_str()
|
||||
);
|
||||
Ok(process)
|
||||
Ok(service)
|
||||
}
|
||||
|
||||
pub fn init_tor_sender(
|
||||
data_dir: &str,
|
||||
server_config: &ServerConfig,
|
||||
) -> Result<TorProcess, TorError> {
|
||||
warn!(
|
||||
"Starting TOR Process for send at {:?}",
|
||||
server_config.socks_proxy_addr
|
||||
// TODO: Add proper error handling
|
||||
fn add_key_to_store(
|
||||
tor_config: &TorClientConfig,
|
||||
state_dir: &String,
|
||||
secret_key: &SecretKey,
|
||||
hs_nickname: &HsNickname,
|
||||
) -> 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);
|
||||
tor_config::output_tor_sender_config(
|
||||
tor_dir.as_str(),
|
||||
&server_config.socks_proxy_addr.to_string(),
|
||||
HashMap::new(),
|
||||
HashMap::new(),
|
||||
)
|
||||
.map_err(|e| TorError::ConfigError(e))?;
|
||||
let mut sk_bytes = [0_u8; 64];
|
||||
sk_bytes[0..32].copy_from_slice(&expanded_sk.scalar.to_bytes());
|
||||
sk_bytes[32..64].copy_from_slice(&expanded_sk.hash_prefix);
|
||||
let expanded_kp = ExpandedKeypair::from_secret_key_bytes(sk_bytes).unwrap();
|
||||
|
||||
// Start TOR process
|
||||
let mut tor_process = TorProcess::new();
|
||||
tor_process
|
||||
.torrc_path("./torrc")
|
||||
.working_dir(tor_dir.as_str())
|
||||
.timeout(40)
|
||||
.completion_percent(100)
|
||||
.launch()
|
||||
.map_err(TorError::ProcessError)?;
|
||||
Ok(tor_process)
|
||||
key_manager
|
||||
.insert(
|
||||
HsIdKey::from(expanded_kp.public().clone()),
|
||||
&HsIdPublicKeySpecifier::new(hs_nickname.clone()),
|
||||
KeystoreSelector::Default,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
key_manager
|
||||
.insert(
|
||||
HsIdKeypair::from(expanded_kp),
|
||||
&HsIdKeypairSpecifier::new(hs_nickname.clone()),
|
||||
KeystoreSelector::Default,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
133
src/wallet.rs
133
src/wallet.rs
|
@ -1,18 +1,20 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
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::libtx::secp_ser;
|
||||
use grin_keychain::BlindingFactor;
|
||||
use grin_onion::crypto::secp;
|
||||
use grin_util::{ToHex, ZeroingString};
|
||||
use grin_wallet_api::{EncryptedRequest, EncryptedResponse, JsonId, Token};
|
||||
use secp256k1zkp::{PublicKey, Secp256k1, SecretKey};
|
||||
use grin_wallet_api::Token;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::net::SocketAddr;
|
||||
use thiserror::Error;
|
||||
|
||||
use grin_onion::crypto::secp;
|
||||
use secp256k1zkp::{PublicKey, Secp256k1, SecretKey};
|
||||
|
||||
use crate::http;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Wallet: Send + Sync {
|
||||
/// 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
|
||||
#[derive(Error, Debug)]
|
||||
pub enum WalletError {
|
||||
#[error("Error encrypting request: {0:?}")]
|
||||
EncryptRequestError(grin_wallet_libwallet::Error),
|
||||
#[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>),
|
||||
#[error("Error communication with wallet: {0:?}")]
|
||||
WalletCommError(http::HttpError),
|
||||
}
|
||||
|
||||
/// HTTP (JSONRPC) implementation of the 'Wallet' trait.
|
||||
|
@ -73,14 +65,16 @@ impl HttpWallet {
|
|||
"name": null,
|
||||
"password": wallet_pass.to_string()
|
||||
});
|
||||
let token: Token = HttpWallet::async_send_enc_request(
|
||||
&wallet_owner_url,
|
||||
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
|
||||
let token: Token = http::async_send_enc_request(
|
||||
&url,
|
||||
&wallet_owner_secret,
|
||||
"open_wallet",
|
||||
&open_wallet_params,
|
||||
&shared_key,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(WalletError::WalletCommError)?;
|
||||
info!("Connected to wallet");
|
||||
|
||||
Ok(HttpWallet {
|
||||
|
@ -98,17 +92,20 @@ impl HttpWallet {
|
|||
let secp = Secp256k1::new();
|
||||
let ephemeral_sk = secp::random_secret();
|
||||
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!({
|
||||
"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(
|
||||
&wallet_owner_url,
|
||||
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
|
||||
let response_pk: ECDHPubkey = http::async_send_json_request(
|
||||
&url,
|
||||
&wallet_owner_secret,
|
||||
"init_secure_api",
|
||||
&init_params,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(WalletError::WalletCommError)?;
|
||||
|
||||
let shared_key = {
|
||||
let mut shared_pubkey = response_pk.ecdh_pubkey.clone();
|
||||
|
@ -126,75 +123,16 @@ impl HttpWallet {
|
|||
method: &str,
|
||||
params: &serde_json::Value,
|
||||
) -> Result<D, WalletError> {
|
||||
HttpWallet::async_send_enc_request(
|
||||
&self.wallet_owner_url,
|
||||
let url = format!("http://{}{}", self.wallet_owner_url, ENDPOINT);
|
||||
http::async_send_enc_request(
|
||||
&url,
|
||||
&self.wallet_owner_secret,
|
||||
method,
|
||||
params,
|
||||
&self.shared_key,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
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)
|
||||
.map_err(WalletError::WalletCommError)
|
||||
}
|
||||
|
||||
pub fn get_token(&self) -> Token {
|
||||
|
@ -219,35 +157,40 @@ impl Wallet for HttpWallet {
|
|||
&self,
|
||||
amount: u64,
|
||||
) -> Result<(BlindingFactor, Output), WalletError> {
|
||||
let req_json = json!({
|
||||
let params = json!({
|
||||
"token": self.token,
|
||||
"features": "Plain",
|
||||
"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,
|
||||
"build_output",
|
||||
&req_json,
|
||||
¶ms,
|
||||
&self.shared_key,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(WalletError::WalletCommError)?;
|
||||
Ok((output.blind, output.output))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod mock {
|
||||
use super::{Wallet, WalletError};
|
||||
use std::borrow::BorrowMut;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use grin_core::core::{Output, OutputFeatures};
|
||||
use grin_keychain::BlindingFactor;
|
||||
|
||||
use grin_onion::crypto::secp;
|
||||
use secp256k1zkp::pedersen::Commitment;
|
||||
use secp256k1zkp::Secp256k1;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{Wallet, WalletError};
|
||||
|
||||
/// Mock implementation of the 'Wallet' trait for unit-tests.
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -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::net::TcpListener;
|
||||
use std::sync::Arc;
|
||||
|
||||
use grin_core::core::Transaction;
|
||||
use tor_rtcompat::PreferredRuntime;
|
||||
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,
|
||||
tor_process: TorProcess,
|
||||
tor_instance: Arc<grin_util::Mutex<TorService<R>>>,
|
||||
swap_server: Arc<tokio::sync::Mutex<dyn SwapServer>>,
|
||||
rpc_server: jsonrpc_http_server::Server,
|
||||
_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> {
|
||||
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,
|
||||
tor_process: TorProcess,
|
||||
tor_instance: Arc<grin_util::Mutex<TorService<R>>>,
|
||||
rpc_server: jsonrpc_http_server::Server,
|
||||
_wallet: Arc<grin_util::Mutex<IntegrationGrinWallet>>,
|
||||
}
|
||||
|
||||
async fn async_new_swap_server(
|
||||
async fn async_new_swap_server<R>(
|
||||
data_dir: &str,
|
||||
rt_handle: &tokio::runtime::Handle,
|
||||
tor_runtime: R,
|
||||
wallets: &mut GrinWalletManager,
|
||||
server_key: &SecretKey,
|
||||
node: &Arc<grin_util::Mutex<IntegrationGrinNode>>,
|
||||
next_server: Option<&IntegrationMixServer>,
|
||||
) -> IntegrationSwapServer {
|
||||
next_server: Option<&IntegrationMixServer<R>>,
|
||||
) -> IntegrationSwapServer<R>
|
||||
where
|
||||
R: tor_rtcompat::Runtime,
|
||||
{
|
||||
let wallet = wallets.async_new_wallet(&node.lock().api_address()).await;
|
||||
|
||||
let server_config = mwixnet::ServerConfig {
|
||||
|
@ -55,10 +63,6 @@ async fn async_new_swap_server(
|
|||
.unwrap()
|
||||
.local_addr()
|
||||
.unwrap(),
|
||||
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")
|
||||
.unwrap()
|
||||
.local_addr()
|
||||
.unwrap(),
|
||||
grin_node_url: node.lock().api_address(),
|
||||
grin_node_secret_path: None,
|
||||
wallet_owner_url: wallet.lock().owner_address(),
|
||||
|
@ -72,7 +76,10 @@ async fn async_new_swap_server(
|
|||
|
||||
// Open SwapStore
|
||||
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(
|
||||
rt_handle,
|
||||
|
@ -80,6 +87,7 @@ async fn async_new_swap_server(
|
|||
match next_server {
|
||||
Some(s) => Some(Arc::new(MixClientImpl::new(
|
||||
server_config.clone(),
|
||||
tor_instance.clone(),
|
||||
DalekPublicKey::from_secret(&s.server_key),
|
||||
))),
|
||||
None => None,
|
||||
|
@ -92,22 +100,26 @@ async fn async_new_swap_server(
|
|||
|
||||
IntegrationSwapServer {
|
||||
server_key: server_key.clone(),
|
||||
tor_process,
|
||||
tor_instance,
|
||||
swap_server,
|
||||
rpc_server,
|
||||
_wallet: wallet,
|
||||
}
|
||||
}
|
||||
|
||||
async fn async_new_mix_server(
|
||||
async fn async_new_mix_server<R>(
|
||||
data_dir: &str,
|
||||
rt_handle: &tokio::runtime::Handle,
|
||||
tor_runtime: R,
|
||||
wallets: &mut GrinWalletManager,
|
||||
server_key: &SecretKey,
|
||||
node: &Arc<grin_util::Mutex<IntegrationGrinNode>>,
|
||||
prev_server: DalekPublicKey,
|
||||
next_server: Option<&IntegrationMixServer>,
|
||||
) -> IntegrationMixServer {
|
||||
next_server: Option<&IntegrationMixServer<R>>,
|
||||
) -> IntegrationMixServer<R>
|
||||
where
|
||||
R: tor_rtcompat::Runtime,
|
||||
{
|
||||
let wallet = wallets.async_new_wallet(&node.lock().api_address()).await;
|
||||
let server_config = mwixnet::ServerConfig {
|
||||
key: server_key.clone(),
|
||||
|
@ -116,10 +128,6 @@ async fn async_new_mix_server(
|
|||
.unwrap()
|
||||
.local_addr()
|
||||
.unwrap(),
|
||||
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")
|
||||
.unwrap()
|
||||
.local_addr()
|
||||
.unwrap(),
|
||||
grin_node_url: node.lock().api_address(),
|
||||
grin_node_secret_path: None,
|
||||
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(
|
||||
rt_handle,
|
||||
|
@ -139,6 +150,7 @@ async fn async_new_mix_server(
|
|||
match next_server {
|
||||
Some(s) => Some(Arc::new(MixClientImpl::new(
|
||||
server_config.clone(),
|
||||
tor_instance.clone(),
|
||||
DalekPublicKey::from_secret(&s.server_key),
|
||||
))),
|
||||
None => None,
|
||||
|
@ -150,16 +162,16 @@ async fn async_new_mix_server(
|
|||
|
||||
IntegrationMixServer {
|
||||
server_key: server_key.clone(),
|
||||
tor_process,
|
||||
tor_instance,
|
||||
rpc_server,
|
||||
_wallet: wallet,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Servers {
|
||||
pub swapper: IntegrationSwapServer,
|
||||
pub swapper: IntegrationSwapServer<PreferredRuntime>,
|
||||
|
||||
pub mixers: Vec<IntegrationMixServer>,
|
||||
pub mixers: Vec<IntegrationMixServer<PreferredRuntime>>,
|
||||
}
|
||||
|
||||
impl Servers {
|
||||
|
@ -176,12 +188,16 @@ impl Servers {
|
|||
.take(num_mixers + 1)
|
||||
.collect();
|
||||
|
||||
// Setup mock tor network
|
||||
let tor_runtime = PreferredRuntime::current().unwrap();
|
||||
|
||||
// Build mixers in reverse order
|
||||
let mut mixers = Vec::new();
|
||||
for i in (0..num_mixers).rev() {
|
||||
let mix_server = async_new_mix_server(
|
||||
format!("{}/mixers/{}", test_dir, i).as_str(),
|
||||
rt_handle,
|
||||
tor_runtime.clone(),
|
||||
wallets,
|
||||
&server_keys[i + 1],
|
||||
&node,
|
||||
|
@ -206,6 +222,7 @@ impl Servers {
|
|||
let swapper = async_new_swap_server(
|
||||
format!("{}/swapper", test_dir).as_str(),
|
||||
rt_handle,
|
||||
tor_runtime.clone(),
|
||||
wallets,
|
||||
&server_keys[0],
|
||||
&node,
|
||||
|
@ -234,11 +251,11 @@ impl Servers {
|
|||
|
||||
pub fn stop_all(&mut self) {
|
||||
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| {
|
||||
mixer.rpc_server.close_handle().close();
|
||||
mixer.tor_process.kill().unwrap();
|
||||
mixer.tor_instance.lock().stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use crate::common::types::BlockFees;
|
||||
use grin_api::client;
|
||||
use grin_api::json_rpc::Response;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use grin_core::core::{FeeFields, Output, OutputFeatures, Transaction, TxKernel};
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_core::libtx::tx_fee;
|
||||
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_wallet_api::Owner;
|
||||
use grin_wallet_config::WalletConfig;
|
||||
|
@ -15,417 +13,401 @@ use grin_wallet_controller::controller;
|
|||
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
|
||||
use grin_wallet_libwallet::{InitTxArgs, Slate, VersionedSlate, WalletInfo, WalletInst};
|
||||
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 secp256k1zkp::pedersen::Commitment;
|
||||
use secp256k1zkp::{Secp256k1, SecretKey};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use x25519_dalek::PublicKey as xPublicKey;
|
||||
|
||||
use crate::common::types::BlockFees;
|
||||
|
||||
/// Response to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbData {
|
||||
/// Output
|
||||
pub output: Output,
|
||||
/// Kernel
|
||||
pub kernel: TxKernel,
|
||||
/// Key Id
|
||||
pub key_id: Option<Identifier>,
|
||||
/// Output
|
||||
pub output: Output,
|
||||
/// Kernel
|
||||
pub kernel: TxKernel,
|
||||
/// Key Id
|
||||
pub key_id: Option<Identifier>,
|
||||
}
|
||||
|
||||
pub struct IntegrationGrinWallet {
|
||||
wallet: Arc<
|
||||
Mutex<
|
||||
Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>,
|
||||
HTTPNodeClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
api_listen_port: u16,
|
||||
owner_api: Arc<
|
||||
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
|
||||
>,
|
||||
http_client: Arc<HttpWallet>,
|
||||
wallet: Arc<
|
||||
Mutex<
|
||||
Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>,
|
||||
HTTPNodeClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
api_listen_port: u16,
|
||||
owner_api: Arc<
|
||||
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
|
||||
>,
|
||||
http_client: Arc<HttpWallet>,
|
||||
}
|
||||
|
||||
impl IntegrationGrinWallet {
|
||||
pub async fn async_new_wallet(
|
||||
wallet_dir: String,
|
||||
api_listen_port: u16,
|
||||
node_api: String,
|
||||
) -> IntegrationGrinWallet {
|
||||
let node_client = HTTPNodeClient::new(&node_api, None).unwrap();
|
||||
let mut wallet = Box::new(
|
||||
DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(),
|
||||
)
|
||||
as Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
|
||||
HTTPNodeClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>;
|
||||
pub async fn async_new_wallet(
|
||||
wallet_dir: String,
|
||||
api_listen_port: u16,
|
||||
node_api: String,
|
||||
) -> IntegrationGrinWallet {
|
||||
let node_client = HTTPNodeClient::new(&node_api, None).unwrap();
|
||||
let mut wallet = Box::new(
|
||||
DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(),
|
||||
)
|
||||
as Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
|
||||
HTTPNodeClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>;
|
||||
|
||||
// Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc...
|
||||
let lc = wallet.lc_provider().unwrap();
|
||||
// Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc...
|
||||
let lc = wallet.lc_provider().unwrap();
|
||||
|
||||
let mut wallet_config = WalletConfig::default();
|
||||
wallet_config.check_node_api_http_addr = node_api.clone();
|
||||
wallet_config.owner_api_listen_port = Some(api_listen_port);
|
||||
wallet_config.api_secret_path = None;
|
||||
wallet_config.data_file_dir = wallet_dir.clone();
|
||||
let mut wallet_config = WalletConfig::default();
|
||||
wallet_config.check_node_api_http_addr = node_api.clone();
|
||||
wallet_config.owner_api_listen_port = Some(api_listen_port);
|
||||
wallet_config.api_secret_path = None;
|
||||
wallet_config.data_file_dir = wallet_dir.clone();
|
||||
|
||||
// The top level wallet directory should be set manually (in the reference implementation,
|
||||
// this is provided in the WalletConfig)
|
||||
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
|
||||
// The top level wallet directory should be set manually (in the reference implementation,
|
||||
// this is provided in the WalletConfig)
|
||||
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
|
||||
|
||||
lc.create_config(
|
||||
&ChainTypes::AutomatedTesting,
|
||||
"grin-wallet.toml",
|
||||
Some(wallet_config.clone()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
lc.create_config(
|
||||
&ChainTypes::AutomatedTesting,
|
||||
"grin-wallet.toml",
|
||||
Some(wallet_config.clone()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
lc.create_wallet(None, None, 12, ZeroingString::from("pass"), false)
|
||||
.unwrap();
|
||||
lc.create_wallet(None, None, 12, ZeroingString::from("pass"), false)
|
||||
.unwrap();
|
||||
|
||||
// Start owner API
|
||||
let km = Arc::new(Mutex::new(None));
|
||||
let wallet = Arc::new(Mutex::new(wallet));
|
||||
let owner_api = Arc::new(Owner::new(wallet.clone(), None));
|
||||
// Start owner API
|
||||
let km = Arc::new(Mutex::new(None));
|
||||
let wallet = Arc::new(Mutex::new(wallet));
|
||||
let owner_api = Arc::new(Owner::new(wallet.clone(), None));
|
||||
|
||||
let address_str = format!("127.0.0.1:{}", api_listen_port);
|
||||
let owner_addr: SocketAddr = address_str.parse().unwrap();
|
||||
let thr_wallet = wallet.clone();
|
||||
let _thread_handle = thread::spawn(move || {
|
||||
controller::owner_listener(
|
||||
thr_wallet,
|
||||
km,
|
||||
address_str.as_str(),
|
||||
None,
|
||||
None,
|
||||
Some(true),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
let address_str = format!("127.0.0.1:{}", api_listen_port);
|
||||
let owner_addr: SocketAddr = address_str.parse().unwrap();
|
||||
let thr_wallet = wallet.clone();
|
||||
let _thread_handle = thread::spawn(move || {
|
||||
controller::owner_listener(
|
||||
thr_wallet,
|
||||
km,
|
||||
address_str.as_str(),
|
||||
None,
|
||||
None,
|
||||
Some(true),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let http_client = Arc::new(
|
||||
HttpWallet::async_open_wallet(&owner_addr, &None, &ZeroingString::from("pass"))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let http_client = Arc::new(
|
||||
HttpWallet::async_open_wallet(&owner_addr, &None, &ZeroingString::from("pass"))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
IntegrationGrinWallet {
|
||||
wallet,
|
||||
api_listen_port,
|
||||
owner_api,
|
||||
http_client,
|
||||
}
|
||||
}
|
||||
IntegrationGrinWallet {
|
||||
wallet,
|
||||
api_listen_port,
|
||||
owner_api,
|
||||
http_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn async_retrieve_summary_info(&self) -> Result<WalletInfo, mwixnet::WalletError> {
|
||||
let params = json!({
|
||||
pub async fn async_retrieve_summary_info(&self) -> Result<WalletInfo, mwixnet::WalletError> {
|
||||
let params = json!({
|
||||
"token": self.http_client.clone().get_token(),
|
||||
"refresh_from_node": true,
|
||||
"minimum_confirmations": 1
|
||||
});
|
||||
let (_, wallet_info): (bool, WalletInfo) = self
|
||||
.http_client
|
||||
.clone()
|
||||
.async_perform_request("retrieve_summary_info", ¶ms)
|
||||
.await?;
|
||||
Ok(wallet_info)
|
||||
}
|
||||
let (_, wallet_info): (bool, WalletInfo) = self
|
||||
.http_client
|
||||
.clone()
|
||||
.async_perform_request("retrieve_summary_info", ¶ms)
|
||||
.await?;
|
||||
Ok(wallet_info)
|
||||
}
|
||||
|
||||
pub async fn async_send(
|
||||
&self,
|
||||
receiving_wallet: &IntegrationGrinWallet,
|
||||
amount: u64,
|
||||
) -> Result<Transaction, mwixnet::WalletError> {
|
||||
let slate = self.async_init_send_tx(amount).await.unwrap();
|
||||
let slate = receiving_wallet.async_receive_tx(&slate).await.unwrap();
|
||||
let slate = self.async_finalize_tx(&slate).await.unwrap();
|
||||
let tx = Slate::from(slate).tx_or_err().unwrap().clone();
|
||||
Ok(tx)
|
||||
}
|
||||
pub async fn async_send(
|
||||
&self,
|
||||
receiving_wallet: &IntegrationGrinWallet,
|
||||
amount: u64,
|
||||
) -> Result<Transaction, mwixnet::WalletError> {
|
||||
let slate = self.async_init_send_tx(amount).await.unwrap();
|
||||
let slate = receiving_wallet.async_receive_tx(&slate).await.unwrap();
|
||||
let slate = self.async_finalize_tx(&slate).await.unwrap();
|
||||
let tx = Slate::from(slate).tx_or_err().unwrap().clone();
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
async fn async_init_send_tx(
|
||||
&self,
|
||||
amount: u64,
|
||||
) -> Result<VersionedSlate, mwixnet::WalletError> {
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
amount,
|
||||
minimum_confirmations: 0,
|
||||
max_outputs: 10,
|
||||
num_change_outputs: 1,
|
||||
selection_strategy_is_use_all: false,
|
||||
..Default::default()
|
||||
};
|
||||
let params = json!({
|
||||
async fn async_init_send_tx(
|
||||
&self,
|
||||
amount: u64,
|
||||
) -> Result<VersionedSlate, mwixnet::WalletError> {
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
amount,
|
||||
minimum_confirmations: 0,
|
||||
max_outputs: 10,
|
||||
num_change_outputs: 1,
|
||||
selection_strategy_is_use_all: false,
|
||||
..Default::default()
|
||||
};
|
||||
let params = json!({
|
||||
"token": self.http_client.clone().get_token(),
|
||||
"args": args
|
||||
});
|
||||
|
||||
let slate: VersionedSlate = self
|
||||
.http_client
|
||||
.clone()
|
||||
.async_perform_request("init_send_tx", ¶ms)
|
||||
.await?;
|
||||
let slate: VersionedSlate = self
|
||||
.http_client
|
||||
.clone()
|
||||
.async_perform_request("init_send_tx", ¶ms)
|
||||
.await?;
|
||||
|
||||
let params = json!({
|
||||
let params = json!({
|
||||
"token": self.http_client.clone().get_token(),
|
||||
"slate": &slate
|
||||
});
|
||||
self.http_client
|
||||
.clone()
|
||||
.async_perform_request("tx_lock_outputs", ¶ms)
|
||||
.await?;
|
||||
self.http_client
|
||||
.clone()
|
||||
.async_perform_request("tx_lock_outputs", ¶ms)
|
||||
.await?;
|
||||
|
||||
Ok(slate)
|
||||
}
|
||||
Ok(slate)
|
||||
}
|
||||
|
||||
pub async fn async_receive_tx(
|
||||
&self,
|
||||
slate: &VersionedSlate,
|
||||
) -> Result<VersionedSlate, grin_servers::common::types::Error> {
|
||||
let req_body = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "receive_tx",
|
||||
"id": 1,
|
||||
"params": [slate, null, null]
|
||||
});
|
||||
pub async fn async_receive_tx(
|
||||
&self,
|
||||
slate: &VersionedSlate,
|
||||
) -> Result<VersionedSlate, grin_servers::common::types::Error> {
|
||||
let params = json!([slate, null, null]);
|
||||
let response =
|
||||
http::async_send_json_request(&self.foreign_api(), &None, "receive_tx", ¶ms)
|
||||
.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 res: Response = client::post_async(self.foreign_api().as_str(), &req_body, None)
|
||||
.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)
|
||||
})?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
let parsed: VersionedSlate = 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)
|
||||
}
|
||||
|
||||
async fn async_finalize_tx(
|
||||
&self,
|
||||
slate: &VersionedSlate,
|
||||
) -> Result<VersionedSlate, mwixnet::WalletError> {
|
||||
let params = json!({
|
||||
async fn async_finalize_tx(
|
||||
&self,
|
||||
slate: &VersionedSlate,
|
||||
) -> Result<VersionedSlate, mwixnet::WalletError> {
|
||||
let params = json!({
|
||||
"token": self.http_client.clone().get_token(),
|
||||
"slate": slate
|
||||
});
|
||||
|
||||
self.http_client
|
||||
.clone()
|
||||
.async_perform_request("finalize_tx", ¶ms)
|
||||
.await
|
||||
}
|
||||
self.http_client
|
||||
.clone()
|
||||
.async_perform_request("finalize_tx", ¶ms)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn async_post_tx(
|
||||
&self,
|
||||
finalized_slate: &VersionedSlate,
|
||||
fluff: bool,
|
||||
) -> Result<VersionedSlate, mwixnet::WalletError> {
|
||||
let params = json!({
|
||||
#[allow(dead_code)]
|
||||
async fn async_post_tx(
|
||||
&self,
|
||||
finalized_slate: &VersionedSlate,
|
||||
fluff: bool,
|
||||
) -> Result<VersionedSlate, mwixnet::WalletError> {
|
||||
let params = json!({
|
||||
"token": self.http_client.clone().get_token(),
|
||||
"slate": finalized_slate,
|
||||
"fluff": fluff
|
||||
});
|
||||
|
||||
self.http_client
|
||||
.clone()
|
||||
.async_perform_request("post_tx", ¶ms)
|
||||
.await
|
||||
}
|
||||
self.http_client
|
||||
.clone()
|
||||
.async_perform_request("post_tx", ¶ms)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Call the wallet API to create a coinbase output for the given block_fees.
|
||||
/// Will retry based on default "retry forever with backoff" behavior.
|
||||
pub async fn async_create_coinbase(
|
||||
&self,
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<CbData, grin_servers::common::types::Error> {
|
||||
let req_body = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "build_coinbase",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"block_fees": 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.
|
||||
pub async fn async_create_coinbase(
|
||||
&self,
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<CbData, grin_servers::common::types::Error> {
|
||||
let params = json!({
|
||||
"block_fees": block_fees
|
||||
});
|
||||
let response =
|
||||
http::async_send_json_request(&self.foreign_api(), &None, "build_coinbase", ¶ms)
|
||||
.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)
|
||||
.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(response)
|
||||
}
|
||||
|
||||
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(
|
||||
&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)?;
|
||||
let mut output = None;
|
||||
for o in &outputs {
|
||||
if o.commit == *commitment {
|
||||
output = Some(o.output.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = None;
|
||||
for o in &outputs {
|
||||
if o.commit == *commitment {
|
||||
output = Some(o.output.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if output.is_none() {
|
||||
return Err(grin_wallet_libwallet::Error::GenericError(String::from(
|
||||
"output not found",
|
||||
)));
|
||||
}
|
||||
|
||||
if output.is_none() {
|
||||
return Err(grin_wallet_libwallet::Error::GenericError(String::from(
|
||||
"output not found",
|
||||
)));
|
||||
}
|
||||
let amount = output.clone().unwrap().value;
|
||||
let input_blind = keychain.derive_key(
|
||||
amount,
|
||||
&output.clone().unwrap().key_id,
|
||||
SwitchCommitmentType::Regular,
|
||||
)?;
|
||||
|
||||
let amount = output.clone().unwrap().value;
|
||||
let input_blind = keychain.derive_key(
|
||||
amount,
|
||||
&output.clone().unwrap().key_id,
|
||||
SwitchCommitmentType::Regular,
|
||||
)?;
|
||||
let fee = tx_fee(1, 1, 1);
|
||||
let new_amount = amount - (fee * server_pubkeys.len() as u64);
|
||||
let new_output = self.owner_api.build_output(
|
||||
self.keychain_mask().as_ref(),
|
||||
OutputFeatures::Plain,
|
||||
new_amount,
|
||||
)?;
|
||||
|
||||
let fee = tx_fee(1, 1, 1);
|
||||
let new_amount = amount - (fee * server_pubkeys.len() as u64);
|
||||
let new_output = self.owner_api.build_output(
|
||||
self.keychain_mask().as_ref(),
|
||||
OutputFeatures::Plain,
|
||||
new_amount,
|
||||
)?;
|
||||
let secp = Secp256k1::new();
|
||||
let mut blind_sum = new_output
|
||||
.blind
|
||||
.split(&BlindingFactor::from_secret_key(input_blind.clone()), &secp)?;
|
||||
|
||||
let secp = Secp256k1::new();
|
||||
let mut blind_sum = new_output
|
||||
.blind
|
||||
.split(&BlindingFactor::from_secret_key(input_blind.clone()), &secp)?;
|
||||
let hops = server_pubkeys
|
||||
.iter()
|
||||
.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 hops = server_pubkeys
|
||||
.iter()
|
||||
.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();
|
||||
let comsig = ComSignature::sign(amount, &input_blind, &onion.serialize().unwrap()).unwrap();
|
||||
|
||||
let onion = grin_onion::create_onion(&commitment, &hops).unwrap();
|
||||
let comsig = ComSignature::sign(amount, &input_blind, &onion.serialize().unwrap()).unwrap();
|
||||
Ok((onion, comsig))
|
||||
}
|
||||
|
||||
Ok((onion, comsig))
|
||||
}
|
||||
pub fn owner_api(
|
||||
&self,
|
||||
) -> Arc<
|
||||
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
|
||||
> {
|
||||
self.owner_api.clone()
|
||||
}
|
||||
|
||||
pub fn owner_api(
|
||||
&self,
|
||||
) -> Arc<
|
||||
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
|
||||
> {
|
||||
self.owner_api.clone()
|
||||
}
|
||||
pub fn foreign_api(&self) -> String {
|
||||
format!("http://127.0.0.1:{}/v2/foreign", self.api_listen_port)
|
||||
}
|
||||
|
||||
pub fn foreign_api(&self) -> String {
|
||||
format!("http://127.0.0.1:{}/v2/foreign", self.api_listen_port)
|
||||
}
|
||||
pub fn owner_address(&self) -> SocketAddr {
|
||||
SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
self.api_listen_port,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn owner_address(&self) -> SocketAddr {
|
||||
SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
self.api_listen_port,
|
||||
)
|
||||
}
|
||||
pub fn keychain_mask(&self) -> Option<SecretKey> {
|
||||
self.http_client.as_ref().get_token().keychain_mask.clone()
|
||||
}
|
||||
|
||||
pub fn keychain_mask(&self) -> Option<SecretKey> {
|
||||
self.http_client.as_ref().get_token().keychain_mask.clone()
|
||||
}
|
||||
|
||||
pub fn get_client(&self) -> Arc<HttpWallet> {
|
||||
self.http_client.clone()
|
||||
}
|
||||
pub fn get_client(&self) -> Arc<HttpWallet> {
|
||||
self.http_client.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct GrinWalletManager {
|
||||
// base directory for the server instance
|
||||
working_dir: String,
|
||||
// base directory for the server instance
|
||||
working_dir: String,
|
||||
|
||||
wallets: Vec<Arc<Mutex<IntegrationGrinWallet>>>,
|
||||
wallets: Vec<Arc<Mutex<IntegrationGrinWallet>>>,
|
||||
}
|
||||
|
||||
impl GrinWalletManager {
|
||||
pub fn new(test_dir: &str) -> GrinWalletManager {
|
||||
GrinWalletManager {
|
||||
working_dir: String::from(test_dir),
|
||||
wallets: vec![],
|
||||
}
|
||||
}
|
||||
pub fn new(test_dir: &str) -> GrinWalletManager {
|
||||
GrinWalletManager {
|
||||
working_dir: String::from(test_dir),
|
||||
wallets: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn async_new_wallet(
|
||||
&mut self,
|
||||
node_api_addr: &SocketAddr,
|
||||
) -> Arc<Mutex<IntegrationGrinWallet>> {
|
||||
let wallet_dir = format!("{}/wallets/{}", self.working_dir, self.wallets.len());
|
||||
let wallet = Arc::new(Mutex::new(
|
||||
IntegrationGrinWallet::async_new_wallet(
|
||||
wallet_dir,
|
||||
21000 + self.wallets.len() as u16,
|
||||
format!("http://{}", node_api_addr),
|
||||
)
|
||||
.await,
|
||||
));
|
||||
self.wallets.push(wallet.clone());
|
||||
wallet
|
||||
}
|
||||
pub async fn async_new_wallet(
|
||||
&mut self,
|
||||
node_api_addr: &SocketAddr,
|
||||
) -> Arc<Mutex<IntegrationGrinWallet>> {
|
||||
let wallet_dir = format!("{}/wallets/{}", self.working_dir, self.wallets.len());
|
||||
let wallet = Arc::new(Mutex::new(
|
||||
IntegrationGrinWallet::async_new_wallet(
|
||||
wallet_dir,
|
||||
21000 + self.wallets.len() as u16,
|
||||
format!("http://{}", node_api_addr),
|
||||
)
|
||||
.await,
|
||||
));
|
||||
self.wallets.push(wallet.clone());
|
||||
wallet
|
||||
}
|
||||
}
|
||||
|
|
21
tests/e2e.rs
21
tests/e2e.rs
|
@ -1,18 +1,20 @@
|
|||
use crate::common::miner::Miner;
|
||||
use crate::common::node::GrinNodeManager;
|
||||
use crate::common::server::Servers;
|
||||
use crate::common::wallet::GrinWalletManager;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use function_name::named;
|
||||
use grin_core::global;
|
||||
use grin_util::logger::LoggingConfig;
|
||||
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;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
/// Just removes all results from previous runs
|
||||
fn clean_all_output(test_dir: &str) {
|
||||
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]
|
||||
fn integration_test() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (mut nodes, mut wallets, test_dir) = setup_test(function_name!());
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
|
||||
|
|
Loading…
Reference in a new issue