fix prove payment zero conf retry logic

This commit is contained in:
creating2morrow 2023-06-12 11:37:39 -04:00
parent c75ff4e4f1
commit 39827212f6
9 changed files with 178 additions and 87 deletions

View file

@ -165,7 +165,6 @@ pub fn trust_gpg(key: String) {
/// Get invoice for jwp creation
pub async fn request_invoice(contact: String) -> Result<reqres::Invoice, Box<dyn Error>> {
// 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<reqres::Invoice, Box<dyn
///
/// for gpg key removal.
pub async fn add_contact_request(contact: String, prune: u32) -> Result<Contact, Box<dyn Error>> {
// 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();

View file

@ -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";

View file

@ -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 {

View file

@ -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<reqres::Jwp, Box<dyn Error>> {
// 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();

View file

@ -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<Destination>,
@ -438,7 +447,8 @@ pub struct Transfer {
pub address: String,
pub amount: u128,
pub amounts: Vec<u128>,
pub confirmations: u64,
/// On zero conf this field is missing
pub confirmations: Option<u64>,
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,

View file

@ -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 {

View file

@ -61,6 +61,7 @@ pub struct AddressBookApp {
invoice_rx: Receiver<reqres::Invoice>,
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();

View file

@ -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");

View file

@ -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))]