Merge branch 'master' into redact-logs

This commit is contained in:
Einliterflasche 2024-08-20 14:29:12 +02:00 committed by GitHub
commit 57e80fced1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 385 additions and 102 deletions

View File

@ -20,7 +20,7 @@ jobs:
run: git checkout -b release/${{ github.event.inputs.version }}
- name: Update changelog
uses: thomaseizinger/keep-a-changelog-new-release@3.0.0
uses: thomaseizinger/keep-a-changelog-new-release@3.1.0
with:
version: ${{ github.event.inputs.version }}
changelogPath: CHANGELOG.md

View File

@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.13.4] - 2024-07-25
- ASB: The `history` command can now be used while the asb is running.
- ASB: Retry locking of Monero if it fails on first attempt
## [0.13.3] - 2024-07-15
@ -372,7 +375,8 @@ It is possible to migrate critical data from the old db to the sqlite but there
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.3...HEAD
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.4...HEAD
[0.13.4]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.3...0.13.4
[0.13.3]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.2...0.13.3
[0.13.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.1...0.13.2
[0.13.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.0...0.13.1

128
Cargo.lock generated
View File

@ -752,7 +752,7 @@ dependencies = [
"nom",
"pathdiff",
"serde",
"toml 0.8.15",
"toml 0.8.19",
]
[[package]]
@ -1783,6 +1783,7 @@ dependencies = [
"http 1.0.0",
"http-body 1.0.0",
"httparse",
"httpdate",
"itoa",
"pin-project-lite 0.2.13",
"smallvec",
@ -2602,14 +2603,19 @@ dependencies = [
[[package]]
name = "mockito"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6e023aa5bdf392aa06c78e4a4e6d498baab5138d0c993503350ebbc37bf1e"
checksum = "09b34bd91b9e5c5b06338d392463e1318d683cf82ec3d3af4014609be6e2108d"
dependencies = [
"assert-json-diff",
"bytes",
"colored",
"futures-core",
"hyper 0.14.28",
"futures-util",
"http 1.0.0",
"http-body 1.0.0",
"http-body-util",
"hyper 1.4.1",
"hyper-util",
"log",
"rand 0.8.3",
"regex",
@ -4016,9 +4022,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.204"
version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
dependencies = [
"serde_derive",
]
@ -4045,9 +4051,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.204"
version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [
"proc-macro2",
"quote",
@ -4056,20 +4062,21 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.118"
version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.6"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
@ -4536,7 +4543,7 @@ checksum = "8049cf85f0e715d6af38dde439cb0ccb91f67fb9f5f63c80f8b43e48356e1a3f"
[[package]]
name = "swap"
version = "0.13.3"
version = "0.13.4"
dependencies = [
"anyhow",
"async-compression",
@ -4602,7 +4609,7 @@ dependencies = [
"tokio-tar",
"tokio-tungstenite",
"tokio-util",
"toml 0.8.15",
"toml 0.8.19",
"torut",
"tracing",
"tracing-appender",
@ -4657,14 +4664,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.10.1"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [
"cfg-if 1.0.0",
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -4864,9 +4872,9 @@ dependencies = [
[[package]]
name = "tokio-socks"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
dependencies = [
"either",
"futures-util",
@ -4942,9 +4950,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.15"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
@ -4954,18 +4962,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.16"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap 2.1.0",
"serde",
@ -5640,7 +5648,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
@ -5660,17 +5677,18 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
@ -5681,9 +5699,9 @@ checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
@ -5699,9 +5717,9 @@ checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
@ -5717,9 +5735,15 @@ checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
@ -5735,9 +5759,9 @@ checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
@ -5753,9 +5777,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
@ -5765,9 +5789,9 @@ checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
@ -5783,15 +5807,15 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.5"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]

View File

@ -1,6 +1,6 @@
[package]
name = "swap"
version = "0.13.3"
version = "0.13.4"
authors = [ "The COMIT guys <hello@comit.network>" ]
edition = "2021"
description = "XMR/BTC trustless atomic swaps."
@ -83,7 +83,7 @@ bitcoin-harness = { git = "https://github.com/delta1/bitcoin-harness-rs.git", re
get-port = "3"
hyper = "1.4"
jsonrpsee = { version = "0.16.2", features = [ "ws-client" ] }
mockito = "1.4"
mockito = "1.5"
monero-harness = { path = "../monero-harness" }
port_check = "0.2"
proptest = "1"

View File

@ -170,6 +170,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)]
@ -183,6 +184,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);
@ -251,6 +253,7 @@ impl Context {
},
swap_lock: Arc::new(SwapLock::new()),
tasks: Arc::new(PendingTaskList::default()),
is_daemon,
};
Ok(context)
@ -275,6 +278,7 @@ impl Context {
monero_rpc_process: None,
swap_lock: Arc::new(SwapLock::new()),
tasks: Arc::new(PendingTaskList::default()),
is_daemon: true,
}
}

View File

@ -9,9 +9,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;
@ -652,14 +655,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::Logs { logs_dir, redact, swap_id } => {
let dir = logs_dir.unwrap_or(context.config.data_dir.join("logs"));

View File

@ -31,12 +31,12 @@ where
env_config: env_config(testnet),
cmd: Command::Start { resume_only },
},
RawCommand::History => Arguments {
RawCommand::History { only_unfinished } => Arguments {
testnet,
json,
config_path: config_path(config, testnet)?,
env_config: env_config(testnet),
cmd: Command::History,
cmd: Command::History { only_unfinished },
},
RawCommand::Logs {
logs_dir: dir_path,
@ -197,7 +197,9 @@ pub enum Command {
Start {
resume_only: bool,
},
History,
History {
only_unfinished: bool,
},
Config,
Logs {
logs_dir: Option<PathBuf>,
@ -275,8 +277,6 @@ pub enum RawCommand {
)]
resume_only: bool,
},
#[structopt(about = "Prints swap-id and the state of each swap ever made.")]
History,
#[structopt(about = "Prints all logging messages issued in the past.")]
Logs {
#[structopt(
@ -296,6 +296,14 @@ pub enum RawCommand {
)]
swap_id: Option<Uuid>
},
#[structopt(about = "Prints swap-id and the state of each swap ever made.")]
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.")]
@ -411,7 +419,9 @@ mod tests {
json: 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);
@ -586,7 +596,9 @@ mod tests {
json: 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);

View File

@ -19,6 +19,8 @@ use libp2p::core::Multiaddr;
use libp2p::swarm::AddressScore;
use libp2p::Swarm;
use swap::common::tracing_util::Format;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;
use std::convert::TryInto;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
@ -34,7 +36,9 @@ use swap::common::{self, check_latest_version, get_logs};
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::{bitcoin, kraken, monero, tor};
@ -233,19 +237,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)?;

View File

@ -78,6 +78,7 @@ where
debug,
json,
None,
false,
)
.await?;
@ -100,7 +101,8 @@ 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::Logs {
@ -117,7 +119,8 @@ where
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 } => {
@ -134,6 +137,7 @@ where
debug,
json,
None,
false,
)
.await?;
(context, request)
@ -155,6 +159,7 @@ where
debug,
json,
server_address,
true,
)
.await?;
(context, request)
@ -176,6 +181,7 @@ where
debug,
json,
None,
false,
)
.await?;
(context, request)
@ -197,6 +203,7 @@ where
debug,
json,
None,
false,
)
.await?;
(context, request)
@ -217,6 +224,7 @@ where
debug,
json,
None,
false,
)
.await?;
(context, request)
@ -227,8 +235,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)
}
@ -244,6 +262,7 @@ where
debug,
json,
None,
false,
)
.await?;
(context, request)
@ -254,7 +273,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)
}

