From f224c495847334dfccdad42f065365fe3447bc73 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Mon, 7 Nov 2022 09:52:34 +0200 Subject: [PATCH 1/4] fix(asb): use unlocked monero balance for quotes --- CHANGELOG.md | 8 ++++- Cargo.lock | 1 + monero-harness/src/lib.rs | 23 ++++++++++++ monero-rpc/Cargo.toml | 1 + monero-rpc/src/wallet.rs | 29 +++++++++++++-- swap/src/asb/event_loop.rs | 6 +++- swap/src/bin/asb.rs | 53 ++++++++++++++++------------ swap/src/common.rs | 4 +-- swap/src/monero/wallet.rs | 6 ++-- swap/src/network/swap_setup/alice.rs | 10 +++--- swap/tests/harness/mod.rs | 4 ++- 11 files changed, 106 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db49608c..3715329c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Changed ASB to quote on Monero unlocked balance instead of total balance + +### Added + ## [0.11.0] - 2022-08-11 ### Changed @@ -320,7 +326,7 @@ 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.11.0...HEAD +[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.11.0...HEAD [0.11.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...0.11.0 [0.10.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.1...0.10.2 [0.10.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.0...0.10.1 diff --git a/Cargo.lock b/Cargo.lock index 5ecbc035..e650676b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2278,6 +2278,7 @@ dependencies = [ "monero-epee-bin-serde", "rand 0.7.3", "reqwest", + "rust_decimal", "serde", "serde_json", "tokio", diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index 5e1f2727..f762d5c5 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -138,15 +138,30 @@ impl<'c> Monero { let wallet = self.wallet(name)?; let address = wallet.address().await?.address; + let mut expected_total = 0; + let mut expected_unlocked = 0; + let mut unlocked = 0; for amount in amount_in_outputs { if amount > 0 { miner_wallet.transfer(&address, amount).await?; + expected_total += amount; tracing::info!("Funded {} wallet with {}", wallet.name, amount); + + // sanity checks for total/unlocked balance + let total = wallet.balance().await?; + assert_eq!(total, expected_total); + assert_eq!(unlocked, expected_unlocked); + monerod .client() .generateblocks(10, miner_address.clone()) .await?; wallet.refresh().await?; + expected_unlocked += amount; + + unlocked = wallet.unlocked_balance().await?; + assert_eq!(unlocked, expected_unlocked); + assert_eq!(total, expected_total); } } @@ -310,10 +325,18 @@ impl<'c> MoneroWalletRpc { Ok(balance) } + pub async fn unlocked_balance(&self) -> Result { + self.client().refresh().await?; + let balance = self.client().get_balance(0).await?.unlocked_balance; + + Ok(balance) + } + pub async fn refresh(&self) -> Result { Ok(self.client().refresh().await?) } } + /// Mine a block ever BLOCK_TIME_SECS seconds. async fn mine(monerod: monerod::Client, reward_address: String) -> Result<()> { loop { diff --git a/monero-rpc/Cargo.toml b/monero-rpc/Cargo.toml index 6bd76ad4..427345d6 100644 --- a/monero-rpc/Cargo.toml +++ b/monero-rpc/Cargo.toml @@ -13,6 +13,7 @@ monero = "0.12" monero-epee-bin-serde = "1" rand = "0.7" reqwest = { version = "0.11", default-features = false, features = [ "json" ] } +rust_decimal = { version = "1", features = [ "serde-float" ] } serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" tracing = "0.1" diff --git a/monero-rpc/src/wallet.rs b/monero-rpc/src/wallet.rs index b0f59b82..404e9693 100644 --- a/monero-rpc/src/wallet.rs +++ b/monero-rpc/src/wallet.rs @@ -1,4 +1,7 @@ +use std::fmt; + use anyhow::{Context, Result}; +use rust_decimal::Decimal; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize}; @@ -86,10 +89,30 @@ pub struct GetAddress { #[derive(Deserialize, Debug, Clone, Copy)] pub struct GetBalance { pub balance: u64, - pub blocks_to_unlock: u32, - pub multisig_import_needed: bool, - pub time_to_unlock: u32, pub unlocked_balance: u64, + pub multisig_import_needed: bool, + pub blocks_to_unlock: u32, + pub time_to_unlock: u32, +} + +impl fmt::Display for GetBalance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut total = Decimal::from(self.balance); + total + .set_scale(12) + .expect("12 is smaller than max precision of 28"); + + let mut unlocked = Decimal::from(self.unlocked_balance); + unlocked + .set_scale(12) + .expect("12 is smaller than max precision of 28"); + + write!( + f, + "total balance: {}, unlocked balance: {}", + total, unlocked + ) + } } #[derive(Deserialize, Debug, Clone)] diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 581bda2e..b90188c0 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -1,4 +1,5 @@ use crate::asb::{Behaviour, OutEvent, Rate}; +use crate::monero::Amount; use crate::network::quote::BidQuote; use crate::network::swap_setup::alice::WalletSnapshot; use crate::network::transfer_proof; @@ -326,7 +327,10 @@ where .ask() .context("Failed to compute asking price")?; - let xmr = self.monero_wallet.get_balance().await?; + let balance = self.monero_wallet.get_balance().await?; + + // use unlocked monero balance for quote + let xmr = Amount::from_piconero(balance.unlocked_balance); let max_bitcoin_for_monero = xmr.max_bitcoin_for_price(ask_price).ok_or_else(|| { anyhow::anyhow!("Bitcoin price ({}) x Monero ({}) overflow", ask_price, xmr) diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index fd2da1f9..3c143dc6 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -31,7 +31,6 @@ use swap::asb::config::{ use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, Finality, KrakenRate}; use swap::common::check_latest_version; use swap::database::open_db; -use swap::monero::Amount; use swap::network::rendezvous::XmrBtcNamespace; use swap::network::swarm; use swap::protocol::alice::{run, AliceState}; @@ -103,24 +102,35 @@ async fn main() -> Result<()> { match cmd { Command::Start { resume_only } => { - let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; - let monero_wallet = init_monero_wallet(&config, env_config).await?; - - let bitcoin_balance = bitcoin_wallet.balance().await?; - tracing::info!(%bitcoin_balance, "Initialized Bitcoin wallet"); - - let monero_balance = monero_wallet.get_balance().await?; - if monero_balance == Amount::ZERO { - let monero_address = monero_wallet.get_main_address(); - tracing::warn!( - %monero_address, - "The Monero balance is 0, make sure to deposit funds at", - ) - } else { - tracing::info!(%monero_balance, "Initialized Monero wallet"); + let monero_address = monero_wallet.get_main_address(); + tracing::info!(%monero_address, "Monero wallet address"); + let monero = monero_wallet.get_balance().await?; + match (monero.balance, monero.unlocked_balance) { + (0, _) => { + tracing::warn!( + %monero_address, + "The Monero balance is 0, make sure to deposit funds at", + ) + } + (total, 0) => { + let total = monero::Amount::from_piconero(total); + tracing::warn!( + %total, + "Unlocked Monero balance is 0, total balance is", + ) + } + (total, unlocked) => { + let total = monero::Amount::from_piconero(total); + let unlocked = monero::Amount::from_piconero(unlocked); + tracing::info!(%total, %unlocked, "Monero wallet balance"); + } } + let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; + let bitcoin_balance = bitcoin_wallet.balance().await?; + tracing::info!(%bitcoin_balance, "Bitcoin wallet balance"); + let kraken_price_updates = kraken::connect(config.maker.price_ticker_ws_url.clone())?; // setup Tor hidden services @@ -236,16 +246,13 @@ async fn main() -> Result<()> { bitcoin_wallet.broadcast(signed_tx, "withdraw").await?; } Command::Balance => { - let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let monero_wallet = init_monero_wallet(&config, env_config).await?; - - let bitcoin_balance = bitcoin_wallet.balance().await?; let monero_balance = monero_wallet.get_balance().await?; + tracing::info!(%monero_balance); - tracing::info!( - %bitcoin_balance, - %monero_balance, - "Current balance"); + let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; + let bitcoin_balance = bitcoin_wallet.balance().await?; + tracing::info!(%bitcoin_balance); } Command::Cancel { swap_id } => { let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; diff --git a/swap/src/common.rs b/swap/src/common.rs index 66adca83..291997e6 100644 --- a/swap/src/common.rs +++ b/swap/src/common.rs @@ -48,10 +48,10 @@ mod test { #[tokio::test] #[ignore = "For local testing, makes http requests to github."] async fn it_compares_with_github() { - let result = check_latest_version("0.10.1").await.unwrap(); + let result = check_latest_version("0.11.0").await.unwrap(); assert_eq!(result, Version::Available); - let result = check_latest_version("0.10.2").await.unwrap(); + let result = check_latest_version("0.11.1").await.unwrap(); assert_eq!(result, Version::Current); } } diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index 0d3d3389..a8fccc5d 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -253,10 +253,8 @@ impl Wallet { } /// Get the balance of the primary account. - pub async fn get_balance(&self) -> Result { - let amount = self.inner.lock().await.get_balance(0).await?.balance; - - Ok(Amount::from_piconero(amount)) + pub async fn get_balance(&self) -> Result { + Ok(self.inner.lock().await.get_balance(0).await?) } pub async fn block_height(&self) -> Result { diff --git a/swap/src/network/swap_setup/alice.rs b/swap/src/network/swap_setup/alice.rs index 1fab2503..3d8eebcc 100644 --- a/swap/src/network/swap_setup/alice.rs +++ b/swap/src/network/swap_setup/alice.rs @@ -1,4 +1,5 @@ use crate::asb::LatestRate; +use crate::monero::Amount; use crate::network::swap_setup; use crate::network::swap_setup::{ protocol, BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse, @@ -42,7 +43,7 @@ pub enum OutEvent { #[derive(Debug)] pub struct WalletSnapshot { - balance: monero::Amount, + balance: monero_rpc::wallet::GetBalance, lock_fee: monero::Amount, // TODO: Consider using the same address for punish and redeem (they are mutually exclusive, so @@ -323,7 +324,8 @@ where .sell_quote(btc) .map_err(Error::SellQuoteCalculationFailed)?; - if wallet_snapshot.balance < xmr + wallet_snapshot.lock_fee { + let unlocked = Amount::from_piconero(wallet_snapshot.balance.unlocked_balance); + if unlocked < xmr + wallet_snapshot.lock_fee { return Err(Error::BalanceTooLow { balance: wallet_snapshot.balance, buy: btc, @@ -479,9 +481,9 @@ pub enum Error { max: bitcoin::Amount, buy: bitcoin::Amount, }, - #[error("Balance {balance} too low to fulfill swapping {buy}")] + #[error("Unlocked balance ({balance}) too low to fulfill swapping {buy}")] BalanceTooLow { - balance: monero::Amount, + balance: monero_rpc::wallet::GetBalance, buy: bitcoin::Amount, }, #[error("Failed to fetch latest rate")] diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index d17a580f..29319c21 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -867,7 +867,9 @@ impl Wallet for monero::Wallet { } async fn get_balance(&self) -> Result { - self.get_balance().await + let total = self.get_balance().await?; + let balance = Self::Amount::from_piconero(total.balance); + Ok(balance) } } From 3d12631d6949947ef481cb4b843a6ecb8395600d Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Mon, 7 Nov 2022 09:53:50 +0200 Subject: [PATCH 2/4] feat: upgrade monero images to v0.18.1.2 --- monero-harness/src/image.rs | 5 +++-- swap/src/monero/wallet_rpc.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/monero-harness/src/image.rs b/monero-harness/src/image.rs index 1b8fc422..63cc4b20 100644 --- a/monero-harness/src/image.rs +++ b/monero-harness/src/image.rs @@ -25,7 +25,7 @@ impl Image for Monerod { type EntryPoint = str; fn descriptor(&self) -> String { - "rinocommunity/monero:v0.18.0.0".to_owned() + "rinocommunity/monero:v0.18.1.2".to_owned() } fn wait_until_ready(&self, container: &Container<'_, D, Self>) { @@ -70,7 +70,7 @@ impl Image for MoneroWalletRpc { type EntryPoint = str; fn descriptor(&self) -> String { - "rinocommunity/monero:v0.18.0.0".to_owned() + "rinocommunity/monero:v0.18.1.2".to_owned() } fn wait_until_ready(&self, container: &Container<'_, D, Self>) { @@ -229,6 +229,7 @@ impl IntoIterator for MoneroWalletRpcArgs { format!("--daemon-address={}", self.daemon_address), format!("--rpc-bind-port={}", RPC_PORT), "--log-level=4".to_string(), + "--allow-mismatched-daemon-version".to_string(), /* https://github.com/monero-project/monero/issues/8600 */ ]; if self.disable_rpc_login { diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs index e3fbb8f4..9cda2fe3 100644 --- a/swap/src/monero/wallet_rpc.rs +++ b/swap/src/monero/wallet_rpc.rs @@ -18,20 +18,20 @@ use tokio_util::io::StreamReader; compile_error!("unsupported operating system"); #[cfg(all(target_os = "macos", target_arch = "x86_64"))] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-x64-v0.18.0.0.tar.bz2"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-x64-v0.18.1.2.tar.bz2"; #[cfg(all(target_os = "macos", target_arch = "arm"))] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-armv8-v0.18.0.0.tar.bz2"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-armv8-v0.18.1.2.tar.bz2"; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.0.0.tar.bz2"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.1.2.tar.bz2"; #[cfg(all(target_os = "linux", target_arch = "arm"))] const DOWNLOAD_URL: &str = - "https://downloads.getmonero.org/cli/monero-linux-armv7-v0.18.0.0.tar.bz2"; + "https://downloads.getmonero.org/cli/monero-linux-armv7-v0.18.1.2.tar.bz2"; #[cfg(target_os = "windows")] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-win-x64-v0.18.0.0.zip"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-win-x64-v0.18.1.2.zip"; #[cfg(any(target_os = "macos", target_os = "linux"))] const PACKED_FILE: &str = "monero-wallet-rpc"; @@ -39,7 +39,7 @@ const PACKED_FILE: &str = "monero-wallet-rpc"; #[cfg(target_os = "windows")] const PACKED_FILE: &str = "monero-wallet-rpc.exe"; -const CODENAME: &str = "Fluorine Fermi"; +const WALLET_RPC_VERSION: &str = "v0.18.1.2"; #[derive(Debug, Clone, Copy, thiserror::Error)] #[error("monero wallet rpc executable not found in downloaded archive")] @@ -86,7 +86,7 @@ impl WalletRpc { let version = String::from_utf8_lossy(&output.stdout); tracing::debug!("RPC version output: {}", version); - if !version.contains(CODENAME) { + if !version.contains(WALLET_RPC_VERSION) { tracing::info!("Removing old version of monero-wallet-rpc"); tokio::fs::remove_file(exec_path).await?; } From f09b237e151994284ba0f5acd3f3f67983c7c6ee Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:31:07 +0200 Subject: [PATCH 3/4] Immediately fetch transaction status upon subscription Immediately fetch transaction status upon subscription instead of waiting 1 minute (cherry picked from commit 5662f7fe81b1d93eed73621dd102f0fac3d59811) --- swap/src/bitcoin/wallet.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 44922dbb..98bbe2ef 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -131,8 +131,6 @@ impl Wallet { let mut last_status = None; loop { - tokio::time::sleep(Duration::from_secs(5)).await; - let new_status = match client.lock().await.status_of_script(&tx) { Ok(new_status) => new_status, Err(error) => { @@ -153,6 +151,8 @@ impl Wallet { return; } } + + tokio::time::sleep(Duration::from_secs(5)).await; } }); @@ -705,7 +705,7 @@ impl Client { electrum, blockchain, latest_block_height: BlockHeight::try_from(latest_block)?, - last_sync: Instant::now(), + last_sync: Instant::now() - interval, sync_interval: interval, script_history: Default::default(), subscriptions: Default::default(), From 08426d817117ad8395eb6013d8b4e4e423a458fe Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Tue, 29 Nov 2022 14:18:42 +0200 Subject: [PATCH 4/4] fixup! fix(asb): use unlocked monero balance for quotes --- swap/src/bin/asb.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index 3c143dc6..17ddc224 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -253,6 +253,7 @@ async fn main() -> Result<()> { let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_balance = bitcoin_wallet.balance().await?; tracing::info!(%bitcoin_balance); + tracing::info!(%bitcoin_balance, %monero_balance, "Current balance"); } Command::Cancel { swap_id } => { let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;