Skip to content

Commit

Permalink
feat(asb): Enhance history command with extra information
Browse files Browse the repository at this point in the history
  • Loading branch information
binarybaron committed Nov 29, 2024
1 parent 22878c8 commit 422ecfe
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 44 deletions.
160 changes: 119 additions & 41 deletions swap/src/bin/asb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use comfy_table::Table;
use libp2p::core::multiaddr::Protocol;
use libp2p::core::Multiaddr;
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};
Expand All @@ -33,11 +35,14 @@ use swap::common::{self, get_logs, warn_if_outdated};
use swap::database::{open_db, AccessMode};
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::{Database, State};
use swap::seed::Seed;
use swap::tor::AuthenticatedClient;
use swap::{bitcoin, kraken, monero, tor};
use tracing_subscriber::filter::LevelFilter;
use uuid::Uuid;

const DEFAULT_WALLET_NAME: &str = "asb-wallet";

Expand Down Expand Up @@ -228,17 +233,39 @@ pub async fn main() -> Result<()> {
}
Command::History => {
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadOnly, None).await?;

let mut table = Table::new();

table.set_header(vec!["SWAP ID", "STATE"]);

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()]);
table.set_header(vec![
"Swap ID",
"Start Date",
"State",
"Bitcoin Lock TxId",
"BTC Amount",
"XMR Amount",
"Exchange Rate",
"Trading Partner Peer ID",
"Completed",
]);

let all_swaps = db.all().await?;
for (swap_id, state) in all_swaps {
match SwapDetails::from_db_state(swap_id.clone(), state, &db).await {
Ok(details) => {
if json {
details.log_info();
} else {
table.add_row(details.to_table_row());
}
}
Err(e) => {
tracing::error!(swap_id = %swap_id, error = %e, "Failed to get swap details");
}
}
}

println!("{}", table);
if !json {
println!("{}", table);
}
}
Command::Config => {
let config_json = serde_json::to_string_pretty(&config)?;
Expand Down Expand Up @@ -392,45 +419,96 @@ async fn init_monero_wallet(
Ok(wallet)
}

/// Registers a hidden service for each network.
/// Note: Once ac goes out of scope, the services will be de-registered.
async fn register_tor_services(
networks: Vec<Multiaddr>,
tor_client: tor::Client,
seed: &Seed,
) -> Result<AuthenticatedClient> {
let mut ac = tor_client.into_authenticated_client().await?;

let hidden_services_details = networks
.iter()
.flat_map(|network| {
network.iter().map(|protocol| match protocol {
Protocol::Tcp(port) => Some((
port,
SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), port),
)),
_ => {
// We only care for Tcp for now.
None
}
/// This struct is used to extract swap details from the database and print them in a table format
#[derive(Debug)]
struct SwapDetails {
swap_id: String,
start_date: String,
state: String,
btc_lock_txid: String,
btc_amount: String,
xmr_amount: String,
exchange_rate: String,
peer_id: String,
completed: bool,
}

impl SwapDetails {
async fn from_db_state(
swap_id: Uuid,
state: State,
db: &Arc<dyn Database + Send + Sync>,
) -> Result<Self> {
let latest_state: AliceState = state.try_into()?;
let completed = is_complete(&latest_state);

let all_states = db.get_states(swap_id.clone()).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 exchange_rate = Self::calculate_exchange_rate(state3.btc, state3.xmr)?;
let start_date = db.get_swap_start_date(swap_id.clone()).await?;
let btc_lock_txid = state3.tx_lock.txid();
let peer_id = db.get_peer_id(swap_id.clone()).await?;

Ok(Self {
swap_id: swap_id.to_string(),
start_date: start_date.to_string(),
state: latest_state.to_string(),
btc_lock_txid: btc_lock_txid.to_string(),
btc_amount: state3.btc.to_string(),
xmr_amount: state3.xmr.to_string(),
exchange_rate,
peer_id: peer_id.to_string(),
completed,
})
.flatten()
.collect::<Vec<_>>();
}

let key = seed.derive_torv3_key();
fn calculate_exchange_rate(btc: bitcoin::Amount, xmr: monero::Amount) -> Result<String> {
let btc_decimal = Decimal::from_f64(btc.to_btc())
.ok_or_else(|| anyhow::anyhow!("Failed to convert BTC amount to Decimal"))?;
let xmr_decimal = Decimal::from_f64(xmr.as_xmr())
.ok_or_else(|| anyhow::anyhow!("Failed to convert XMR amount to Decimal"))?;

ac.add_services(&hidden_services_details, &key).await?;
let rate = btc_decimal
.checked_div(xmr_decimal)
.ok_or_else(|| anyhow::anyhow!("Division by zero or overflow"))?;

let onion_address = key
.public()
.get_onion_address()
.get_address_without_dot_onion();
Ok(format!("{} XMR/BTC", rate.round_dp(8)))
}

hidden_services_details.iter().for_each(|(port, _)| {
let onion_address = format!("/onion3/{}:{}", onion_address, port);
tracing::info!(%onion_address, "Successfully created hidden service");
});
fn to_table_row(&self) -> Vec<String> {
vec![
self.swap_id.clone(),
self.start_date.clone(),
self.state.clone(),
self.btc_lock_txid.clone(),
self.btc_amount.clone(),
self.xmr_amount.clone(),
self.exchange_rate.clone(),
self.peer_id.clone(),
self.completed.to_string(),
]
}

Ok(ac)
fn log_info(&self) {
tracing::info!(
swap_id = %self.swap_id,
swap_start_date = %self.start_date,
latest_state = %self.state,
btc_lock_txid = %self.btc_lock_txid,
btc_amount = %self.btc_amount,
xmr_amount = %self.xmr_amount,
exchange_rate = %self.exchange_rate,
trading_partner_peer_id = %self.peer_id,
completed = self.completed,
"Found swap in database"
);
}
>>>>>>> 0900fb9f (feat(asb): Enhance history command)
}
5 changes: 5 additions & 0 deletions swap/src/monero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ impl Amount {
self.0
}

/// Return Monero Amount as XMR.
pub fn as_xmr(&self) -> f64 {
self.0 as f64 / PICONERO_OFFSET as f64
}

/// Calculate the maximum amount of Bitcoin that can be bought at a given
/// asking price for this amount of Monero including the median fee.
pub fn max_bitcoin_for_price(&self, ask_price: bitcoin::Amount) -> Option<bitcoin::Amount> {
Expand Down
4 changes: 2 additions & 2 deletions swap/src/protocol/alice/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion swap/src/protocol/alice/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ where
})
}

pub(crate) fn is_complete(state: &AliceState) -> bool {
pub fn is_complete(state: &AliceState) -> bool {
matches!(
state,
AliceState::XmrRefunded
Expand Down

0 comments on commit 422ecfe

Please sign in to comment.