View File

@ -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)
}
}

View File

@ -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,

View File

@ -1,11 +1,14 @@
//! Run an XMR/BTC swap in the role of Alice.
//! Alice holds XMR and wishes receive BTC.
use std::time::Duration;
use crate::asb::{EventLoopHandle, LatestRate};
use crate::bitcoin::ExpiredTimelocks;
use crate::env::Config;
use crate::protocol::alice::{AliceState, Swap};
use crate::{bitcoin, monero};
use anyhow::{bail, Context, Result};
use backoff::ExponentialBackoffBuilder;
use tokio::select;
use tokio::time::timeout;
use uuid::Uuid;
@ -111,23 +114,63 @@ where
}
}
AliceState::BtcLocked { state3 } => {
match state3.expired_timelocks(bitcoin_wallet).await? {
ExpiredTimelocks::None { .. } => {
// Record the current monero wallet block height so we don't have to scan from
// block 0 for scenarios where we create a refund wallet.
let monero_wallet_restore_blockheight = monero_wallet.block_height().await?;
// We retry to lock the Monero wallet until we succeed or until the cancel timelock expires.
//
// This is necessary because the monero-wallet-rpc can sometimes error out due to various reasons, such as
// - no connection to the daemon
// - "failed to get output distribution"
// See https://github.com/comit-network/xmr-btc-swap/issues/1726
let backoff = ExponentialBackoffBuilder::new()
.with_initial_interval(Duration::from_secs(5))
.with_max_interval(Duration::from_secs(60 * 3))
.with_max_elapsed_time(None)
.build();
let transfer_proof = monero_wallet
.transfer(state3.lock_xmr_transfer_request())
.await?;
let result = backoff::future::retry_notify(
backoff,
|| async {
match state3.expired_timelocks(bitcoin_wallet).await {
Ok(ExpiredTimelocks::None { .. }) => {
// Record the current monero wallet block height so we don't have to scan from
// block 0 for scenarios where we create a refund wallet.
let monero_wallet_restore_blockheight = monero_wallet
.block_height()
.await
.map_err(backoff::Error::transient)?;
let transfer_proof = monero_wallet
.transfer(state3.lock_xmr_transfer_request())
.await
.map_err(backoff::Error::transient)?;
Ok(Some((monero_wallet_restore_blockheight, transfer_proof)))
}
Ok(_) => Ok(None),
Err(e) => Err(backoff::Error::transient(e)),
}
},
|err, delay: Duration| {
tracing::warn!(
%err,
delay_secs = delay.as_secs(),
"Failed to lock XMR. We will retry after a delay"
);
},
)
.await;
match result {
Ok(Some((monero_wallet_restore_blockheight, transfer_proof))) => {
AliceState::XmrLockTransactionSent {
monero_wallet_restore_blockheight,
transfer_proof,
state3,
}
}
_ => AliceState::SafelyAborted,
Ok(None) => AliceState::SafelyAborted,
Err(e) => {
unreachable!("We should retry forever until the cancel timelock expires. But we got an error: {:#}", e);
}
}
}
AliceState::XmrLockTransactionSent {
@ -397,7 +440,7 @@ where
})
}
pub(crate) fn is_complete(state: &AliceState) -> bool {
pub fn is_complete(state: &AliceState) -> bool {
matches!(
state,
AliceState::XmrRefunded

View File

@ -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,

View File

@ -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())