mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
feat: Enhance history command with more swap details (#1725)
This commit is contained in:
parent
b18ba95e8c
commit
cc854be8f4
@ -167,6 +167,7 @@ pub struct Context {
|
||||
pub swap_lock: Arc<SwapLock>,
|
||||
pub config: Config,
|
||||
pub tasks: Arc<PendingTaskList>,
|
||||
pub is_daemon: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -180,6 +181,7 @@ impl Context {
|
||||
debug: bool,
|
||||
json: bool,
|
||||
server_address: Option<SocketAddr>,
|
||||
is_daemon: bool,
|
||||
) -> Result<Context> {
|
||||
let data_dir = data::data_dir_from(data, is_testnet)?;
|
||||
let env_config = env_config_from(is_testnet);
|
||||
@ -241,6 +243,7 @@ impl Context {
|
||||
},
|
||||
swap_lock: Arc::new(SwapLock::new()),
|
||||
tasks: Arc::new(PendingTaskList::default()),
|
||||
is_daemon,
|
||||
};
|
||||
|
||||
Ok(context)
|
||||
@ -265,6 +268,7 @@ impl Context {
|
||||
monero_rpc_process: None,
|
||||
swap_lock: Arc::new(SwapLock::new()),
|
||||
tasks: Arc::new(PendingTaskList::default()),
|
||||
is_daemon: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,12 @@ use crate::protocol::bob::{BobState, Swap};
|
||||
use crate::protocol::{bob, State};
|
||||
use crate::{bitcoin, cli, monero, rpc};
|
||||
use anyhow::{bail, Context as AnyContext, Result};
|
||||
use comfy_table::Table;
|
||||
use libp2p::core::Multiaddr;
|
||||
use qrcode::render::unicode;
|
||||
use qrcode::QrCode;
|
||||
use rust_decimal::prelude::FromPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
use std::cmp::min;
|
||||
use std::convert::TryInto;
|
||||
@ -638,14 +641,97 @@ impl Request {
|
||||
})
|
||||
}
|
||||
Method::History => {
|
||||
let swaps = context.db.all().await?;
|
||||
let mut vec: Vec<(Uuid, String)> = Vec::new();
|
||||
for (swap_id, state) in swaps {
|
||||
let state: BobState = state.try_into()?;
|
||||
vec.push((swap_id, state.to_string()));
|
||||
let mut table = Table::new();
|
||||
table.set_header(vec![
|
||||
"Swap ID",
|
||||
"Start Date",
|
||||
"State",
|
||||
"BTC Amount",
|
||||
"XMR Amount",
|
||||
"Exchange Rate",
|
||||
"Trading Partner Peer ID",
|
||||
]);
|
||||
|
||||
let all_swaps = context.db.all().await?;
|
||||
let mut json_results = Vec::new();
|
||||
|
||||
for (swap_id, state) in all_swaps {
|
||||
let result: Result<_> = async {
|
||||
let latest_state: BobState = state.try_into()?;
|
||||
let all_states = context.db.get_states(swap_id).await?;
|
||||
let state3 = all_states
|
||||
.iter()
|
||||
.find_map(|s| {
|
||||
if let State::Bob(BobState::BtcLocked { state3, .. }) = s {
|
||||
Some(state3)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.context("Failed to get \"BtcLocked\" state")?;
|
||||
|
||||
let swap_start_date = context.db.get_swap_start_date(swap_id).await?;
|
||||
let peer_id = context.db.get_peer_id(swap_id).await?;
|
||||
let btc_amount = state3.tx_lock.lock_amount();
|
||||
let xmr_amount = state3.xmr;
|
||||
let exchange_rate = Decimal::from_f64(btc_amount.to_btc())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Failed to convert BTC amount to Decimal")
|
||||
})?
|
||||
.checked_div(xmr_amount.as_xmr())
|
||||
.ok_or_else(|| anyhow::anyhow!("Division by zero or overflow"))?;
|
||||
let exchange_rate = format!("{} XMR/BTC", exchange_rate.round_dp(8));
|
||||
|
||||
let swap_data = json!({
|
||||
"swapId": swap_id.to_string(),
|
||||
"startDate": swap_start_date.to_string(),
|
||||
"state": latest_state.to_string(),
|
||||
"btcAmount": btc_amount.to_string(),
|
||||
"xmrAmount": xmr_amount.to_string(),
|
||||
"exchangeRate": exchange_rate,
|
||||
"tradingPartnerPeerId": peer_id.to_string()
|
||||
});
|
||||
|
||||
if context.config.json {
|
||||
tracing::info!(
|
||||
swap_id = %swap_id,
|
||||
swap_start_date = %swap_start_date,
|
||||
latest_state = %latest_state,
|
||||
btc_amount = %btc_amount,
|
||||
xmr_amount = %xmr_amount,
|
||||
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(),
|
||||
btc_amount.to_string(),
|
||||
xmr_amount.to_string(),
|
||||
exchange_rate,
|
||||
peer_id.to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
Ok(swap_data)
|
||||
}
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(swap_data) => json_results.push(swap_data),
|
||||
Err(e) => {
|
||||
tracing::error!(swap_id = %swap_id, error = %e, "Failed to get swap details")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(json!({ "swaps": vec }))
|
||||
if !context.config.json && !context.is_daemon {
|
||||
println!("{}", table);
|
||||
}
|
||||
|
||||
Ok(json!({"swaps": json_results}))
|
||||
}
|
||||
Method::GetRawStates => {
|
||||
let raw_history = context.db.raw_all().await?;
|
||||
|
@ -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<Amount>,
|
||||
@ -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.")]
|
||||
@ -387,7 +395,9 @@ mod tests {
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::History,
|
||||
cmd: Command::History {
|
||||
only_unfinished: false,
|
||||
},
|
||||
};
|
||||
let args = parse_args(raw_ars).unwrap();
|
||||
assert_eq!(expected_args, args);
|
||||
@ -570,7 +580,9 @@ mod tests {
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::History,
|
||||
cmd: Command::History {
|
||||
only_unfinished: false,
|
||||
},
|
||||
};
|
||||
let args = parse_args(raw_ars).unwrap();
|
||||
assert_eq!(expected_args, args);
|
||||
|
@ -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, 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::State;
|
||||
use swap::seed::Seed;
|
||||
use swap::tor::AuthenticatedClient;
|
||||
use swap::{asb, bitcoin, kraken, monero, tor};
|
||||
@ -224,19 +228,87 @@ async fn main() -> Result<()> {
|
||||
|
||||
event_loop.run().await;
|
||||
}
|
||||
Command::History => {
|
||||
Command::History { only_unfinished } => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadOnly).await?;
|
||||
let mut table: Table = Table::new();
|
||||
|
||||
let mut table = Table::new();
|
||||
table.set_header(vec![
|
||||
"Swap ID",
|
||||
"Start Date",
|
||||
"State",
|
||||
"BTC Amount",
|
||||
"XMR Amount",
|
||||
"Exchange Rate",
|
||||
"Trading Partner Peer ID",
|
||||
"Completed",
|
||||
]);
|
||||
|
||||
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()?;
|
||||
let is_completed = is_complete(&latest_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()]);
|
||||
if only_unfinished && is_completed {
|
||||
return Ok::<_, anyhow::Error>(());
|
||||
}
|
||||
|
||||
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 = Decimal::from_f64(state3.btc.to_btc())
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to convert BTC amount to Decimal"))?
|
||||
.checked_div(state3.xmr.as_xmr())
|
||||
.ok_or_else(|| anyhow::anyhow!("Division by zero or overflow"))?;
|
||||
let exchange_rate = format!("{} XMR/BTC", exchange_rate.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,
|
||||
completed = is_completed,
|
||||
"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(),
|
||||
is_completed.to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
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)?;
|
||||
|
@ -78,6 +78,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -100,14 +101,16 @@ where
|
||||
let request = Request::new(Method::History);
|
||||
|
||||
let context =
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None, false)
|
||||
.await?;
|
||||
(context, request)
|
||||
}
|
||||
CliCommand::Config => {
|
||||
let request = Request::new(Method::Config);
|
||||
|
||||
let context =
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None, false)
|
||||
.await?;
|
||||
(context, request)
|
||||
}
|
||||
CliCommand::Balance { bitcoin } => {
|
||||
@ -124,6 +127,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -145,6 +149,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
server_address,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -166,6 +171,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -187,6 +193,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -207,6 +214,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -217,8 +225,18 @@ where
|
||||
} => {
|
||||
let request = Request::new(Method::ListSellers { rendezvous_point });
|
||||
|
||||
let context =
|
||||
Context::build(None, None, Some(tor), data, is_testnet, debug, json, None).await?;
|
||||
let context = Context::build(
|
||||
None,
|
||||
None,
|
||||
Some(tor),
|
||||
data,
|
||||
is_testnet,
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
(context, request)
|
||||
}
|
||||
@ -234,6 +252,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -244,7 +263,8 @@ where
|
||||
let request = Request::new(Method::MoneroRecovery { swap_id });
|
||||
|
||||
let context =
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None, false)
|
||||
.await?;
|
||||
|
||||
(context, request)
|
||||
}
|
||||
|
@ -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<Self> {
|
||||
let piconeros_dec =
|
||||
amount.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64"));
|
||||
@ -184,11 +192,8 @@ impl From<Amount> 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -440,7 +440,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_complete(state: &AliceState) -> bool {
|
||||
pub fn is_complete(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::XmrRefunded
|
||||
|
@ -369,7 +369,7 @@ pub struct State3 {
|
||||
S_a_monero: monero::PublicKey,
|
||||
S_a_bitcoin: bitcoin::PublicKey,
|
||||
v: monero::PrivateViewKey,
|
||||
xmr: monero::Amount,
|
||||
pub xmr: monero::Amount,
|
||||
pub cancel_timelock: CancelTimelock,
|
||||
punish_timelock: PunishTimelock,
|
||||
refund_address: bitcoin::Address,
|
||||
|
@ -103,13 +103,26 @@ mod test {
|
||||
|
||||
let (client, _, _) = setup_daemon(harness_ctx).await;
|
||||
|
||||
let response: HashMap<String, Vec<(Uuid, String)>> = client
|
||||
let response: HashMap<String, Vec<Value>> = client
|
||||
.request("get_history", ObjectParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let swaps: Vec<(Uuid, String)> = vec![(bob_swap_id, "btc is locked".to_string())];
|
||||
|
||||
assert_eq!(response, HashMap::from([("swaps".to_string(), swaps)]));
|
||||
let swaps = response.get("swaps").unwrap();
|
||||
assert_eq!(swaps.len(), 1);
|
||||
|
||||
assert_has_keys_serde(
|
||||
swaps[0].as_object().unwrap(),
|
||||
&[
|
||||
"swapId",
|
||||
"startDate",
|
||||
"state",
|
||||
"btcAmount",
|
||||
"xmrAmount",
|
||||
"exchangeRate",
|
||||
"tradingPartnerPeerId",
|
||||
],
|
||||
);
|
||||
|
||||
let response: HashMap<String, HashMap<Uuid, Vec<Value>>> = client
|
||||
.request("get_raw_states", ObjectParams::new())
|
||||
|
Loading…
Reference in New Issue
Block a user