From 6e7a43316cb5f997fef6aabdcea623c8f45e9f16 Mon Sep 17 00:00:00 2001 From: Ash Manning Date: Mon, 25 Nov 2024 20:59:53 +0800 Subject: [PATCH] start gui even if app fails to start --- app/gui/block_explorer.rs | 15 ++-- app/gui/coins/mod.rs | 4 +- app/gui/coins/transfer_receive.rs | 38 +++++---- app/gui/coins/tx_builder.rs | 7 +- app/gui/coins/tx_creator.rs | 11 ++- app/gui/coins/utxo_creator.rs | 23 ++++-- app/gui/coins/utxo_selector.rs | 26 ++++--- app/gui/console_logs.rs | 7 +- app/gui/mempool_explorer.rs | 10 ++- app/gui/miner.rs | 16 ++-- app/gui/mod.rs | 125 +++++++++++++++++++----------- app/gui/parent_chain/info.rs | 32 +++++--- app/gui/parent_chain/mod.rs | 4 +- app/gui/parent_chain/transfer.rs | 23 +++--- app/gui/withdrawals.rs | 6 +- app/main.rs | 25 ++++-- 16 files changed, 239 insertions(+), 133 deletions(-) diff --git a/app/gui/block_explorer.rs b/app/gui/block_explorer.rs index d119226..9c0a082 100644 --- a/app/gui/block_explorer.rs +++ b/app/gui/block_explorer.rs @@ -19,11 +19,13 @@ impl BlockExplorer { Self { height } } - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { - let max_height = app.node.get_height().unwrap_or(0); + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { + let max_height = + app.and_then(|app| app.node.get_height().ok()).unwrap_or(0); let block: Option<(Header, Body)> = { - if let Ok(Some(block_hash)) = - app.node.try_get_block_hash(self.height) + if let Some(app) = app + && let Ok(Some(block_hash)) = + app.node.try_get_block_hash(self.height) && let Ok(header) = app.node.get_header(block_hash) && let Ok(body) = app.node.get_body(block_hash) { @@ -97,8 +99,9 @@ impl BlockExplorer { let body_sigops_limit = State::body_sigops_limit(self.height); ui.monospace(format!("Body sigops limit: {body_sigops_limit}")); - if let Ok(Some(acc)) = - app.node.try_get_accumulator(header.hash()) + if let Some(app) = app + && let Ok(Some(acc)) = + app.node.try_get_accumulator(header.hash()) { ui.monospace_selectable_multiline(format!( "Utreexo accumulator: \n{}", diff --git a/app/gui/coins/mod.rs b/app/gui/coins/mod.rs index e29982d..c31c525 100644 --- a/app/gui/coins/mod.rs +++ b/app/gui/coins/mod.rs @@ -28,7 +28,7 @@ pub struct Coins { } impl Coins { - pub fn new(app: &App) -> Self { + pub fn new(app: Option<&App>) -> Self { Self { transfer_receive: TransferReceive::new(app), tab: Tab::default(), @@ -36,7 +36,7 @@ impl Coins { } } - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { egui::TopBottomPanel::top("coins_tabs").show(ui.ctx(), |ui| { ui.horizontal(|ui| { Tab::iter().for_each(|tab_variant| { diff --git a/app/gui/coins/transfer_receive.rs b/app/gui/coins/transfer_receive.rs index bc39b94..ddd92f6 100644 --- a/app/gui/coins/transfer_receive.rs +++ b/app/gui/coins/transfer_receive.rs @@ -1,4 +1,4 @@ -use eframe::egui; +use eframe::egui::{self, Button}; use thunder::types::Address; use crate::{app::App, gui::util::UiExt}; @@ -25,7 +25,7 @@ fn create_transfer( } impl Transfer { - fn show(&mut self, app: &App, ui: &mut egui::Ui) { + fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { ui.add_sized((250., 10.), |ui: &mut egui::Ui| { ui.horizontal(|ui| { let dest_edit = egui::TextEdit::singleline(&mut self.dest) @@ -66,13 +66,16 @@ impl Transfer { ); if ui .add_enabled( - dest.is_some() && amount.is_ok() && fee.is_ok(), + app.is_some() + && dest.is_some() + && amount.is_ok() + && fee.is_ok(), egui::Button::new("transfer"), ) .clicked() { if let Err(err) = create_transfer( - app, + app.unwrap(), dest.expect("should not happen"), amount.expect("should not happen"), fee.expect("should not happen"), @@ -87,29 +90,38 @@ impl Transfer { #[derive(Debug)] struct Receive { - address: anyhow::Result
, + address: Option>, } impl Receive { - fn new(app: &App) -> Self { + fn new(app: Option<&App>) -> Self { + let Some(app) = app else { + return Self { address: None }; + }; let address = app .wallet .get_new_address() .map_err(anyhow::Error::from) .inspect_err(|err| tracing::error!("{err:#}")); - Self { address } + Self { + address: Some(address), + } } - fn show(&mut self, app: &App, ui: &mut egui::Ui) { + fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { match &self.address { - Ok(address) => { + Some(Ok(address)) => { ui.monospace_selectable_singleline(false, address.to_string()); } - Err(err) => { + Some(Err(err)) => { ui.monospace_selectable_multiline(format!("{err:#}")); } + None => (), } - if ui.button("generate").clicked() { + if ui + .add_enabled(app.is_some(), Button::new("generate")) + .clicked() + { *self = Self::new(app) } } @@ -122,14 +134,14 @@ pub(super) struct TransferReceive { } impl TransferReceive { - pub fn new(app: &App) -> Self { + pub fn new(app: Option<&App>) -> Self { Self { transfer: Transfer::default(), receive: Receive::new(app), } } - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { egui::SidePanel::left("transfer") .exact_width(ui.available_width() / 2.) .resizable(false) diff --git a/app/gui/coins/tx_builder.rs b/app/gui/coins/tx_builder.rs index 2dd9bb8..1aad177 100644 --- a/app/gui/coins/tx_builder.rs +++ b/app/gui/coins/tx_builder.rs @@ -21,8 +21,11 @@ pub struct TxBuilder { } impl TxBuilder { - pub fn show_value_in(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show_value_in(&mut self, app: Option<&App>, ui: &mut egui::Ui) { ui.heading("Value In"); + let Some(app) = app else { + return; + }; let selected: HashSet<_> = self .base_tx .inputs @@ -102,7 +105,7 @@ impl TxBuilder { pub fn show( &mut self, - app: &mut App, + app: Option<&App>, ui: &mut egui::Ui, ) -> anyhow::Result<()> { egui::SidePanel::left("spend_utxo") diff --git a/app/gui/coins/tx_creator.rs b/app/gui/coins/tx_creator.rs index 6bac416..60e3644 100644 --- a/app/gui/coins/tx_creator.rs +++ b/app/gui/coins/tx_creator.rs @@ -1,4 +1,4 @@ -use eframe::egui; +use eframe::egui::{self, Button}; use thunder::types::{Transaction, Txid}; @@ -22,7 +22,7 @@ fn send_tx(app: &App, tx: &mut Transaction) -> anyhow::Result<()> { impl TxCreator { pub fn show( &mut self, - app: &mut App, + app: Option<&App>, ui: &mut egui::Ui, base_tx: &mut Transaction, ) -> anyhow::Result<()> { @@ -48,8 +48,11 @@ impl TxCreator { if self.value_in >= self.value_out { let fee = self.value_in - self.value_out; ui.monospace(format!("fee: {fee}")); - if ui.button("sign and send").clicked() { - if let Err(err) = send_tx(app, final_tx) { + if ui + .add_enabled(app.is_some(), Button::new("sign and send")) + .clicked() + { + if let Err(err) = send_tx(app.unwrap(), final_tx) { tracing::error!("{err:#}"); } else { *base_tx = Transaction::default(); diff --git a/app/gui/coins/utxo_creator.rs b/app/gui/coins/utxo_creator.rs index 2c7dc53..42cf70f 100644 --- a/app/gui/coins/utxo_creator.rs +++ b/app/gui/coins/utxo_creator.rs @@ -1,4 +1,4 @@ -use eframe::egui; +use eframe::egui::{self, Button}; use thunder::types::{self, Output, OutputContent, Transaction}; use crate::app::App; @@ -42,7 +42,7 @@ impl Default for UtxoCreator { impl UtxoCreator { pub fn show( &mut self, - app: &mut App, + app: Option<&App>, ui: &mut egui::Ui, tx: &mut Transaction, ) { @@ -73,8 +73,12 @@ impl UtxoCreator { ui.horizontal(|ui| { ui.monospace("Address: "); ui.add(egui::TextEdit::singleline(&mut self.address)); - if ui.button("generate").clicked() { + if ui + .add_enabled(app.is_some(), Button::new("generate")) + .clicked() + { self.address = app + .unwrap() .wallet .get_new_address() .map(|address| format!("{address}")) @@ -85,8 +89,11 @@ impl UtxoCreator { ui.horizontal(|ui| { ui.monospace("Main Address:"); ui.add(egui::TextEdit::singleline(&mut self.main_address)); - if ui.button("generate").clicked() { - match app.get_new_main_address() { + if ui + .add_enabled(app.is_some(), Button::new("generate")) + .clicked() + { + match app.unwrap().get_new_main_address() { Ok(main_address) => { self.main_address = format!("{main_address}"); } @@ -171,8 +178,10 @@ impl UtxoCreator { } } } - let num_addresses = app.wallet.get_num_addresses().unwrap(); - ui.label(format!("{num_addresses} addresses generated")); + if let Some(app) = app { + let num_addresses = app.wallet.get_num_addresses().unwrap(); + ui.label(format!("{num_addresses} addresses generated")); + } }); } } diff --git a/app/gui/coins/utxo_selector.rs b/app/gui/coins/utxo_selector.rs index 1a07518..e4c1240 100644 --- a/app/gui/coins/utxo_selector.rs +++ b/app/gui/coins/utxo_selector.rs @@ -13,22 +13,28 @@ pub struct UtxoSelector; impl UtxoSelector { pub fn show( &mut self, - app: &mut App, + app: Option<&App>, ui: &mut egui::Ui, tx: &mut Transaction, ) { ui.heading("Spend UTXO"); let selected: HashSet<_> = tx.inputs.iter().map(|(outpoint, _)| *outpoint).collect(); - let utxos_read = app.utxos.read(); - let total: bitcoin::Amount = utxos_read - .iter() - .filter(|(outpoint, _)| !selected.contains(outpoint)) - .map(|(_, output)| output.get_value()) - .sum(); - let mut utxos: Vec<_> = (*utxos_read).clone().into_iter().collect(); - drop(utxos_read); - utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}")); + let (total, utxos): (bitcoin::Amount, Vec<_>) = app + .map(|app| { + let utxos_read = app.utxos.read(); + let total: bitcoin::Amount = utxos_read + .iter() + .filter(|(outpoint, _)| !selected.contains(outpoint)) + .map(|(_, output)| output.get_value()) + .sum(); + let mut utxos: Vec<_> = + (*utxos_read).clone().into_iter().collect(); + drop(utxos_read); + utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}")); + (total, utxos) + }) + .unwrap_or_default(); ui.separator(); ui.monospace(format!("Total: {}", total)); ui.separator(); diff --git a/app/gui/console_logs.rs b/app/gui/console_logs.rs index 14aadec..78c65fc 100644 --- a/app/gui/console_logs.rs +++ b/app/gui/console_logs.rs @@ -89,7 +89,7 @@ impl ConsoleLogs { }); } - pub fn show(&mut self, app: &App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| { let line_buffer_read = self.line_buffer.as_str(); let mut logs: &str = &line_buffer_read; @@ -106,7 +106,8 @@ impl ConsoleLogs { .hint_text("help") .return_key(SHIFT_ENTER); let command_input_resp = ui.add_enabled( - !self.running_command.load(atomic::Ordering::SeqCst), + app.is_some() + && !self.running_command.load(atomic::Ordering::SeqCst), command_input, ); if command_input_resp.ctx.input_mut(|input| { @@ -114,7 +115,7 @@ impl ConsoleLogs { && input.consume_key(Modifiers::NONE, Key::Enter) && !self.running_command.load(atomic::Ordering::SeqCst) }) { - self.console_command(app); + self.console_command(app.unwrap()); } }); } diff --git a/app/gui/mempool_explorer.rs b/app/gui/mempool_explorer.rs index 9fad396..50444fa 100644 --- a/app/gui/mempool_explorer.rs +++ b/app/gui/mempool_explorer.rs @@ -10,9 +10,13 @@ pub struct MemPoolExplorer { } impl MemPoolExplorer { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { - let transactions = app.node.get_all_transactions().unwrap_or_default(); - let utxos = app.wallet.get_utxos().unwrap_or_default(); + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { + let transactions = app + .and_then(|app| app.node.get_all_transactions().ok()) + .unwrap_or_default(); + let utxos = app + .and_then(|app| app.wallet.get_utxos().ok()) + .unwrap_or_default(); egui::SidePanel::left("transaction_picker") .resizable(false) .show_inside(ui, |ui| { diff --git a/app/gui/miner.rs b/app/gui/miner.rs index 08def35..650cfcc 100644 --- a/app/gui/miner.rs +++ b/app/gui/miner.rs @@ -21,18 +21,22 @@ impl Default for Miner { } impl Miner { - pub fn show(&mut self, app: &App, ui: &mut egui::Ui) { - let block_height = app.node.get_height().unwrap_or(0); - let best_hash = app.node.get_best_hash().unwrap_or([0; 32].into()); + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { + let block_height = + app.and_then(|app| app.node.get_height().ok()).unwrap_or(0); + let best_hash = app + .and_then(|app| app.node.get_best_hash().ok()) + .unwrap_or([0; 32].into()); ui.label("Block height: "); ui.monospace(format!("{block_height}")); ui.label("Best hash: "); let best_hash = &format!("{best_hash}")[0..8]; ui.monospace(format!("{best_hash}...")); let running = self.running.load(atomic::Ordering::SeqCst); - if ui - .add_enabled(!running, Button::new("Mine / Refresh Block")) - .clicked() + if let Some(app) = app + && ui + .add_enabled(!running, Button::new("Mine / Refresh Block")) + .clicked() { self.running.store(true, atomic::Ordering::SeqCst); app.local_pool.spawn_pinned({ diff --git a/app/gui/mod.rs b/app/gui/mod.rs index 6df5dbc..c3fbbe4 100644 --- a/app/gui/mod.rs +++ b/app/gui/mod.rs @@ -31,7 +31,7 @@ use withdrawals::Withdrawals; use self::util::UiExt; pub struct EguiApp { - app: App, + app: Option, block_explorer: BlockExplorer, bottom_panel: BottomPanel, coins: Coins, @@ -61,8 +61,29 @@ enum Tab { ConsoleLogs, } -struct BottomPanel { +/// Bottom panel, if initialized +struct BottomPanelInitialized { + app: App, wallet_updated: PromiseStream<>::WatchStream>, +} + +impl BottomPanelInitialized { + fn new(app: App) -> Self { + let wallet_updated = { + let rt_guard = app.runtime.enter(); + let wallet_updated = PromiseStream::from(app.wallet.watch()); + drop(rt_guard); + wallet_updated + }; + Self { + app, + wallet_updated, + } + } +} + +struct BottomPanel { + initialized: Option, /// None if uninitialized /// Some(None) if failed to initialize balance: Option>, @@ -70,24 +91,34 @@ struct BottomPanel { impl BottomPanel { /// MUST be run from within a tokio runtime - fn new(wallet: &Wallet) -> Self { - let wallet_updated = PromiseStream::from(wallet.watch()); + fn new(app: Option) -> Self { + let initialized = app.map(BottomPanelInitialized::new); Self { - wallet_updated, + initialized, balance: None, } } - /// Updates values - fn update(&mut self, app: &App) { - self.balance = match app.wallet.get_balance() { - Ok(balance) => Some(Some(balance.total)), - Err(err) => { - let err = anyhow::Error::from(err); - tracing::error!("Failed to update balance: {err:#}"); - Some(None) + /// Updates values if the wallet has been updated + fn update(&mut self) { + let Some(initialized) = &mut self.initialized else { + return; + }; + let rt_guard = initialized.app.runtime.enter(); + match initialized.wallet_updated.poll_next() { + Some(Poll::Ready(())) => { + self.balance = match initialized.app.wallet.get_balance() { + Ok(balance) => Some(Some(balance.total)), + Err(err) => { + let err = anyhow::Error::from(err); + tracing::error!("Failed to update balance: {err:#}"); + Some(None) + } + } } + Some(Poll::Pending) | None => (), } + drop(rt_guard) } fn show_balance(&self, ui: &mut egui::Ui) { @@ -114,16 +145,9 @@ impl BottomPanel { } } - fn show(&mut self, app: &App, miner: &mut Miner, ui: &mut egui::Ui) { + fn show(&mut self, miner: &mut Miner, ui: &mut egui::Ui) { ui.horizontal(|ui| { - let rt_guard = app.runtime.enter(); - match self.wallet_updated.poll_next() { - Some(Poll::Ready(())) => { - self.update(app); - } - Some(Poll::Pending) | None => (), - } - drop(rt_guard); + self.update(); self.show_balance(ui); // Fill center space, // see https://github.com/emilk/egui/discussions/3908#discussioncomment-8270353 @@ -142,7 +166,12 @@ impl BottomPanel { ui.add_space(this_target_width); ui.separator(); - miner.show(app, ui); + miner.show( + self.initialized + .as_ref() + .map(|initialized| &initialized.app), + ui, + ); // this frame others width // == this frame final min rect width - this frame target width ui.data_mut(|data| { @@ -157,7 +186,7 @@ impl BottomPanel { impl EguiApp { pub fn new( - app: App, + app: Option, cc: &eframe::CreationContext<'_>, logs_capture: LineBuffer, rpc_addr: SocketAddr, @@ -167,13 +196,14 @@ impl EguiApp { // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use // for e.g. egui::PaintCallback. cc.egui_ctx.set_fonts(FONT_DEFINITIONS.clone()); - let rt_guard = app.runtime.enter(); - let bottom_panel = BottomPanel::new(&app.wallet); - drop(rt_guard); - let coins = Coins::new(&app); + let bottom_panel = BottomPanel::new(app.clone()); + let coins = Coins::new(app.as_ref()); let console_logs = ConsoleLogs::new(logs_capture, rpc_addr); - let height = app.node.get_height().unwrap_or(0); - let parent_chain = ParentChain::new(&app); + let height = app + .as_ref() + .and_then(|app| app.node.get_height().ok()) + .unwrap_or(0); + let parent_chain = ParentChain::new(app.as_ref()); Self { app, block_explorer: BlockExplorer::new(height), @@ -192,7 +222,15 @@ impl EguiApp { impl eframe::App for EguiApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - if self.app.wallet.has_seed().unwrap_or(false) { + if let Some(app) = self.app.as_ref() + && !app.wallet.has_seed().unwrap_or(false) + { + egui::CentralPanel::default().show(ctx, |_ui| { + egui::Window::new("Set Seed").show(ctx, |ui| { + self.set_seed.show(app, ui); + }); + }); + } else { egui::TopBottomPanel::top("tabs").show(ctx, |ui| { ui.horizontal(|ui| { Tab::iter().for_each(|tab_variant| { @@ -205,33 +243,28 @@ impl eframe::App for EguiApp { }) }); }); - egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { - self.bottom_panel.show(&self.app, &mut self.miner, ui) - }); + egui::TopBottomPanel::bottom("bottom_panel") + .show(ctx, |ui| self.bottom_panel.show(&mut self.miner, ui)); egui::CentralPanel::default().show(ctx, |ui| match self.tab { - Tab::ParentChain => self.parent_chain.show(&mut self.app, ui), + Tab::ParentChain => { + self.parent_chain.show(self.app.as_ref(), ui) + } Tab::Coins => { - self.coins.show(&mut self.app, ui); + self.coins.show(self.app.as_ref(), ui); } Tab::MemPoolExplorer => { - self.mempool_explorer.show(&mut self.app, ui); + self.mempool_explorer.show(self.app.as_ref(), ui); } Tab::BlockExplorer => { - self.block_explorer.show(&mut self.app, ui); + self.block_explorer.show(self.app.as_ref(), ui); } Tab::Withdrawals => { - self.withdrawals.show(&mut self.app, ui); + self.withdrawals.show(self.app.as_ref(), ui); } Tab::ConsoleLogs => { - self.console_logs.show(&self.app, ui); + self.console_logs.show(self.app.as_ref(), ui); } }); - } else { - egui::CentralPanel::default().show(ctx, |_ui| { - egui::Window::new("Set Seed").show(ctx, |ui| { - self.set_seed.show(&self.app, ui); - }); - }); } } } diff --git a/app/gui/parent_chain/info.rs b/app/gui/parent_chain/info.rs index 85d8f38..e794c77 100644 --- a/app/gui/parent_chain/info.rs +++ b/app/gui/parent_chain/info.rs @@ -1,4 +1,4 @@ -use eframe::egui; +use eframe::egui::{self, Button}; use futures::FutureExt; use thunder::types::proto::mainchain; @@ -10,7 +10,7 @@ struct Inner { sidechain_wealth: bitcoin::Amount, } -pub(super) struct Info(anyhow::Result); +pub(super) struct Info(Option>); impl Info { fn get_parent_chain_info(app: &App) -> anyhow::Result { @@ -25,27 +25,35 @@ impl Info { }) } - pub fn new(app: &App) -> Self { - let inner = Self::get_parent_chain_info(app) - .inspect_err(|err| tracing::error!("{err:#}")); + pub fn new(app: Option<&App>) -> Self { + let inner = app.map(|app| { + Self::get_parent_chain_info(app) + .inspect_err(|err| tracing::error!("{err:#}")) + }); Self(inner) } fn refresh_parent_chain_info(&mut self, app: &App) { - self.0 = Self::get_parent_chain_info(app) - .inspect_err(|err| tracing::error!("{err:#}")); + self.0 = Some( + Self::get_parent_chain_info(app) + .inspect_err(|err| tracing::error!("{err:#}")), + ); } - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { - if ui.button("Refresh").clicked() { - let () = self.refresh_parent_chain_info(app); + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { + if ui + .add_enabled(app.is_some(), Button::new("Refresh")) + .clicked() + { + let () = self.refresh_parent_chain_info(app.unwrap()); } let parent_chain_info = match self.0.as_ref() { - Ok(parent_chain_info) => parent_chain_info, - Err(err) => { + Some(Ok(parent_chain_info)) => parent_chain_info, + Some(Err(err)) => { ui.monospace_selectable_multiline(format!("{err:#}")); return; } + None => return, }; ui.horizontal(|ui| { ui.monospace("Mainchain tip hash: "); diff --git a/app/gui/parent_chain/mod.rs b/app/gui/parent_chain/mod.rs index 2adc3b3..29f65bc 100644 --- a/app/gui/parent_chain/mod.rs +++ b/app/gui/parent_chain/mod.rs @@ -25,7 +25,7 @@ pub struct ParentChain { } impl ParentChain { - pub fn new(app: &App) -> Self { + pub fn new(app: Option<&App>) -> Self { let info = Info::new(app); Self { info, @@ -34,7 +34,7 @@ impl ParentChain { } } - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { egui::TopBottomPanel::top("parent_chain_tabs").show(ui.ctx(), |ui| { ui.horizontal(|ui| { Tab::iter().for_each(|tab_variant| { diff --git a/app/gui/parent_chain/transfer.rs b/app/gui/parent_chain/transfer.rs index 9b5823e..f64ee66 100644 --- a/app/gui/parent_chain/transfer.rs +++ b/app/gui/parent_chain/transfer.rs @@ -1,4 +1,4 @@ -use eframe::egui; +use eframe::egui::{self, Button}; use crate::app::App; @@ -9,7 +9,7 @@ pub struct Deposit { } impl Deposit { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { ui.add_sized((110., 10.), |ui: &mut egui::Ui| { ui.horizontal(|ui| { let amount_edit = egui::TextEdit::singleline(&mut self.amount) @@ -41,11 +41,12 @@ impl Deposit { if ui .add_enabled( - amount.is_ok() && fee.is_ok(), + app.is_some() && amount.is_ok() && fee.is_ok(), egui::Button::new("deposit"), ) .clicked() { + let app = app.unwrap(); if let Err(err) = app.deposit( app.wallet.get_new_address().expect("should not happen"), amount.expect("should not happen"), @@ -87,7 +88,7 @@ fn create_withdrawal( } impl Withdrawal { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { ui.add_sized((250., 10.), |ui: &mut egui::Ui| { ui.horizontal(|ui| { let mainchain_address_edit = @@ -95,8 +96,11 @@ impl Withdrawal { .hint_text("mainchain address") .desired_width(150.); ui.add(mainchain_address_edit); - if ui.button("generate").clicked() { - match app.get_new_main_address() { + if ui + .add_enabled(app.is_some(), Button::new("generate")) + .clicked() + { + match app.unwrap().get_new_main_address() { Ok(main_address) => { self.mainchain_address = main_address.to_string(); } @@ -158,7 +162,8 @@ impl Withdrawal { if ui .add_enabled( - mainchain_address.is_some() + app.is_some() + && mainchain_address.is_some() && amount.is_ok() && fee.is_ok() && mainchain_fee.is_ok(), @@ -167,7 +172,7 @@ impl Withdrawal { .clicked() { if let Err(err) = create_withdrawal( - app, + app.unwrap(), mainchain_address.expect("should not happen"), amount.expect("should not happen"), fee.expect("should not happen"), @@ -188,7 +193,7 @@ pub(super) struct Transfer { } impl Transfer { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { egui::SidePanel::left("deposit") .exact_width(ui.available_width() / 2.) .resizable(false) diff --git a/app/gui/withdrawals.rs b/app/gui/withdrawals.rs index 004fc93..5ff9979 100644 --- a/app/gui/withdrawals.rs +++ b/app/gui/withdrawals.rs @@ -7,9 +7,11 @@ use crate::app::App; pub struct Withdrawals {} impl Withdrawals { - pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { + pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) { ui.heading("Pending withdrawals"); - let bundle = app.node.get_pending_withdrawal_bundle().ok().flatten(); + let bundle = app.and_then(|app| { + app.node.get_pending_withdrawal_bundle().ok().flatten() + }); if let Some(bundle) = bundle { let mut spent_utxos: Vec<_> = bundle.spend_utxos().iter().collect(); spent_utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}")); diff --git a/app/main.rs b/app/main.rs index 3ab4ea4..a68343d 100644 --- a/app/main.rs +++ b/app/main.rs @@ -93,13 +93,18 @@ fn main() -> anyhow::Result<()> { let config = cli.get_config()?; let (line_buffer, _rolling_log_guard) = set_tracing_subscriber(config.log_dir.as_deref(), config.log_level)?; - let app = app::App::new(&config)?; - // spawn rpc server - app.runtime.spawn({ - let app = app.clone(); - async move { rpc_server::run_server(app, config.rpc_addr).await.unwrap() } - }); + let app: Result = + app::App::new(&config).inspect(|app| { + // spawn rpc server + app.runtime.spawn({ + let app = app.clone(); + async move { + rpc_server::run_server(app, config.rpc_addr).await.unwrap() + } + }); + }); if config.headless { + let _app = app?; // wait for ctrlc signal let (tx, rx) = mpsc::channel(); ctrlc::set_handler(move || { @@ -110,6 +115,14 @@ fn main() -> anyhow::Result<()> { println!("Received Ctrl-C signal, exiting..."); } else { let native_options = eframe::NativeOptions::default(); + let app: Option<_> = app.map_or_else( + |err| { + let err = anyhow::Error::from(err); + tracing::error!("{err:#}"); + None + }, + Some, + ); eframe::run_native( "Thunder", native_options,