From 01405e3d6f096e914181ddf5bc02f0dc1e1046a3 Mon Sep 17 00:00:00 2001 From: creating2morrow Date: Tue, 11 Jul 2023 08:21:11 -0400 Subject: [PATCH] begin prepare msig orchestration --- neveko-core/src/message.rs | 77 ++++++++++++++++++++--- neveko-core/src/order.rs | 9 +++ neveko-gui/src/apps/market.rs | 114 ++++++++++++++++++++++++++++------ src/controller.rs | 6 +- 4 files changed, 178 insertions(+), 28 deletions(-) diff --git a/neveko-core/src/message.rs b/neveko-core/src/message.rs index 1f2185a..b008282 100644 --- a/neveko-core/src/message.rs +++ b/neveko-core/src/message.rs @@ -140,11 +140,12 @@ fn parse_multisig_message(mid: String) -> MultisigMessageData { return Default::default(); } let orid: String = v.remove(0); - let customer_info: String = v.remove(0); - let mut info = String::from(&customer_info); - if sub_type != TXSET_MSIG { - let mediator_info: String = v.remove(0); - info = format!("{}:{}", customer_info, mediator_info); + let a_info: String = v.remove(0); + let mut info = String::from(&a_info); + // on prepare info customer only receives one set of info + if sub_type != TXSET_MSIG || !v.is_empty() { + let b_info: String = v.remove(0); + info = format!("{}:{}", a_info, b_info); } bytes = Vec::new(); debug!("zero decryption bytes: {:?}", bytes); @@ -174,8 +175,6 @@ fn parse_multisig_message(mid: String) -> MultisigMessageData { /// let s = db::Interface::open(); /// let key = "prepare-o123-test.b32.i2p"; /// 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 @@ -207,7 +206,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} + // store info as {a_info}:{a_info (optional)} let msig_key = format!("{}-{}-{}", &data.sub_type, &data.orid, &m.from); db::Interface::async_write(&s.env, &s.handle, &msig_key, &data.info).await; } @@ -564,6 +563,68 @@ pub async fn send_export_info(orid: &String, contact: &String) { create(j_message, jwp, MessageType::Multisig).await; } +/// Customer begins multisig orchestration by requesting the prepare info +/// +/// from the mediator and the vendor. In response they create an encrypted +/// +/// multisig message with the requested data. Cusomter manages multisig by +/// +/// injecting +async fn trigger_msig_info_request( + contact: String, + jwp: String, + request: reqres::MultisigInfoRequest, +) -> Result> { + let host = utils::get_i2p_http_proxy(); + let proxy = reqwest::Proxy::http(&host)?; + let client = reqwest::Client::builder().proxy(proxy).build(); + match client? + .post(format!("http://{}/multisig/info", contact)) + .header("proof", jwp) + .json(&request) + .send() + .await + { + Ok(response) => { + let res = response.json::().await; + debug!("{} info for order response: {:?}", &request.msig_type, res); + match res { + Ok(r) => Ok(r), + _ => Ok(Default::default()), + } + } + Err(e) => { + error!("failed to {} info for order due to: {:?}", &request.msig_type, e); + Ok(Default::default()) + } + } +} + +/// Deconstruction pass-through so that we can send the request from an async +/// +/// channel in the neveko-gui module. +pub async fn d_trigger_msig_info( + contact: &String, + jwp: &String, + request: &reqres::MultisigInfoRequest, +) -> Order { + let d_contact: String = String::from(contact); + let d_jwp: String = String::from(jwp); + let d_request: reqres::MultisigInfoRequest = reqres::MultisigInfoRequest { + contact: String::from(&request.contact), + info: request.info.clone(), + init_mediator: request.init_mediator, + msig_type: String::from(&request.msig_type), + orid: String::from(&request.orid), + }; + let pre = trigger_msig_info_request(d_contact, d_jwp, d_request).await; + if pre.is_err() { + log::error!("faile to trigger {} info request", request.msig_type); + return Default::default(); + } + pre.unwrap_or(Default::default()) +} + // Tests //------------------------------------------------------------------------------- diff --git a/neveko-core/src/order.rs b/neveko-core/src/order.rs index 137db21..e12fd50 100644 --- a/neveko-core/src/order.rs +++ b/neveko-core/src/order.rs @@ -366,3 +366,12 @@ pub async fn transmit_order_request( } } } + +pub async fn init_mediator_wallet(orid: &String) { + let password = std::env::var(crate::MONERO_WALLET_PASSWORD) + .unwrap_or(utils::empty_string()); + let m_wallet = monero::create_wallet(orid, &password).await; + if !m_wallet { + log::error!("failed to create mediator wallet"); + } +} diff --git a/neveko-gui/src/apps/market.rs b/neveko-gui/src/apps/market.rs index 7edbaf3..eb5b2f1 100644 --- a/neveko-gui/src/apps/market.rs +++ b/neveko-gui/src/apps/market.rs @@ -62,6 +62,8 @@ pub struct MarketApp { /// order currently being acted on m_order: models::Order, orders: Vec, + our_prepare_info_tx: Sender, + our_prepare_info_rx: Receiver, product_from_vendor: models::Product, product_image: egui_extras::RetainedImage, products: Vec, @@ -98,6 +100,7 @@ impl Default for MarketApp { let (get_vendor_products_tx, get_vendor_products_rx) = std::sync::mpsc::channel(); let (get_vendor_product_tx, get_vendor_product_rx) = std::sync::mpsc::channel(); let (submit_order_tx, submit_order_rx) = std::sync::mpsc::channel(); + let (our_prepare_info_tx, our_prepare_info_rx) = std::sync::mpsc::channel(); MarketApp { contact_info_rx, contact_info_tx, @@ -126,6 +129,8 @@ impl Default for MarketApp { is_window_shopping: false, msig: Default::default(), m_order: Default::default(), + our_prepare_info_rx, + our_prepare_info_tx, new_order: Default::default(), new_order_price: 0, new_order_shipping_address: utils::empty_string(), @@ -224,6 +229,12 @@ impl eframe::App for MarketApp { } } + // TODO(c2m): extract from db after initialization + if let Ok(our_prepare_info) = self.our_prepare_info_rx.try_recv() { + self.msig.prepare_info = our_prepare_info; + self.is_loading = false; + } + // Vendor status window //----------------------------------------------------------------------------------- let mut is_showing_vendor_status = self.is_showing_vendor_status; @@ -287,21 +298,30 @@ impl eframe::App for MarketApp { .vscroll(true) .show(ctx, |ui| { ui.heading("Multisig Management"); + if self.is_loading { + ui.add(egui::Spinner::new()); + ui.label("msig request in progress..."); + } ui.horizontal(|ui| { let mediator = ui.label("Mediator: "); let prefix = String::from(crate::GUI_MSIG_MEDIATOR_DB_KEY); if !self.msig.query_mediator { - let mediator_db = utils::search_gui_db(String::from(&prefix), self.m_order.orid.clone()); + let mediator_db = + utils::search_gui_db(String::from(&prefix), self.m_order.orid.clone()); log::debug!("mediator db: {}", mediator_db); self.msig.has_mediator = mediator_db != utils::empty_string(); self.msig.mediator = mediator_db; self.msig.query_mediator = true; } else if self.msig.query_mediator && !self.msig.has_mediator { ui.text_edit_singleline(&mut self.msig.mediator) - .labelled_by(mediator.id); + .labelled_by(mediator.id); ui.label("\t"); if ui.button("Set Mediator").clicked() { - utils::write_gui_db(prefix, self.m_order.orid.clone(), self.msig.mediator.clone()); + utils::write_gui_db( + prefix, + self.m_order.orid.clone(), + self.msig.mediator.clone(), + ); self.msig.has_mediator = true; } } else { @@ -318,38 +338,47 @@ impl eframe::App for MarketApp { ui.horizontal(|ui| { ui.label("Prepare: \t\t\t\t\t"); if ui.button("Prepare").clicked() { - + self.is_loading = true; + let mediator_prefix = String::from(crate::GUI_MSIG_MEDIATOR_DB_KEY); + let vendor_prefix = String::from(crate::GUI_OVL_DB_KEY); + let mediator = + utils::search_gui_db(mediator_prefix, self.m_order.orid.clone()); + let vendor = utils::search_gui_db(vendor_prefix, self.m_order.orid.clone()); + // get prepare multisig info from vendor and mediator + // call prepare multisig and save to db + send_prepare_info_req( + self.our_prepare_info_tx.clone(), + ctx.clone(), + self.vendor_status.jwp.clone(), + mediator, + &self.m_order.orid.clone(), + vendor, + ) } }); ui.horizontal(|ui| { ui.label("Make: \t\t\t\t\t\t"); - if ui.button("Make").clicked() { - - } + if ui.button("Make").clicked() {} }); ui.horizontal(|ui| { ui.label("Exchange Keys: \t\t"); - if ui.button("Exchange").clicked() { - - } + if ui.button("Exchange").clicked() {} }); ui.horizontal(|ui| { ui.label("Fund:\t\t\t\t\t\t\t"); - if ui.button("Fund").clicked() { - - } + if ui.button("Fund").clicked() {} }); ui.horizontal(|ui| { ui.label("Export Info: \t\t\t\t"); - if ui.button("Export").clicked() { - - } + if ui.button("Export").clicked() {} }); ui.horizontal(|ui| { ui.label("Release Payment: \t"); - if ui.button("Sign Txset").clicked() { - - } + if ui.button("Sign Txset").clicked() {} + }); + ui.horizontal(|ui| { + ui.label("Create Dispute: \t\t"); + if ui.button("Dispute").clicked() {} }); ui.label("\n"); if ui.button("Exit").clicked() { @@ -1225,7 +1254,54 @@ fn submit_order_req( let u_order = order.unwrap_or_else(|_| Default::default()); // cache order request to db order::backup(&u_order); + let prefix = String::from(crate::GUI_OVL_DB_KEY); + let orid = String::from(&u_order.orid); + let i2p = String::from(&contact); + utils::write_gui_db(prefix, orid, i2p); let _ = tx.send(u_order); ctx.request_repaint(); }); } + +fn send_prepare_info_req( + tx: Sender, + ctx: egui::Context, + jwp: String, + mediator: String, + orid: &String, + vendor: String, +) + { + let m_orid: String = String::from(orid); + let v_orid: String = String::from(orid); + tokio::spawn(async move { + let prepare_info = monero::prepare_wallet().await; + let ref_prepare_info: &String = &prepare_info.result.multisig_info; + utils::write_gui_db( + String::from(crate::GUI_MSIG_PREPARE_DB_KEY), + utils::empty_string(), + String::from(ref_prepare_info) + ); + // Request mediator and vendor while we're at it + // Will coordinating send this on make requests next + log::debug!("constructing {} msig messages", message::PREPARE_MSIG); + let v_msig_request: reqres::MultisigInfoRequest = reqres::MultisigInfoRequest { + contact: i2p::get_destination(None), + info: Vec::new(), + init_mediator: false, + msig_type: String::from(message::PREPARE_MSIG), + orid: String::from(v_orid), + }; + let _v_result = message::d_trigger_msig_info(&vendor, &jwp, &v_msig_request).await; + let m_msig_request: reqres::MultisigInfoRequest = reqres::MultisigInfoRequest { + contact: i2p::get_destination(None), + info: Vec::new(), + init_mediator: false, + msig_type: String::from(message::PREPARE_MSIG), + orid: String::from(m_orid), + }; + let _m_result = message::d_trigger_msig_info(&mediator, &jwp, &m_msig_request).await; + let _ = tx.send(String::from(ref_prepare_info)); + }); + ctx.request_repaint(); +} diff --git a/src/controller.rs b/src/controller.rs index ce5af71..050729b 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -145,6 +145,10 @@ pub async fn get_multisig_info( ) -> Custom> { let info: Vec = r_info.info.iter().cloned().collect(); if r_info.msig_type == String::from(message::PREPARE_MSIG) { + // mediator won't have wallet for order yet do that first + if r_info.init_mediator { + order::init_mediator_wallet(&r_info.orid).await; + } message::send_prepare_info(&r_info.orid, &r_info.contact).await; } else if r_info.msig_type == String::from(message::MAKE_MSIG) { message::send_make_info(&r_info.orid, &r_info.contact, info).await; @@ -156,7 +160,7 @@ pub async fn get_multisig_info( Custom(Status::Ok, Json(Default::default())) } -/// Recieve multisig messages here +/// Recieve multisig messages here for vendor order processing /// /// Protected: true #[post("/", data = "")]