add fee estimator before jwp creation

This commit is contained in:
creating2morrow 2023-05-12 04:51:20 -04:00
parent dea7a3a52a
commit dbcddfa6a4
6 changed files with 123 additions and 43 deletions

View file

@ -3,3 +3,4 @@ format_code_in_doc_comments = true
imports_granularity = "Crate"
imports_layout = "Vertical"
wrap_comments = true
ignore = ["lib.rs"]

View file

@ -91,8 +91,8 @@ async fn find_tunnels() {
tokio::time::sleep(Duration::new(120, 0)).await;
create_tunnel();
}
// TODO(c2m): why is i2p-zero http proxy always giving "destination not found" error?
// if !has_http_tunnel { create_http_proxy(); }
// TODO(c2m): why is i2p-zero http proxy always giving "destination not
// found" error? if !has_http_tunnel { create_http_proxy(); }
}
pub async fn start() {
@ -133,7 +133,8 @@ fn create_tunnel() {
// TODO(c2m): use i2p-zero http proxy
// fn create_http_proxy() {
// info!("creating http proxy");
// let output = Command::new("./i2p-zero-linux.v1.20/router/bin/tunnel-control.sh")
// let output =
// Command::new("./i2p-zero-linux.v1.20/router/bin/tunnel-control.sh")
// .args(["http.create", "4444"])
// .spawn()
// .expect("i2p-zero failed to create a http proxy");

View file

@ -935,7 +935,7 @@ pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse {
match client.post(host).json(&req).send().await {
Ok(response) => {
let res = response.json::<reqres::XmrDaemonGetInfoResponse>().await;
debug!("{} response: {:?}", DaemonFields::GetInfo.value(), res);
// add debug log here if needed for adding more info to home screen in gui
match res {
Ok(res) => res,
_ => Default::default(),
@ -949,16 +949,13 @@ pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse {
pub async fn get_height() -> reqres::XmrDaemonGetHeightResponse {
info!("fetching daemon height");
let client = reqwest::Client::new();
let host = get_rpc_daemon();
let req = reqres::XmrRpcRequest {
jsonrpc: DaemonFields::Version.value(),
id: DaemonFields::Id.value(),
method: DaemonFields::GetHeight.value(),
};
match client.post(host).json(&req).send().await {
let args = args::Args::parse();
let daemon = String::from(args.monero_rpc_daemon);
let req = format!("{}/{}", daemon, DaemonFields::GetHeight.value());
match client.post(req).send().await {
Ok(response) => {
let res = response.json::<reqres::XmrDaemonGetHeightResponse>().await;
debug!("{} response: {:?}", DaemonFields::GetHeight.value(), res);
// don't log this one. The fee estimator blows up logs (T_T)
match res {
Ok(res) => res,
_ => Default::default(),
@ -983,7 +980,7 @@ pub async fn get_block(height: u64) -> reqres::XmrDaemonGetBlockResponse {
match client.post(host).json(&req).send().await {
Ok(response) => {
let res = response.json::<reqres::XmrDaemonGetBlockResponse>().await;
debug!("{} response: {:?}", DaemonFields::GetBlock.value(), res);
// don't log this one. The fee estimator blows up logs (T_T)
match res {
Ok(res) => res,
_ => Default::default(),
@ -997,15 +994,19 @@ pub async fn get_block(height: u64) -> reqres::XmrDaemonGetBlockResponse {
pub async fn get_transactions(txs_hashes: Vec<String>) -> reqres::XmrDaemonGetTransactionsResponse {
info!("fetching {} transactions", txs_hashes.len());
let client = reqwest::Client::new();
let host = get_rpc_daemon();
let args = args::Args::parse();
let daemon = String::from(args.monero_rpc_daemon);
let url = format!("{}/{}", daemon, DaemonFields::GetTransactions.value());
let req = reqres::XmrDaemonGetTransactionsRequest {
txs_hashes,
decode_as_json: true,
};
match client.post(host).json(&req).send().await {
match client.post(url).json(&req).send().await {
Ok(response) => {
let res = response.json::<reqres::XmrDaemonGetTransactionsResponse>().await;
debug!("{} response: {:?}", DaemonFields::GetTransactions.value(), res);
let res = response
.json::<reqres::XmrDaemonGetTransactionsResponse>()
.await;
// don't log this one. The fee estimator blows up logs (T_T)
match res {
Ok(res) => res,
_ => Default::default(),

View file

@ -563,7 +563,10 @@ pub struct XmrDaemonGetBlockResult {
pub miner_tx_hash: String,
pub status: String,
pub top_hash: String,
pub tx_hashes: Vec<String>,
/// For some reason this field just disappears on non-
///
/// coinbase transactions instead of being an empty list.
pub tx_hashes: Option<Vec<String>>,
pub untrusted: bool,
}
// responses
@ -659,13 +662,14 @@ impl Default for XmrDaemonGetBlockResponse {
miner_tx_hash: utils::empty_string(),
status: utils::empty_string(),
top_hash: utils::empty_string(),
tx_hashes: Vec::new(),
tx_hashes: Some(Vec::new()),
untrusted: false,
}
},
}
}
}
/// Only extract the json string. TODO(c2m): map to a struct
#[derive(Serialize, Deserialize, Debug)]
pub struct XmrDaemonGetTransactionsResponse {
pub txs_as_json: Vec<String>,

View file

@ -593,7 +593,7 @@ fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool {
actual_hash == expected_hash
}
/// The highly ineffecient fee estimator.
/// ### The highly ineffecient fee estimator.
///
/// Get the current height. Start fetching blocks
///
@ -603,21 +603,53 @@ fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool {
///
/// extract the `txnFee` from the `as_json` field.
///
/// Once we have accumulated n=30 fees paid return the
/// Once we have accumulated n>=30 fees paid return the
///
/// average fee paid from the most recent 30 transactions.
///
/// Note, it may take more than one block to do this,
///
/// especially on stagenet.
pub fn estimate_fee() -> u128 {
0
pub async fn estimate_fee() -> u128 {
let mut height: u64 = 0;
let mut count: u64 = 1;
let mut v_fee: Vec<u128> = Vec::new();
loop {
debug!("current height: {}", height);
if v_fee.len() >= 30 { break; }
let r_height = monero::get_height().await;
height = r_height.height - count;
let block = monero::get_block(height).await;
if block.result.block_header.num_txes > 0 {
debug!("fetching {} txs", block.result.block_header.num_txes);
let tx_hashes: Option<Vec<String>> = block.result.tx_hashes;
let transactions = monero::get_transactions(tx_hashes.unwrap()).await;
for tx in transactions.txs_as_json {
let pre_fee_split = tx.split("txnFee\":");
let mut v1: Vec<String> = pre_fee_split.map(|s| String::from(s)).collect();
let fee_split = v1.remove(1);
let post_fee_split = fee_split.split(",");
let mut v2: Vec<String> = post_fee_split.map(|s| String::from(s)).collect();
let fee: u128 = match v2.remove(0).trim().parse::<u128>() {
Ok(n) => n,
Err(_e) => 0,
};
v_fee.push(fee);
}
}
count += 1;
}
&v_fee.iter().sum() / v_fee.len() as u128
}
/// Combine the results `estimate_fee()` and `get_balance()` to
///
/// determine whether or not a transfer is possible.
pub fn can_transfer() -> bool {
false
/// determine whether or not a transfer for a given invoice is possible.
pub async fn can_transfer(invoice: u128) -> bool {
let balance = monero::get_balance().await;
let fee = estimate_fee().await;
debug!("fee estimated to: {}", fee);
debug!("balance: {}", balance.result.unlocked_balance);
debug!("fee + invoice = {}", invoice + fee);
balance.result.unlocked_balance > (fee + invoice)
}

View file

@ -74,6 +74,9 @@ pub struct AddressBookApp {
approve_contact: bool,
approve_payment: bool,
added: bool,
can_transfer: bool,
can_transfer_tx: Sender<bool>,
can_transfer_rx: Receiver<bool>,
compose: Compose,
contact: String,
find_contact: String,
@ -89,6 +92,7 @@ pub struct AddressBookApp {
invoice_rx: Receiver<reqres::Invoice>,
is_adding: bool,
is_composing: bool,
is_estimating_fee: bool,
is_pinging: bool,
is_loading: bool,
is_message_sent: bool,
@ -107,6 +111,7 @@ pub struct AddressBookApp {
impl Default for AddressBookApp {
fn default() -> Self {
let (can_transfer_tx, can_transfer_rx) = std::sync::mpsc::channel();
let (contact_add_tx, contact_add_rx) = std::sync::mpsc::channel();
let (contact_info_tx, contact_info_rx) = std::sync::mpsc::channel();
let (contact_timeout_tx, contact_timeout_rx) = std::sync::mpsc::channel();
@ -118,6 +123,9 @@ impl Default for AddressBookApp {
approve_contact: false,
approve_payment: false,
added: false,
can_transfer: false,
can_transfer_rx,
can_transfer_tx,
compose: Default::default(),
contact: utils::empty_string(),
contacts: Vec::new(),
@ -133,6 +141,7 @@ impl Default for AddressBookApp {
invoice_rx,
is_adding: false,
is_composing: false,
is_estimating_fee: false,
is_loading: false,
is_message_sent: false,
is_pinging: false,
@ -164,6 +173,7 @@ impl eframe::App for AddressBookApp {
self.is_pinging = false;
}
}
if let Ok(added_contact) = self.contact_add_rx.try_recv() {
self.s_added_contact = added_contact;
if self.s_added_contact.cid != utils::empty_string() {
@ -171,6 +181,7 @@ impl eframe::App for AddressBookApp {
self.is_loading = false;
}
}
if let Ok(timeout) = self.contact_timeout_rx.try_recv() {
self.is_timeout = true;
if timeout {
@ -180,9 +191,18 @@ impl eframe::App for AddressBookApp {
self.contact = utils::empty_string();
}
}
if let Ok(invoice) = self.invoice_rx.try_recv() {
self.s_invoice = invoice;
if self.s_invoice.pay_threshold > 0 {
send_can_transfer_req(
self.can_transfer_tx.clone(),
ctx.clone(), self.s_invoice.pay_threshold
);
self.is_estimating_fee = true;
}
}
if let Ok(payment) = self.payment_rx.try_recv() {
self.is_payment_processed = payment;
if self.is_payment_processed {
@ -191,6 +211,7 @@ impl eframe::App for AddressBookApp {
self.showing_status = false;
}
}
if let Ok(message) = self.send_message_rx.try_recv() {
self.is_message_sent = message;
if self.is_message_sent {
@ -200,6 +221,11 @@ impl eframe::App for AddressBookApp {
}
}
if let Ok(can_transfer) = self.can_transfer_rx.try_recv() {
self.can_transfer = can_transfer;
self.is_estimating_fee = false;
}
// initial contact load
if !self.contacts_init {
self.contacts = contact::find_all();
@ -258,14 +284,19 @@ impl eframe::App for AddressBookApp {
ui.add(egui::Spinner::new());
ui.label("creating jwp may take a few minutes...");
}
if self.is_estimating_fee {
ui.add(egui::Spinner::new());
ui.label("running nevmes jwp fee estimator...");
}
ui.heading(self.status.i2p.clone());
ui.label(format!("pay to: {}", address));
ui.label(format!("amount: {} piconero(s)", amount));
ui.label(format!("expiration: {} blocks", expire));
if !self.is_loading {
if self.s_invoice.address != utils::empty_string() {
if self.s_invoice.address != utils::empty_string() && self.can_transfer {
if ui.button("Approve").clicked() {
// activate xmr "transfer", check the hash, update db and refresh
// Note it is simply disabled on insufficient funds as calcd by fee estimator
let d: reqres::Destination = reqres::Destination { address, amount };
send_payment_req(
self.payment_tx.clone(),
@ -764,3 +795,13 @@ fn change_nick_req(contact: String, nick: String) {
clear_gui_db(String::from("gui-nick"), String::from(&contact));
write_gui_db(String::from("gui-nick"), String::from(&contact), nick);
}
fn send_can_transfer_req(tx: Sender<bool>, ctx: egui::Context, invoice: u128) {
log::debug!("async send_can_transfer_req");
tokio::spawn(async move {
let can_transfer = utils::can_transfer(invoice).await;
log::debug!("can transfer: {}", can_transfer);
let _ = tx.send(can_transfer);
ctx.request_repaint();
});
}