diff --git a/swap/src/asb/command.rs b/swap/src/asb/command.rs index f22e1500..07e4b7de 100644 --- a/swap/src/asb/command.rs +++ b/swap/src/asb/command.rs @@ -33,13 +33,13 @@ where env_config: env_config(testnet), cmd: Command::Start { resume_only }, }, - RawCommand::History => Arguments { + RawCommand::History { only_unfinished } => Arguments { testnet, json, disable_timestamp, config_path: config_path(config, testnet)?, env_config: env_config(testnet), - cmd: Command::History, + cmd: Command::History { only_unfinished }, }, RawCommand::WithdrawBtc { amount, address } => Arguments { testnet, @@ -195,7 +195,9 @@ pub enum Command { Start { resume_only: bool, }, - History, + History { + only_unfinished: bool, + }, Config, WithdrawBtc { amount: Option, @@ -269,7 +271,13 @@ pub enum RawCommand { resume_only: bool, }, #[structopt(about = "Prints swap-id and the state of each swap ever made.")] - History, + History { + #[structopt( + long = "only-unfinished", + help = "If set, only unfinished swaps will be printed." + )] + only_unfinished: bool, + }, #[structopt(about = "Prints the current config")] Config, #[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")] diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index ff3b0e4c..33d30709 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -18,6 +18,8 @@ use libp2p::core::multiaddr::Protocol; use libp2p::core::Multiaddr; use libp2p::swarm::AddressScore; use libp2p::Swarm; +use rust_decimal::prelude::FromPrimitive; +use rust_decimal::Decimal; use std::convert::TryInto; use std::env; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -33,7 +35,9 @@ use swap::common::check_latest_version; use swap::database::open_db; use swap::network::rendezvous::XmrBtcNamespace; use swap::network::swarm; +use swap::protocol::alice::swap::is_complete; use swap::protocol::alice::{run, AliceState}; +use swap::protocol::State; use swap::seed::Seed; use swap::tor::AuthenticatedClient; use swap::{asb, bitcoin, kraken, monero, tor}; @@ -224,17 +228,81 @@ async fn main() -> Result<()> { event_loop.run().await; } - Command::History => { + Command::History { only_unfinished } => { let mut table = Table::new(); + table.set_header(vec![ + "SWAP ID", + "START DATETIME", + "CURRENT STATE", + "BTC AMOUNT", + "XMR AMOUNT", + "EXCHANGE RATE", + "TRADING PARTNER PEER ID", + ]); - table.set_header(vec!["SWAP ID", "STATE"]); + let all_swaps = db.all().await?; + for (swap_id, state) in all_swaps { + if let Err(e) = async { + let latest_state: AliceState = state.try_into()?; - for (swap_id, state) in db.all().await? { - let state: AliceState = state.try_into()?; - table.add_row(vec![swap_id.to_string(), state.to_string()]); + if only_unfinished && is_complete(&latest_state) { + () // Skip finished swaps + } + + let all_states = db.get_states(swap_id).await?; + let state3 = all_states + .iter() + .find_map(|s| match s { + State::Alice(AliceState::BtcLockTransactionSeen { state3 }) => { + Some(state3) + } + _ => None, + }) + .context("Failed to get \"BtcLockTransactionSeen\" state")?; + + let swap_start_date = db.get_swap_start_date(swap_id).await?; + let peer_id = db.get_peer_id(swap_id).await?; + + let exchange_rate = format!( + "{} XMR/BTC", + (Decimal::from_f64(state3.btc.to_btc()).unwrap() / state3.xmr.as_xmr()) + .round_dp(8) + ); + + if json { + tracing::info!( + swap_id = %swap_id, + swap_start_date = %swap_start_date, + latest_state = %latest_state, + btc_amount = %state3.btc, + xmr_amount = %state3.xmr, + exchange_rate = %exchange_rate, + trading_partner_peer_id = %peer_id, + "Found swap in database" + ); + } else { + table.add_row(vec![ + swap_id.to_string(), + swap_start_date.to_string(), + latest_state.to_string(), + state3.btc.to_string(), + state3.xmr.to_string(), + exchange_rate, + peer_id.to_string(), + ]); + } + + Ok::<_, anyhow::Error>(()) + } + .await + { + tracing::error!(swap_id = %swap_id, "{:#}", e); + } } - println!("{}", table); + if !json { + println!("{}", table); + } } Command::Config => { let config_json = serde_json::to_string_pretty(&config)?; diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 8205e75f..e5dd9e80 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -142,6 +142,14 @@ impl Amount { Decimal::from(self.as_piconero()) } + pub fn as_xmr(&self) -> Decimal { + let mut decimal = Decimal::from(self.0); + decimal + .set_scale(12) + .expect("12 is smaller than max precision of 28"); + decimal + } + fn from_decimal(amount: Decimal) -> Result { let piconeros_dec = amount.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64")); @@ -184,11 +192,8 @@ impl From for u64 { impl fmt::Display for Amount { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut decimal = Decimal::from(self.0); - decimal - .set_scale(12) - .expect("12 is smaller than max precision of 28"); - write!(f, "{} XMR", decimal) + let xmr_value = self.as_xmr(); + write!(f, "{} XMR", xmr_value) } } diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index f0acab23..7627a529 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -384,8 +384,8 @@ pub struct State3 { S_b_bitcoin: bitcoin::PublicKey, pub v: monero::PrivateViewKey, #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - btc: bitcoin::Amount, - xmr: monero::Amount, + pub btc: bitcoin::Amount, + pub xmr: monero::Amount, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, refund_address: bitcoin::Address, diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 79236563..d4625ed3 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -397,7 +397,7 @@ where }) } -pub(crate) fn is_complete(state: &AliceState) -> bool { +pub fn is_complete(state: &AliceState) -> bool { matches!( state, AliceState::XmrRefunded