diff --git a/neveko-core/src/contact.rs b/neveko-core/src/contact.rs index 997fcb3..7db9cf7 100644 --- a/neveko-core/src/contact.rs +++ b/neveko-core/src/contact.rs @@ -113,7 +113,7 @@ pub async fn share() -> Contact { let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; monero::close_wallet(&wallet_name, &wallet_password).await; let gpg_key = gpg::export_key().unwrap_or(Vec::new()); - let i2p_address = i2p::get_destination(); + let i2p_address = i2p::get_destination(None); let xmr_address = m_address.result.address; Contact { cid: utils::empty_string(), diff --git a/neveko-core/src/gpg.rs b/neveko-core/src/gpg.rs index cc329f6..2c78ff5 100644 --- a/neveko-core/src/gpg.rs +++ b/neveko-core/src/gpg.rs @@ -24,7 +24,7 @@ pub fn find_key() -> Result> { let mode = KeyListMode::LOCAL; let mut ctx = Context::from_protocol(proto)?; ctx.set_key_list_mode(mode)?; - let name = i2p::get_destination(); + let name = i2p::get_destination(None); let mut keys = ctx.find_keys([&name])?; let mut k: String = utils::empty_string(); for key in keys.by_ref().filter_map(|x| x.ok()) { @@ -56,7 +56,7 @@ pub fn export_key() -> Result, Box> { info!("exporting public key"); let mut ctx = Context::from_protocol(Protocol::OpenPgp)?; ctx.set_armor(true); - let name = i2p::get_destination(); + let name = i2p::get_destination(None); let keys = { let mut key_iter = ctx.find_keys([&name])?; let keys: Vec<_> = key_iter.by_ref().collect::>()?; @@ -132,7 +132,7 @@ pub fn decrypt(mid: &String, body: &Vec) -> Result, Box> } pub fn write_gen_batch() -> Result<(), Box> { - let name = i2p::get_destination(); + let name = i2p::get_destination(None); let data = format!( "%no-protection Key-Type: RSA @@ -168,7 +168,7 @@ pub fn sign_key(key: &str) -> Result<(), Box> { let key_to_sign = k2s_ctx .get_key(k) .map_err(|e| format!("no key matched given key-id: {:?}", e))?; - let name = Some(i2p::get_destination()); + let name = Some(i2p::get_destination(None)); if let Some(app_key) = name { let key = k2s_ctx .get_secret_key(app_key) diff --git a/neveko-core/src/i2p.rs b/neveko-core/src/i2p.rs index 0c5f84a..48b815f 100644 --- a/neveko-core/src/i2p.rs +++ b/neveko-core/src/i2p.rs @@ -1,5 +1,6 @@ use crate::{ args, + monero, utils, }; use clap::Parser; @@ -72,7 +73,9 @@ async fn find_tunnels() { debug!("i2p tunnels: {}", contents); let has_app_tunnel = contents.contains(&format!("{}", app_port)); let proxy_port = get_i2p_proxy_port(); + let tx_proxy_port = monero::get_daemon_port(); let has_http_tunnel = contents.contains(&proxy_port); + let has_tx_proxy_tunnel = contents.contains(&format!("{}", &tx_proxy_port)); if !has_app_tunnel || !has_http_tunnel { tokio::time::sleep(Duration::new(120, 0)).await; } @@ -84,6 +87,9 @@ async fn find_tunnels() { debug!("creating http tunnel"); create_http_proxy(); } + if !has_tx_proxy_tunnel && !utils::is_using_remote_node() { + create_tx_proxy_tunnel(); + } } /// Called on application startup for i2p tunnel creation, @@ -129,6 +135,22 @@ fn create_tunnel() { debug!("{:?}", output.stdout); } +/// Create an i2p tunnel for the monero tx proxy +fn create_tx_proxy_tunnel() { + info!("creating monerod tx proxy tunnel"); + let args = args::Args::parse(); + let path = args.i2p_zero_dir; + let output = Command::new(format!("{}/router/bin/tunnel-control.sh", path)) + .args([ + "server.create", + "127.0.0.1", + &format!("{}", monero::get_daemon_port()), + ]) + .spawn() + .expect("i2p-zero failed to create a tunnel"); + debug!("{:?}", output.stdout); +} + /// Extract i2p port from command line arg fn get_i2p_proxy_port() -> String { let proxy_host = utils::get_i2p_http_proxy(); @@ -151,10 +173,12 @@ fn create_http_proxy() { debug!("{:?}", output.stdout); } -/// This is the `dest` value of the app i2p tunnel +/// This is the `dest` value of the app i2p tunnels /// /// in `tunnels-config.json`. -pub fn get_destination() -> String { +/// +/// `port` - the port of the tunnel (e.g. `utils::get_app_port()`) +pub fn get_destination(port: Option) -> String { let file_path = format!( "/home/{}/.i2p-zero/config/tunnels.json", env::var("USER").unwrap_or(String::from("user")) @@ -170,7 +194,7 @@ pub fn get_destination() -> String { let mut destination: String = utils::empty_string(); let tunnels: Vec = j.tunnels; for tunnel in tunnels { - if tunnel.port == format!("{}", utils::get_app_port()) { + if tunnel.port == format!("{}", port.unwrap_or(utils::get_app_port())) { destination = tunnel.dest.unwrap_or(utils::empty_string()); } } diff --git a/neveko-core/src/message.rs b/neveko-core/src/message.rs index 85814f1..1cea423 100644 --- a/neveko-core/src/message.rs +++ b/neveko-core/src/message.rs @@ -22,6 +22,8 @@ pub const EXCHANGE_MSIG: &str = "exchange"; pub const EXPORT_MSIG: &str = "export"; pub const MAKE_MSIG: &str = "make"; pub const PREPARE_MSIG: &str = "prepare"; +pub const SIGN_MSIG: &str = "sign"; +pub const VALID_MSIG_MSG_LENGTH: usize = 4; #[derive(PartialEq)] pub enum MessageType { @@ -35,6 +37,16 @@ struct MultisigMessageData { orid: String, } +impl Default for MultisigMessageData { + fn default() -> Self { + MultisigMessageData { + info: utils::empty_string(), + sub_type: utils::empty_string(), + orid: utils::empty_string(), + } + } +} + /// Create a new message pub async fn create(m: Json, jwp: String, m_type: MessageType) -> Message { let rnd = utils::generate_rnd(); @@ -50,7 +62,7 @@ pub async fn create(m: Json, jwp: String, m_type: MessageType) -> Messa let new_message = Message { mid: String::from(&f_mid), uid: String::from(&m.uid), - from: i2p::get_destination(), + from: i2p::get_destination(None), body: e_body, created, to: String::from(&m.to), @@ -117,9 +129,14 @@ fn parse_multisig_message(mid: String) -> MultisigMessageData { let decoded = String::from_utf8(bytes).unwrap_or(utils::empty_string()); let values = decoded.split(":"); let mut v: Vec = values.map(|s| String::from(s)).collect(); + if v.len() != VALID_MSIG_MSG_LENGTH { + return Default::default(); + } let sub_type: String = v.remove(0); let orid: String = v.remove(0); - let info: String = v.remove(0); + let customer_info: String = v.remove(0); + let mediator_info: String = v.remove(0); + let info = format!("{}:{}", customer_info, mediator_info); bytes = Vec::new(); debug!("zero decryption bytes: {:?}", bytes); MultisigMessageData { @@ -136,7 +153,10 @@ fn parse_multisig_message(mid: String) -> MultisigMessageData { /// decrypted for convenience sake. The client must determine which /// /// .b32.i2p address belongs to the vendor / mediator. -/// +/// +/// The result should be a string that needs to be decomposed into a +/// +/// vector. /// ### Example /// /// ```rust @@ -144,7 +164,9 @@ fn parse_multisig_message(mid: String) -> MultisigMessageData { /// use neveko_core::db; /// let s = db::Interface::open(); /// let key = "prepare-o123-test.b32.i2p"; -/// db::Interface::read(&s.env, &s.handle, &key); +/// let info_str = db::Interface::read(&s.env, &s.handle, &key); +/// let info_split = info_str.split(":"); +/// let mut v_info: Vec = info_split.map(|s| String::from(s)).collect(); /// ``` pub async fn rx_multisig(m: Json) { // make sure the message isn't something strange @@ -157,7 +179,7 @@ pub async fn rx_multisig(m: Json) { if !is_in_contact_list { return; } - let f_mid: String = format!("m{}", utils::generate_rnd()); + let f_mid: String = format!("msig{}", utils::generate_rnd()); let new_message = Message { mid: String::from(&f_mid), uid: String::from("rx"), @@ -176,6 +198,7 @@ pub async fn rx_multisig(m: Json) { &data.sub_type, &data.orid ); // lookup msig message data by {type}-{order id}-{contact .b32.i2p address} + // store info as {customer_info}:{mediator_info} let msig_key = format!("{}-{}-{}", &data.sub_type, &data.orid, &m.from); db::Interface::async_write(&s.env, &s.handle, &msig_key, &data.info).await; } @@ -428,7 +451,7 @@ fn validate_message(j: &Json) -> bool { info!("validating message: {}", &j.mid); j.mid.len() < utils::string_limit() && j.body.len() < utils::message_limit() - && j.to == i2p::get_destination() + && j.to == i2p::get_destination(None) && j.uid.len() < utils::string_limit() } diff --git a/neveko-core/src/monero.rs b/neveko-core/src/monero.rs index 5d3bde4..d4c48a3 100644 --- a/neveko-core/src/monero.rs +++ b/neveko-core/src/monero.rs @@ -2,7 +2,7 @@ use crate::{ args, proof, reqres, - utils, + utils, i2p, }; use clap::Parser; use diqwest::WithDigestAuth; @@ -111,6 +111,8 @@ impl LockTimeLimit { } } +// TODO(c2m): make inbound connections for i2p tx proxy configurable + /// Start monerod from the -`-monero-location` flag /// /// default: /home/$USER/monero-xxx-xxx @@ -119,7 +121,10 @@ pub fn start_daemon() { let blockchain_dir = get_blockchain_dir(); let bin_dir = get_monero_location(); let release_env = utils::get_release_env(); - let tx_proxy = utils::get_i2p_http_proxy(); + let tx_proxy = format!("i2p,{}", utils::get_i2p_http_proxy()); + let port = get_daemon_port(); + let destination = i2p::get_destination(Some(port)); + let anon_inbound = format!("{}:{},8", destination, port); if release_env == utils::ReleaseEnvironment::Development { let args = ["--data-dir", &blockchain_dir, "--stagenet", "--detach"]; let output = Command::new(format!("{}/monerod", bin_dir)) @@ -128,7 +133,15 @@ pub fn start_daemon() { .expect("monerod failed to start"); debug!("{:?}", output.stdout); } else { - let args = ["--data-dir", &blockchain_dir, "--detach", "--tx-proxy", &tx_proxy]; + let args = [" + --data-dir", + &blockchain_dir, + "--tx-proxy", + &tx_proxy, + "--anonymous-inbound", + &anon_inbound, + "--detach", + ]; let output = Command::new(format!("{}/monerod", bin_dir)) .args(args) .spawn() @@ -200,6 +213,19 @@ fn get_rpc_port() -> String { port } +pub fn get_daemon_port() -> u16 { + let args = args::Args::parse(); + let rpc = String::from(args.monero_rpc_daemon); + let values = rpc.split(":"); + let mut v: Vec = values.map(|s| String::from(s)).collect(); + let port = v.remove(2); + debug!("monerod port: {}", port); + match port.parse::() { + Ok(p) => p, + Err(_) => 0, + } +} + /// Get monero rpc host from command line argument fn get_blockchain_dir() -> String { let args = args::Args::parse(); diff --git a/neveko-core/src/order.rs b/neveko-core/src/order.rs index 79eb78c..8837d6e 100644 --- a/neveko-core/src/order.rs +++ b/neveko-core/src/order.rs @@ -15,7 +15,9 @@ use rocket::serde::json::Json; /* TODOs(c2m): - - API to valid payment and import multisig info + - API to validate payment and import multisig info + - API to upload gpg encrypted tracking number + release tracking (locker code?) when txset is released - update order status */ @@ -156,12 +158,12 @@ pub fn modify(o: Json) -> Order { pub async fn sign_and_submit_multisig( orid: &String, tx_data_hex: &String) -> reqres::XmrRpcSubmitMultisigResponse { - info!("signin and submitting multisig"); + info!("signing and submitting multisig"); let r_sign: reqres::XmrRpcSignMultisigResponse = monero::sign_multisig(String::from(tx_data_hex)).await; let r_submit: reqres::XmrRpcSubmitMultisigResponse = monero::submit_multisig(r_sign.result.tx_data_hex).await; - if r_submit.result.tx_hash_list.len() == 0 { + if r_submit.result.tx_hash_list.is_empty() { error!("unable to submit payment for order: {}", orid); } r_submit diff --git a/neveko-gui/src/apps/address_book.rs b/neveko-gui/src/apps/address_book.rs index 5a2f0d3..13b831c 100644 --- a/neveko-gui/src/apps/address_book.rs +++ b/neveko-gui/src/apps/address_book.rs @@ -781,7 +781,7 @@ fn send_message_req(tx: Sender, ctx: egui::Context, body: String, to: Stri mid: utils::empty_string(), uid: utils::empty_string(), created: 0, - from: i2p::get_destination(), + from: i2p::get_destination(None), }; let j_message = utils::message_to_json(&m); tokio::spawn(async move { diff --git a/neveko-gui/src/apps/home.rs b/neveko-gui/src/apps/home.rs index 4dcff13..57c0cf7 100644 --- a/neveko-gui/src/apps/home.rs +++ b/neveko-gui/src/apps/home.rs @@ -297,7 +297,7 @@ impl eframe::App for HomeApp { ui.horizontal(|ui| { self.logo_i2p.show(ui); ui.horizontal(|ui| { - let i2p_address = i2p::get_destination(); + let i2p_address = i2p::get_destination(None); ui.label(format!("- status: {}\n- address: {}", str_i2p_status, i2p_address)); }); }); diff --git a/src/controller.rs b/src/controller.rs index fe65d3b..497f466 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -79,7 +79,7 @@ pub async fn gen_jwp(proof: Json) -> Custom> { /// Get all products /// -/// Protected: false +/// Protected: true #[get("/products")] pub async fn get_products(_jwp: proof::PaymentProof) -> Custom>> { let m_products: Vec = product::find_all(); @@ -149,3 +149,19 @@ pub async fn rx_multisig_message( message::rx_multisig(message).await; Custom(Status::Ok, Json(Default::default())) } + +/// Customer can request shipment after the wallet is funded +/// +/// with the amount of the order. The vendor will then request export +/// +/// multisig info, check balance and sanity check `unlock_time`. +/// +/// Protected: true +#[post("/", data = "")] +pub async fn request_shipment( + _jwp: proof::PaymentProof, + message: Json, +) -> Custom> { + //validate_order_for_ship(); + Custom(Status::Ok, Json(Default::default())) +}