diff --git a/Cargo.lock b/Cargo.lock index 09cb586..d78469b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,7 +635,7 @@ dependencies = [ [[package]] name = "bip300301" version = "0.1.1" -source = "git+https://github.com/Ash-L2L/bip300301.git?rev=1bd864165a8cd1beaa1c7816b845d766ae563a3f#1bd864165a8cd1beaa1c7816b845d766ae563a3f" +source = "git+https://github.com/Ash-L2L/bip300301.git?rev=c6e410e702f3d22f5801f21ffdf39edece3985df#c6e410e702f3d22f5801f21ffdf39edece3985df" dependencies = [ "base64 0.21.7", "bitcoin", @@ -3648,7 +3648,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plain_bitassets" -version = "0.6.0" +version = "0.6.1" dependencies = [ "addr", "anyhow", @@ -3689,7 +3689,7 @@ dependencies = [ [[package]] name = "plain_bitassets_app" -version = "0.6.0" +version = "0.6.1" dependencies = [ "anyhow", "async_zmq", diff --git a/Cargo.toml b/Cargo.toml index 8131912..9499a43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,11 @@ members = [ [workspace.package] authors = [ "Ash Manning " ] edition = "2021" -version = "0.6.0" +version = "0.6.1" [workspace.dependencies.bip300301] git = "https://github.com/Ash-L2L/bip300301.git" -rev = "1bd864165a8cd1beaa1c7816b845d766ae563a3f" +rev = "c6e410e702f3d22f5801f21ffdf39edece3985df" [profile.release] # lto = "fat" \ No newline at end of file diff --git a/app/Cargo.toml b/app/Cargo.toml index a38c2e6..dc21ca7 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -24,6 +24,7 @@ dirs = "5.0.1" eframe = "0.26.2" either = "1.9.0" fraction = { version = "0.14.0", features = ["with-serde-support"] } +futures = "0.3.30" hex = "0.4.3" human-size = "0.4.3" itertools = "0.11.0" diff --git a/app/gui/mod.rs b/app/gui/mod.rs index 15df259..4d1f28a 100644 --- a/app/gui/mod.rs +++ b/app/gui/mod.rs @@ -1,4 +1,5 @@ use eframe::egui::{self, Color32}; +use strum::{EnumIter, IntoEnumIterator}; use crate::{app::App, logs::LogsCapture}; @@ -9,6 +10,7 @@ mod encrypt_message; mod logs; mod lookup; mod miner; +mod parent_chain; mod seed; mod util; @@ -19,27 +21,36 @@ use encrypt_message::EncryptMessage; use logs::Logs; use lookup::Lookup; use miner::Miner; +use parent_chain::ParentChain; use seed::SetSeed; pub struct EguiApp { - app: App, - set_seed: SetSeed, - miner: Miner, - deposit: Deposit, - lookup: Lookup, - tab: Tab, activity: Activity, + app: App, coins: Coins, + deposit: Deposit, encrypt_message: EncryptMessage, logs: Logs, + lookup: Lookup, + miner: Miner, + parent_chain: ParentChain, + set_seed: SetSeed, + tab: Tab, } -#[derive(Eq, PartialEq)] +#[derive(EnumIter, Eq, PartialEq, strum::Display)] enum Tab { + #[strum(to_string = "Coins")] Coins, + #[strum(to_string = "Lookup")] Lookup, + #[strum(to_string = "Messaging")] EncryptMessage, + #[strum(to_string = "Activity")] Activity, + #[strum(to_string = "Parent Chain")] + ParentChain, + #[strum(to_string = "Logs")] Logs, } @@ -70,17 +81,19 @@ impl EguiApp { cc.egui_ctx.set_style(style); let activity = Activity::new(&app); + let parent_chain = ParentChain::new(&app); Self { - app, - set_seed: SetSeed::default(), - miner: Miner::default(), - deposit: Deposit::default(), - lookup: Lookup::default(), - tab: Tab::Coins, activity, + app, coins: Coins::default(), + deposit: Deposit::default(), encrypt_message: EncryptMessage::new(), logs: Logs::new(logs_capture), + lookup: Lookup::default(), + miner: Miner::default(), + parent_chain, + set_seed: SetSeed::default(), + tab: Tab::Coins, } } @@ -123,19 +136,14 @@ impl eframe::App for EguiApp { if self.app.wallet.has_seed().unwrap_or(false) { egui::TopBottomPanel::top("tabs").show(ctx, |ui| { ui.horizontal(|ui| { - ui.selectable_value(&mut self.tab, Tab::Coins, "Coins"); - ui.selectable_value(&mut self.tab, Tab::Lookup, "Lookup"); - ui.selectable_value( - &mut self.tab, - Tab::EncryptMessage, - "Messaging", - ); - ui.selectable_value( - &mut self.tab, - Tab::Activity, - "Activity", - ); - ui.selectable_value(&mut self.tab, Tab::Logs, "Logs"); + Tab::iter().for_each(|tab_variant| { + let tab_name = tab_variant.to_string(); + ui.selectable_value( + &mut self.tab, + tab_variant, + tab_name, + ); + }) }); }); egui::TopBottomPanel::bottom("util") @@ -153,6 +161,9 @@ impl eframe::App for EguiApp { Tab::Activity => { self.activity.show(&mut self.app, ui); } + Tab::ParentChain => { + self.parent_chain.show(&mut self.app, ui); + } Tab::Logs => { self.logs.show(ui); } diff --git a/app/gui/parent_chain.rs b/app/gui/parent_chain.rs new file mode 100644 index 0000000..eb8e13d --- /dev/null +++ b/app/gui/parent_chain.rs @@ -0,0 +1,76 @@ +use bip300301::{bitcoin, MainClient}; +use eframe::egui; +use futures::TryFutureExt; + +use crate::{app::App, gui::util::UiExt}; + +#[derive(Clone, Debug)] +struct ParentChainInfo { + mainchain_tip: bip300301::client::Block, + sidechain_wealth: bitcoin::Amount, +} + +pub struct ParentChain(anyhow::Result); + +impl ParentChain { + fn get_parent_chain_info(app: &App) -> anyhow::Result { + let dc = app.node.drivechain(); + let mainchain_tip = app.runtime.block_on(async { + let mainchain_tip_blockhash = dc.get_mainchain_tip().await?; + dc.client + .getblock(&mainchain_tip_blockhash, None) + .map_err(bip300301::Error::Jsonrpsee) + .await + })?; + let sidechain_wealth = app.node.get_sidechain_wealth()?; + Ok(ParentChainInfo { + mainchain_tip, + sidechain_wealth, + }) + } + + pub fn new(app: &App) -> Self { + let parent_chain_info = Self::get_parent_chain_info(app) + .inspect_err(|err| tracing::error!("{err:#}")); + Self(parent_chain_info) + } + + fn refresh_parent_chain_info(&mut self, app: &App) { + self.0 = 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); + } + let parent_chain_info = match self.0.as_ref() { + Ok(parent_chain_info) => parent_chain_info, + Err(err) => { + ui.monospace_selectable_multiline(format!("{err:#}")); + return; + } + }; + ui.horizontal(|ui| { + ui.monospace("Mainchain tip hash: "); + ui.monospace_selectable_singleline( + true, + parent_chain_info.mainchain_tip.hash.to_string(), + ) + }); + ui.horizontal(|ui| { + ui.monospace("Mainchain tip height: "); + ui.monospace_selectable_singleline( + true, + parent_chain_info.mainchain_tip.height.to_string(), + ) + }); + ui.horizontal(|ui| { + ui.monospace("Sidechain wealth: "); + ui.monospace_selectable_singleline( + false, + parent_chain_info.sidechain_wealth.to_string(), + ) + }); + } +} diff --git a/app/rpc_api.rs b/app/rpc_api.rs index 5d2b483..f40a10c 100644 --- a/app/rpc_api.rs +++ b/app/rpc_api.rs @@ -1,5 +1,6 @@ //! RPC API +use bip300301::bitcoin; use fraction::Fraction; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; @@ -131,6 +132,9 @@ pub trait Rpc { #[method(name = "set_seed_from_mnemonic")] async fn set_seed_from_mnemonic(&self, mnemonic: String) -> RpcResult<()>; + #[method(name = "sidechain_wealth")] + async fn sidechain_wealth(&self) -> RpcResult; + #[method(name = "transfer")] async fn transfer( &self, diff --git a/app/rpc_server.rs b/app/rpc_server.rs index 48df351..84ae96e 100644 --- a/app/rpc_server.rs +++ b/app/rpc_server.rs @@ -1,5 +1,6 @@ use std::{cmp::Ordering, net::SocketAddr}; +use bip300301::bitcoin; use fraction::Fraction; use jsonrpsee::{ core::{async_trait, RpcResult}, @@ -391,6 +392,13 @@ impl RpcServer for RpcServerImpl { .map_err(convert_wallet_err) } + async fn sidechain_wealth(&self) -> RpcResult { + self.app + .node + .get_sidechain_wealth() + .map_err(convert_node_err) + } + async fn transfer( &self, dest: Address, diff --git a/lib/node.rs b/lib/node.rs index ab7e88d..7d6187d 100644 --- a/lib/node.rs +++ b/lib/node.rs @@ -8,6 +8,7 @@ use std::{ #[cfg(all(not(target_os = "windows"), feature = "zmq"))] use async_zmq::SinkExt; +use bip300301::bitcoin; use fraction::Fraction; use heed::RoTxn; use tokio::sync::RwLock; @@ -155,6 +156,23 @@ impl Node { }) } + pub fn drivechain(&self) -> &bip300301::Drivechain { + &self.drivechain + } + + pub async fn get_best_parentchain_hash( + &self, + ) -> Result { + use bip300301::MainClient; + let res = self + .drivechain + .client + .getbestblockhash() + .await + .map_err(bip300301::Error::Jsonrpsee)?; + Ok(res) + } + pub fn get_height(&self) -> Result { let txn = self.env.read_txn()?; Ok(self.archive.get_height(&txn)?) @@ -441,6 +459,12 @@ impl Node { Ok(transactions) } + /// Get total sidechain wealth in Bitcoin + pub fn get_sidechain_wealth(&self) -> Result { + let txn = self.env.read_txn()?; + Ok(self.state.sidechain_wealth(&txn)?) + } + pub fn get_transactions( &self, number: usize, diff --git a/lib/state.rs b/lib/state.rs index c599051..8a9ba51 100644 --- a/lib/state.rs +++ b/lib/state.rs @@ -1911,4 +1911,39 @@ impl State { } Ok(()) } + + /// Get total sidechain wealth in Bitcoin + pub fn sidechain_wealth( + &self, + rotxn: &RoTxn, + ) -> Result { + let mut total_deposit_utxo_value: u64 = 0; + self.utxos.iter(rotxn)?.try_for_each(|utxo| { + let (outpoint, output) = utxo?; + if let OutPoint::Deposit(_) = outpoint { + total_deposit_utxo_value += output.get_bitcoin_value(); + } + Ok::<_, Error>(()) + })?; + let mut total_deposit_stxo_value: u64 = 0; + let mut total_withdrawal_stxo_value: u64 = 0; + self.stxos.iter(rotxn)?.try_for_each(|stxo| { + let (outpoint, spent_output) = stxo?; + if let OutPoint::Deposit(_) = outpoint { + total_deposit_stxo_value += + spent_output.output.get_bitcoin_value(); + } + if let InPoint::Withdrawal { .. } = spent_output.inpoint { + total_withdrawal_stxo_value += + spent_output.output.get_bitcoin_value(); + } + Ok::<_, Error>(()) + })?; + + let total_wealth_sats: u64 = (total_deposit_utxo_value + + total_deposit_stxo_value) + - total_withdrawal_stxo_value; + let total_wealth = BitcoinAmount::from_sat(total_wealth_sats); + Ok(total_wealth) + } }