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