From 39827212f67946b22cfcc6bac7084eec288ba762 Mon Sep 17 00:00:00 2001 From: creating2morrow Date: Mon, 12 Jun 2023 11:37:39 -0400 Subject: [PATCH] fix prove payment zero conf retry logic --- neveko-core/src/contact.rs | 2 - neveko-core/src/lib.rs | 1 - neveko-core/src/monero.rs | 8 +- neveko-core/src/proof.rs | 1 - neveko-core/src/reqres.rs | 14 +- neveko-core/src/utils.rs | 1 + neveko-gui/src/apps/address_book.rs | 234 +++++++++++++++++++--------- neveko-gui/src/apps/home.rs | 1 - neveko-gui/src/apps/lock_screen.rs | 3 - 9 files changed, 178 insertions(+), 87 deletions(-) diff --git a/neveko-core/src/contact.rs b/neveko-core/src/contact.rs index 7b959bb..a79894c 100644 --- a/neveko-core/src/contact.rs +++ b/neveko-core/src/contact.rs @@ -165,7 +165,6 @@ pub fn trust_gpg(key: String) { /// Get invoice for jwp creation pub async fn request_invoice(contact: String) -> Result> { - // TODO(c2m): Error handling for http 402 status let host = utils::get_i2p_http_proxy(); let proxy = reqwest::Proxy::http(&host)?; let client = reqwest::Client::builder().proxy(proxy).build(); @@ -193,7 +192,6 @@ pub async fn request_invoice(contact: String) -> Result Result> { - // TODO(c2m): Error handling for http 402 status let host = utils::get_i2p_http_proxy(); let proxy = reqwest::Proxy::http(&host)?; let client = reqwest::Client::builder().proxy(proxy).build(); diff --git a/neveko-core/src/lib.rs b/neveko-core/src/lib.rs index 5706b96..57c5858 100644 --- a/neveko-core/src/lib.rs +++ b/neveko-core/src/lib.rs @@ -19,7 +19,6 @@ pub const APP_NAME: &str = "neveko"; pub const NEVEKO_JWP_SECRET_KEY: &str = "NEVEKO_JWP_SECRET_KEY"; pub const NEVEKO_JWT_SECRET_KEY: &str = "NEVEKO_JWT_SECRET_KEY"; -// TODO(c2m): better handling of setting initial wallet password /// Environment variable for injecting wallet password pub const MONERO_WALLET_PASSWORD: &str = "MONERO_WALLET_PASSWORD"; diff --git a/neveko-core/src/monero.rs b/neveko-core/src/monero.rs index 642a1f6..6ba0a78 100644 --- a/neveko-core/src/monero.rs +++ b/neveko-core/src/monero.rs @@ -45,10 +45,10 @@ impl TransactionType { pub fn value(&self) -> String { match *self { Self::Failed => String::from("failed"), - Self::In => String::from("In"), - Self::Out => String::from("Out"), - Self::Pending => String::from("Pending"), - Self::Pool => String::from("Pool"), + Self::In => String::from("in"), + Self::Out => String::from("out"), + Self::Pending => String::from("pending"), + Self::Pool => String::from("pool"), } } pub fn propogated(tx_type: String) -> bool { diff --git a/neveko-core/src/proof.rs b/neveko-core/src/proof.rs index a143c3c..b88df29 100644 --- a/neveko-core/src/proof.rs +++ b/neveko-core/src/proof.rs @@ -117,7 +117,6 @@ pub async fn create_jwp(proof: &TxProof) -> String { /// Send transaction proof to contact for JWP generation pub async fn prove_payment(contact: String, txp: &TxProof) -> Result> { - // TODO(c2m): Error handling for http 402 status let host = utils::get_i2p_http_proxy(); let proxy = reqwest::Proxy::http(&host)?; let client = reqwest::Client::builder().proxy(proxy).build(); diff --git a/neveko-core/src/reqres.rs b/neveko-core/src/reqres.rs index c7b6083..3fe5608 100644 --- a/neveko-core/src/reqres.rs +++ b/neveko-core/src/reqres.rs @@ -112,6 +112,15 @@ pub struct Destination { pub amount: u128, } +impl Default for Destination { + fn default() -> Self { + Destination { + address: utils::empty_string(), + amount: 0, + } + } +} + #[derive(Deserialize, Serialize, Debug)] pub struct XmrRpcTransferParams { pub destinations: Vec, @@ -438,7 +447,8 @@ pub struct Transfer { pub address: String, pub amount: u128, pub amounts: Vec, - pub confirmations: u64, + /// On zero conf this field is missing + pub confirmations: Option, pub double_spend_seen: bool, pub fee: u128, pub height: u64, @@ -984,7 +994,7 @@ impl Default for XmrRpcGetTxByIdResponse { address: utils::empty_string(), amount: 0, amounts: Vec::new(), - confirmations: 0, + confirmations: None, double_spend_seen: false, fee: 0, height: 0, diff --git a/neveko-core/src/utils.rs b/neveko-core/src/utils.rs index 7232e0f..3451d35 100644 --- a/neveko-core/src/utils.rs +++ b/neveko-core/src/utils.rs @@ -716,6 +716,7 @@ pub async fn can_transfer(invoice: u128) -> bool { /// Gui toggle for vendor mode pub fn toggle_vendor_enabled() -> bool { + // TODO(c2m): Dont toggle vendors with orders status != Delivered let s = db::Interface::open(); let r = db::Interface::read(&s.env, &s.handle, contact::NEVEKO_VENDOR_ENABLED); if r != contact::NEVEKO_VENDOR_MODE_ON { diff --git a/neveko-gui/src/apps/address_book.rs b/neveko-gui/src/apps/address_book.rs index 968554d..9b41539 100644 --- a/neveko-gui/src/apps/address_book.rs +++ b/neveko-gui/src/apps/address_book.rs @@ -61,6 +61,7 @@ pub struct AddressBookApp { invoice_rx: Receiver, is_adding: bool, is_composing: bool, + is_approving_jwp: bool, is_estimating_fee: bool, is_pinging: bool, is_loading: bool, @@ -110,6 +111,7 @@ impl Default for AddressBookApp { invoice_rx, is_adding: false, is_composing: false, + is_approving_jwp: false, is_estimating_fee: false, is_loading: false, is_message_sent: false, @@ -275,8 +277,10 @@ impl eframe::App for AddressBookApp { d, self.status.i2p.clone(), expire, + false, ); self.is_loading = true; + self.is_approving_jwp = false; } } if ui.button("Exit").clicked() { @@ -295,9 +299,14 @@ impl eframe::App for AddressBookApp { .title_bar(false) .id(egui::Id::new(self.status.i2p.clone())) .show(&ctx, |ui| { - if self.is_pinging { + if self.is_pinging || self.is_loading { + let spinner_text = if self.is_loading { + "retrying payment proof... " + } else { + "pinging..." + }; ui.add(egui::Spinner::new()); - ui.label("pinging..."); + ui.label(spinner_text); } let status = if self.s_contact.xmr_address != utils::empty_string() { "online" @@ -323,6 +332,7 @@ impl eframe::App for AddressBookApp { ); self.approve_payment = true; self.showing_status = false; + self.is_approving_jwp = true; } } if !self.status.signed_key { @@ -346,6 +356,21 @@ impl eframe::App for AddressBookApp { self.showing_status = false; } } + if self.status.txp != utils::empty_string() + && self.status.jwp == utils::empty_string() + && status == "online" { + if ui.button("Prove Retry").clicked() { + send_payment_req( + self.payment_tx.clone(), + ctx.clone(), + Default::default(), + self.status.i2p.clone(), + expire as u64, + true, + ); + self.is_loading = true; + } + } ui.horizontal(|ui| { let nick_label = ui.label("nick: "); ui.text_edit_singleline(&mut self.add_nick) @@ -357,12 +382,16 @@ impl eframe::App for AddressBookApp { } if ui.button("Exit").clicked() { self.showing_status = false; + self.is_loading = false; } }); // Main panel for adding contacts //----------------------------------------------------------------------------------- egui::CentralPanel::default().show(ctx, |ui| { + if self.is_approving_jwp { + ui.add(egui::Spinner::new()); + } ui.heading("Add Contact"); ui.label( "____________________________________________________________________________\n", @@ -643,83 +672,142 @@ fn send_payment_req( d: reqres::Destination, contact: String, expire: u64, + retry: bool, ) { log::debug!("async send_payment_req"); log::debug!("cleaning stale jwp values"); - utils::clear_gui_db(String::from("gui-txp"), String::from(&contact)); - utils::clear_gui_db(String::from("gui-jwp"), String::from(&contact)); - utils::clear_gui_db(String::from("gui-exp"), String::from(&contact)); tokio::spawn(async move { - let ptxp_address = String::from(&d.address); - let ftxp_address = String::from(&d.address); - log::debug!("sending {} piconero(s) to: {}", &d.amount, &d.address); - let wallet_name = String::from(neveko_core::APP_NAME); - let wallet_password = - std::env::var(neveko_core::MONERO_WALLET_PASSWORD).unwrap_or(String::from("password")); - monero::open_wallet(&wallet_name, &wallet_password).await; - let transfer: reqres::XmrRpcTransferResponse = monero::transfer(d).await; - // in order to keep the jwp creation process transparent to the user - // we will process all logic in one shot here. + if !retry { + utils::clear_gui_db(String::from("gui-txp"), String::from(&contact)); + utils::clear_gui_db(String::from("gui-jwp"), String::from(&contact)); + utils::clear_gui_db(String::from("gui-exp"), String::from(&contact)); + let ptxp_address = String::from(&d.address); + let ftxp_address = String::from(&d.address); + log::debug!("sending {} piconero(s) to: {}", &d.amount, &d.address); + let wallet_name = String::from(neveko_core::APP_NAME); + let wallet_password = + std::env::var(neveko_core::MONERO_WALLET_PASSWORD).unwrap_or(String::from("password")); + monero::open_wallet(&wallet_name, &wallet_password).await; + let transfer: reqres::XmrRpcTransferResponse = monero::transfer(d).await; + // in order to keep the jwp creation process transparent to the user + // we will process all logic in one shot here. - // use the hash to create a PENDING transaction proof - let ptxp_hash = String::from(&transfer.result.tx_hash); - let ftxp_hash = String::from(&transfer.result.tx_hash); - let ptxp: proof::TxProof = proof::TxProof { - subaddress: ptxp_address, - confirmations: 0, - hash: ptxp_hash, - message: utils::empty_string(), - signature: utils::empty_string(), - }; - log::debug!("creating transaction proof for: {}", &ptxp.hash); - let get_txp: reqres::XmrRpcGetTxProofResponse = monero::get_tx_proof(ptxp).await; - // use the signature to create the FINALIZED transaction proof - let ftxp: proof::TxProof = proof::TxProof { - subaddress: ftxp_address, - confirmations: 0, - hash: ftxp_hash, - message: utils::empty_string(), - signature: get_txp.result.signature, - }; - utils::write_gui_db( - String::from("gui-txp"), - String::from(&contact), - String::from(&ftxp.signature), - ); - log::debug!( - "proving payment to {} for: {}", - String::from(&contact), - &ftxp.hash - ); - monero::close_wallet(&wallet_name, &wallet_password).await; - // if we made it this far we can now request a JWP from our friend - // wait a bit for the tx to propogate - tokio::time::sleep(std::time::Duration::from_secs( - crate::BLOCK_TIME_IN_SECS_EST, - )) - .await; - match proof::prove_payment(String::from(&contact), &ftxp).await { - Ok(result) => { - utils::write_gui_db( - String::from("gui-jwp"), - String::from(&contact), - String::from(&result.jwp), - ); - // this is just an estimate expiration but should suffice - let seconds: i64 = expire as i64 * 2 * 60; - // subtract 120 seconds since we had to wait for one confirmation - let grace: i64 = seconds - BLOCK_TIME_IN_SECS_EST as i64; - let unix: i64 = chrono::offset::Utc::now().timestamp() + grace; - utils::write_gui_db( - String::from("gui-exp"), - String::from(&contact), - format!("{}", unix), - ); - // TODO(c2m): edge case when proving payment fails to complete - // case the payment proof data and set retry logic - ctx.request_repaint(); + // use the hash to create a PENDING transaction proof + let ptxp_hash = String::from(&transfer.result.tx_hash); + let ftxp_hash = String::from(&transfer.result.tx_hash); + let ptxp: proof::TxProof = proof::TxProof { + subaddress: ptxp_address, + confirmations: 0, + hash: ptxp_hash, + message: utils::empty_string(), + signature: utils::empty_string(), + }; + log::debug!("creating transaction proof for: {}", &ptxp.hash); + let get_txp: reqres::XmrRpcGetTxProofResponse = monero::get_tx_proof(ptxp).await; + // use the signature to create the FINALIZED transaction proof + let ftxp: proof::TxProof = proof::TxProof { + subaddress: ftxp_address, + confirmations: 0, + hash: ftxp_hash, + message: utils::empty_string(), + signature: get_txp.result.signature, + }; + utils::write_gui_db( + String::from("gui-txp"), + String::from(&contact), + String::from(&ftxp.signature), + ); + utils::write_gui_db( + String::from("gui-txp-hash"), + String::from(&contact), + String::from(&ftxp.hash), + ); + utils::write_gui_db( + String::from("gui-txp-sig"), + String::from(&contact), + String::from(&ftxp.signature), + ); + utils::write_gui_db( + String::from("gui-txp-subaddress"), + String::from(&contact), + String::from(&ftxp.subaddress), + ); + log::debug!( + "proving payment to {} for: {}", + String::from(&contact), + &ftxp.hash + ); + match proof::prove_payment(String::from(&contact), &ftxp).await { + Ok(result) => { + utils::write_gui_db( + String::from("gui-jwp"), + String::from(&contact), + String::from(&result.jwp), + ); + // this is just an estimate expiration but should suffice + let seconds: i64 = expire as i64 * 2 * 60; + // subtract 120 seconds since we had to wait for one confirmation + let grace: i64 = seconds - BLOCK_TIME_IN_SECS_EST as i64; + let unix: i64 = chrono::offset::Utc::now().timestamp() + grace; + utils::write_gui_db( + String::from("gui-exp"), + String::from(&contact), + format!("{}", unix), + ); + // TODO(c2m): edge case when proving payment fails to complete + // case the payment proof data and set retry logic + ctx.request_repaint(); + } + _ => log::error!("failed to obtain jwp"), + } + monero::close_wallet(&wallet_name, &wallet_password).await; + // if we made it this far we can now request a JWP from our friend + // wait a bit for the tx to propogate + tokio::time::sleep(std::time::Duration::from_secs( + crate::BLOCK_TIME_IN_SECS_EST, + )) + .await; + } + if retry { + let k_hash = String::from("gui-txp-hash"); + let k_sig = String::from("gui-txp-sig"); + let k_subaddress = String::from("gui-txp-subaddress"); + let hash = utils::search_gui_db(k_hash, String::from(&contact)); + let signature = utils::search_gui_db(k_sig, String::from(&contact)); + let subaddress = utils::search_gui_db(k_subaddress, String::from(&contact)); + let ftxp: proof::TxProof = proof::TxProof { + subaddress, + confirmations: 0, + hash: String::from(&hash), + message: utils::empty_string(), + signature, + }; + log::debug!( + "proving payment to {} for: {}", + String::from(&contact), + &ftxp.hash + ); + match proof::prove_payment(String::from(&contact), &ftxp).await { + Ok(result) => { + utils::write_gui_db( + String::from("gui-jwp"), + String::from(&contact), + String::from(&result.jwp), + ); + // this is just an estimate expiration but should suffice + let seconds: i64 = expire as i64 * 2 * 60; + // subtract 120 seconds since we had to wait for one confirmation + let grace: i64 = seconds - BLOCK_TIME_IN_SECS_EST as i64; + let unix: i64 = chrono::offset::Utc::now().timestamp() + grace; + utils::write_gui_db( + String::from("gui-exp"), + String::from(&contact), + format!("{}", unix), + ); + ctx.request_repaint(); + } + _ => log::error!("failed to obtain jwp"), } - _ => log::error!("failed to obtain jwp"), } let _ = tx.send(true); ctx.request_repaint(); diff --git a/neveko-gui/src/apps/home.rs b/neveko-gui/src/apps/home.rs index 9c52b53..3b44427 100644 --- a/neveko-gui/src/apps/home.rs +++ b/neveko-gui/src/apps/home.rs @@ -365,7 +365,6 @@ impl eframe::App for HomeApp { self.s_xmr_rpc_ver.result.version, address, unlocked_balance, locked_balance, unlock_time, xmrd_info.nettype, xmrd_info.top_block_hash, xmrd_info.height, xmrd_info.synchronized, db_size, free_space, xmrd_info.version)); - // TODO(c2m): pull in more xmr blockchain information? }); ui.label("____________________________________________________________________\n"); ui.label("\n"); diff --git a/neveko-gui/src/apps/lock_screen.rs b/neveko-gui/src/apps/lock_screen.rs index 84c0623..a6b182e 100644 --- a/neveko-gui/src/apps/lock_screen.rs +++ b/neveko-gui/src/apps/lock_screen.rs @@ -5,9 +5,6 @@ use sha2::{ Sha512, }; -/// TODO(c2m): Create a more secure locking mechanism -/// -/// is there a way to trigger system screen lock on the machine??? #[derive(PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))]