mirror of
https://github.com/creating2morrow/neveko.git
synced 2025-01-03 09:29:39 +00:00
add fee estimator before jwp creation
This commit is contained in:
parent
dea7a3a52a
commit
dbcddfa6a4
6 changed files with 123 additions and 43 deletions
|
@ -3,3 +3,4 @@ format_code_in_doc_comments = true
|
||||||
imports_granularity = "Crate"
|
imports_granularity = "Crate"
|
||||||
imports_layout = "Vertical"
|
imports_layout = "Vertical"
|
||||||
wrap_comments = true
|
wrap_comments = true
|
||||||
|
ignore = ["lib.rs"]
|
||||||
|
|
|
@ -91,8 +91,8 @@ async fn find_tunnels() {
|
||||||
tokio::time::sleep(Duration::new(120, 0)).await;
|
tokio::time::sleep(Duration::new(120, 0)).await;
|
||||||
create_tunnel();
|
create_tunnel();
|
||||||
}
|
}
|
||||||
// TODO(c2m): why is i2p-zero http proxy always giving "destination not found" error?
|
// TODO(c2m): why is i2p-zero http proxy always giving "destination not
|
||||||
// if !has_http_tunnel { create_http_proxy(); }
|
// found" error? if !has_http_tunnel { create_http_proxy(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start() {
|
pub async fn start() {
|
||||||
|
@ -133,7 +133,8 @@ fn create_tunnel() {
|
||||||
// TODO(c2m): use i2p-zero http proxy
|
// TODO(c2m): use i2p-zero http proxy
|
||||||
// fn create_http_proxy() {
|
// fn create_http_proxy() {
|
||||||
// info!("creating 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"])
|
// .args(["http.create", "4444"])
|
||||||
// .spawn()
|
// .spawn()
|
||||||
// .expect("i2p-zero failed to create a http proxy");
|
// .expect("i2p-zero failed to create a http proxy");
|
||||||
|
|
|
@ -935,7 +935,7 @@ pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse {
|
||||||
match client.post(host).json(&req).send().await {
|
match client.post(host).json(&req).send().await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let res = response.json::<reqres::XmrDaemonGetInfoResponse>().await;
|
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 {
|
match res {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
_ => Default::default(),
|
_ => Default::default(),
|
||||||
|
@ -949,16 +949,13 @@ pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse {
|
||||||
pub async fn get_height() -> reqres::XmrDaemonGetHeightResponse {
|
pub async fn get_height() -> reqres::XmrDaemonGetHeightResponse {
|
||||||
info!("fetching daemon height");
|
info!("fetching daemon height");
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let host = get_rpc_daemon();
|
let args = args::Args::parse();
|
||||||
let req = reqres::XmrRpcRequest {
|
let daemon = String::from(args.monero_rpc_daemon);
|
||||||
jsonrpc: DaemonFields::Version.value(),
|
let req = format!("{}/{}", daemon, DaemonFields::GetHeight.value());
|
||||||
id: DaemonFields::Id.value(),
|
match client.post(req).send().await {
|
||||||
method: DaemonFields::GetHeight.value(),
|
|
||||||
};
|
|
||||||
match client.post(host).json(&req).send().await {
|
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let res = response.json::<reqres::XmrDaemonGetHeightResponse>().await;
|
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 {
|
match res {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
_ => Default::default(),
|
_ => Default::default(),
|
||||||
|
@ -983,7 +980,7 @@ pub async fn get_block(height: u64) -> reqres::XmrDaemonGetBlockResponse {
|
||||||
match client.post(host).json(&req).send().await {
|
match client.post(host).json(&req).send().await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let res = response.json::<reqres::XmrDaemonGetBlockResponse>().await;
|
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 {
|
match res {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
_ => Default::default(),
|
_ => 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 {
|
pub async fn get_transactions(txs_hashes: Vec<String>) -> reqres::XmrDaemonGetTransactionsResponse {
|
||||||
info!("fetching {} transactions", txs_hashes.len());
|
info!("fetching {} transactions", txs_hashes.len());
|
||||||
let client = reqwest::Client::new();
|
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 {
|
let req = reqres::XmrDaemonGetTransactionsRequest {
|
||||||
txs_hashes,
|
txs_hashes,
|
||||||
decode_as_json: true,
|
decode_as_json: true,
|
||||||
};
|
};
|
||||||
match client.post(host).json(&req).send().await {
|
match client.post(url).json(&req).send().await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let res = response.json::<reqres::XmrDaemonGetTransactionsResponse>().await;
|
let res = response
|
||||||
debug!("{} response: {:?}", DaemonFields::GetTransactions.value(), res);
|
.json::<reqres::XmrDaemonGetTransactionsResponse>()
|
||||||
|
.await;
|
||||||
|
// don't log this one. The fee estimator blows up logs (T_T)
|
||||||
match res {
|
match res {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
_ => Default::default(),
|
_ => Default::default(),
|
||||||
|
|
|
@ -165,7 +165,7 @@ pub struct XmrDaemonGetBlockRequest {
|
||||||
pub jsonrpc: String,
|
pub jsonrpc: String,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub method: String,
|
pub method: String,
|
||||||
pub params: XmrDaemonGetBlockParams,
|
pub params: XmrDaemonGetBlockParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
@ -559,12 +559,15 @@ pub struct XmrDaemonGetBlockResult {
|
||||||
pub blob: String,
|
pub blob: String,
|
||||||
pub block_header: BlockHeader,
|
pub block_header: BlockHeader,
|
||||||
pub credits: u64,
|
pub credits: u64,
|
||||||
pub json: String,
|
pub json: String,
|
||||||
pub miner_tx_hash: String,
|
pub miner_tx_hash: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub top_hash: String,
|
pub top_hash: String,
|
||||||
pub tx_hashes: Vec<String>,
|
/// For some reason this field just disappears on non-
|
||||||
pub untrusted: bool,
|
///
|
||||||
|
/// coinbase transactions instead of being an empty list.
|
||||||
|
pub tx_hashes: Option<Vec<String>>,
|
||||||
|
pub untrusted: bool,
|
||||||
}
|
}
|
||||||
// responses
|
// responses
|
||||||
|
|
||||||
|
@ -655,17 +658,18 @@ impl Default for XmrDaemonGetBlockResponse {
|
||||||
blob: utils::empty_string(),
|
blob: utils::empty_string(),
|
||||||
block_header: Default::default(),
|
block_header: Default::default(),
|
||||||
credits: 0,
|
credits: 0,
|
||||||
json: utils::empty_string(),
|
json: utils::empty_string(),
|
||||||
miner_tx_hash: utils::empty_string(),
|
miner_tx_hash: utils::empty_string(),
|
||||||
status: utils::empty_string(),
|
status: utils::empty_string(),
|
||||||
top_hash: utils::empty_string(),
|
top_hash: utils::empty_string(),
|
||||||
tx_hashes: Vec::new(),
|
tx_hashes: Some(Vec::new()),
|
||||||
untrusted: false,
|
untrusted: false,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only extract the json string. TODO(c2m): map to a struct
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct XmrDaemonGetTransactionsResponse {
|
pub struct XmrDaemonGetTransactionsResponse {
|
||||||
pub txs_as_json: Vec<String>,
|
pub txs_as_json: Vec<String>,
|
||||||
|
|
|
@ -593,31 +593,63 @@ fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool {
|
||||||
actual_hash == expected_hash
|
actual_hash == expected_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The highly ineffecient fee estimator.
|
/// ### The highly ineffecient fee estimator.
|
||||||
///
|
///
|
||||||
/// Get the current height. Start fetching blocks
|
/// Get the current height. Start fetching blocks
|
||||||
///
|
///
|
||||||
/// and checking the number of transactions. If
|
/// and checking the number of transactions. If
|
||||||
///
|
///
|
||||||
/// there were non-coinbase transactions in the block
|
/// there were non-coinbase transactions in the block
|
||||||
///
|
///
|
||||||
/// extract the `txnFee` from the `as_json` field.
|
/// 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.
|
/// average fee paid from the most recent 30 transactions.
|
||||||
///
|
///
|
||||||
/// Note, it may take more than one block to do this,
|
/// Note, it may take more than one block to do this,
|
||||||
///
|
///
|
||||||
/// especially on stagenet.
|
/// especially on stagenet.
|
||||||
pub fn estimate_fee() -> u128 {
|
pub async fn estimate_fee() -> u128 {
|
||||||
|
let mut height: u64 = 0;
|
||||||
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
|
/// Combine the results `estimate_fee()` and `get_balance()` to
|
||||||
///
|
///
|
||||||
/// determine whether or not a transfer is possible.
|
/// determine whether or not a transfer for a given invoice is possible.
|
||||||
pub fn can_transfer() -> bool {
|
pub async fn can_transfer(invoice: u128) -> bool {
|
||||||
false
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,9 @@ pub struct AddressBookApp {
|
||||||
approve_contact: bool,
|
approve_contact: bool,
|
||||||
approve_payment: bool,
|
approve_payment: bool,
|
||||||
added: bool,
|
added: bool,
|
||||||
|
can_transfer: bool,
|
||||||
|
can_transfer_tx: Sender<bool>,
|
||||||
|
can_transfer_rx: Receiver<bool>,
|
||||||
compose: Compose,
|
compose: Compose,
|
||||||
contact: String,
|
contact: String,
|
||||||
find_contact: String,
|
find_contact: String,
|
||||||
|
@ -89,6 +92,7 @@ pub struct AddressBookApp {
|
||||||
invoice_rx: Receiver<reqres::Invoice>,
|
invoice_rx: Receiver<reqres::Invoice>,
|
||||||
is_adding: bool,
|
is_adding: bool,
|
||||||
is_composing: bool,
|
is_composing: bool,
|
||||||
|
is_estimating_fee: bool,
|
||||||
is_pinging: bool,
|
is_pinging: bool,
|
||||||
is_loading: bool,
|
is_loading: bool,
|
||||||
is_message_sent: bool,
|
is_message_sent: bool,
|
||||||
|
@ -107,6 +111,7 @@ pub struct AddressBookApp {
|
||||||
|
|
||||||
impl Default for AddressBookApp {
|
impl Default for AddressBookApp {
|
||||||
fn default() -> Self {
|
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_add_tx, contact_add_rx) = std::sync::mpsc::channel();
|
||||||
let (contact_info_tx, contact_info_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();
|
let (contact_timeout_tx, contact_timeout_rx) = std::sync::mpsc::channel();
|
||||||
|
@ -118,6 +123,9 @@ impl Default for AddressBookApp {
|
||||||
approve_contact: false,
|
approve_contact: false,
|
||||||
approve_payment: false,
|
approve_payment: false,
|
||||||
added: false,
|
added: false,
|
||||||
|
can_transfer: false,
|
||||||
|
can_transfer_rx,
|
||||||
|
can_transfer_tx,
|
||||||
compose: Default::default(),
|
compose: Default::default(),
|
||||||
contact: utils::empty_string(),
|
contact: utils::empty_string(),
|
||||||
contacts: Vec::new(),
|
contacts: Vec::new(),
|
||||||
|
@ -133,6 +141,7 @@ impl Default for AddressBookApp {
|
||||||
invoice_rx,
|
invoice_rx,
|
||||||
is_adding: false,
|
is_adding: false,
|
||||||
is_composing: false,
|
is_composing: false,
|
||||||
|
is_estimating_fee: false,
|
||||||
is_loading: false,
|
is_loading: false,
|
||||||
is_message_sent: false,
|
is_message_sent: false,
|
||||||
is_pinging: false,
|
is_pinging: false,
|
||||||
|
@ -164,6 +173,7 @@ impl eframe::App for AddressBookApp {
|
||||||
self.is_pinging = false;
|
self.is_pinging = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(added_contact) = self.contact_add_rx.try_recv() {
|
if let Ok(added_contact) = self.contact_add_rx.try_recv() {
|
||||||
self.s_added_contact = added_contact;
|
self.s_added_contact = added_contact;
|
||||||
if self.s_added_contact.cid != utils::empty_string() {
|
if self.s_added_contact.cid != utils::empty_string() {
|
||||||
|
@ -171,6 +181,7 @@ impl eframe::App for AddressBookApp {
|
||||||
self.is_loading = false;
|
self.is_loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(timeout) = self.contact_timeout_rx.try_recv() {
|
if let Ok(timeout) = self.contact_timeout_rx.try_recv() {
|
||||||
self.is_timeout = true;
|
self.is_timeout = true;
|
||||||
if timeout {
|
if timeout {
|
||||||
|
@ -180,9 +191,18 @@ impl eframe::App for AddressBookApp {
|
||||||
self.contact = utils::empty_string();
|
self.contact = utils::empty_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(invoice) = self.invoice_rx.try_recv() {
|
if let Ok(invoice) = self.invoice_rx.try_recv() {
|
||||||
self.s_invoice = invoice;
|
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() {
|
if let Ok(payment) = self.payment_rx.try_recv() {
|
||||||
self.is_payment_processed = payment;
|
self.is_payment_processed = payment;
|
||||||
if self.is_payment_processed {
|
if self.is_payment_processed {
|
||||||
|
@ -191,6 +211,7 @@ impl eframe::App for AddressBookApp {
|
||||||
self.showing_status = false;
|
self.showing_status = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(message) = self.send_message_rx.try_recv() {
|
if let Ok(message) = self.send_message_rx.try_recv() {
|
||||||
self.is_message_sent = message;
|
self.is_message_sent = message;
|
||||||
if self.is_message_sent {
|
if self.is_message_sent {
|
||||||
|
@ -199,6 +220,11 @@ impl eframe::App for AddressBookApp {
|
||||||
self.compose.message = utils::empty_string();
|
self.compose.message = utils::empty_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(can_transfer) = self.can_transfer_rx.try_recv() {
|
||||||
|
self.can_transfer = can_transfer;
|
||||||
|
self.is_estimating_fee = false;
|
||||||
|
}
|
||||||
|
|
||||||
// initial contact load
|
// initial contact load
|
||||||
if !self.contacts_init {
|
if !self.contacts_init {
|
||||||
|
@ -258,14 +284,19 @@ impl eframe::App for AddressBookApp {
|
||||||
ui.add(egui::Spinner::new());
|
ui.add(egui::Spinner::new());
|
||||||
ui.label("creating jwp may take a few minutes...");
|
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.heading(self.status.i2p.clone());
|
||||||
ui.label(format!("pay to: {}", address));
|
ui.label(format!("pay to: {}", address));
|
||||||
ui.label(format!("amount: {} piconero(s)", amount));
|
ui.label(format!("amount: {} piconero(s)", amount));
|
||||||
ui.label(format!("expiration: {} blocks", expire));
|
ui.label(format!("expiration: {} blocks", expire));
|
||||||
if !self.is_loading {
|
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() {
|
if ui.button("Approve").clicked() {
|
||||||
// activate xmr "transfer", check the hash, update db and refresh
|
// 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 };
|
let d: reqres::Destination = reqres::Destination { address, amount };
|
||||||
send_payment_req(
|
send_payment_req(
|
||||||
self.payment_tx.clone(),
|
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));
|
clear_gui_db(String::from("gui-nick"), String::from(&contact));
|
||||||
write_gui_db(String::from("gui-nick"), String::from(&contact), nick);
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue