diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbb1b03..e6e1aab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The rendezvous node address (`rendezvous_point`), as well as the ASB's external addresses (`external_addresses`) to be registered, is configured in the `network` section of the ASB config file. A rendezvous node is provided at `/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o` which is used as default for discovery in the CLI. Upon discovery using `list-sellers` CLI users are provided with quote and connection information for each ASB discovered through the rendezvous node. +- A mandatory `--change-address` parameter to the CLI's `buy-xmr` command. + The provided address is used to transfer Bitcoin in case of a refund and in case the user transfers more than the specified amount into the swap. + For more information see [#513](https://github.com/comit-network/xmr-btc-swap/issues/513). ### Fixed @@ -39,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The CLI can use regular TCP connections and having both - TCP and websockets - causes problems and unnecessary overhead. - The `--seller-addr` parameter from the CLI's `resume` command. This information is now loaded from the database. +- The `--receive-address` parameter from the CLI's `resume` command. + This information is now loaded from the database. ## [0.7.0] - 2021-05-28 diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 6eda996f..87a95d91 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -45,7 +45,7 @@ rust_decimal_macros = "1" serde = { version = "1", features = [ "derive" ] } serde_cbor = "0.11" serde_json = "1" -serde_with = { version = "1.9.4", features = [ "macros" ] } +serde_with = { version = "1", features = [ "macros" ] } sha2 = "0.9" sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] } sled = "0.34" diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index 3f43fe21..35a7e436 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -231,7 +231,9 @@ async fn main() -> Result<()> { } }; - let psbt = bitcoin_wallet.send_to_address(address, amount).await?; + let psbt = bitcoin_wallet + .send_to_address(address, amount, None) + .await?; let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?; bitcoin_wallet.broadcast(signed_tx, "withdraw").await?; diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index ba3e3ed2..7018d43a 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -12,7 +12,7 @@ #![forbid(unsafe_code)] #![allow(non_snake_case)] -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use comfy_table::Table; use qrcode::render::unicode; use qrcode::QrCode; @@ -59,6 +59,7 @@ async fn main() -> Result<()> { seller, bitcoin_electrum_rpc_url, bitcoin_target_block, + bitcoin_change_address, monero_receive_address, monero_daemon_address, tor_socks5_port, @@ -113,6 +114,8 @@ async fn main() -> Result<()> { info!(%amount, %fees, %swap_id, "Swapping"); db.insert_peer_id(swap_id, seller_peer_id).await?; + db.insert_monero_address(swap_id, monero_receive_address) + .await?; let swap = Swap::new( db, @@ -122,6 +125,7 @@ async fn main() -> Result<()> { env_config, event_loop_handle, monero_receive_address, + bitcoin_change_address, amount, ); @@ -153,7 +157,6 @@ async fn main() -> Result<()> { swap_id, bitcoin_electrum_rpc_url, bitcoin_target_block, - monero_receive_address, monero_daemon_address, tor_socks5_port, } => { @@ -163,10 +166,6 @@ async fn main() -> Result<()> { let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read in seed file")?; - if monero_receive_address.network != env_config.monero_network { - bail!("The given monero address is on network {:?}, expected address of network {:?}.", monero_receive_address.network, env_config.monero_network) - } - let bitcoin_wallet = init_bitcoin_wallet( bitcoin_electrum_rpc_url, &seed, @@ -198,6 +197,7 @@ async fn main() -> Result<()> { EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?; let handle = tokio::spawn(event_loop.run()); + let monero_receive_address = db.get_monero_address(swap_id)?; let swap = Swap::from_db( db, swap_id, diff --git a/swap/src/bitcoin/lock.rs b/swap/src/bitcoin/lock.rs index 231edff4..8bb6ab29 100644 --- a/swap/src/bitcoin/lock.rs +++ b/swap/src/bitcoin/lock.rs @@ -24,6 +24,7 @@ impl TxLock { amount: Amount, A: PublicKey, B: PublicKey, + change: bitcoin::Address, ) -> Result where C: EstimateFeeRate, @@ -34,7 +35,9 @@ impl TxLock { .address(wallet.get_network()) .expect("can derive address from descriptor"); - let psbt = wallet.send_to_address(address, amount).await?; + let psbt = wallet + .send_to_address(address, amount, Some(change)) + .await?; Ok(Self { inner: psbt, @@ -251,7 +254,11 @@ mod tests { wallet: &Wallet<(), bdk::database::MemoryDatabase, StaticFeeRate>, amount: Amount, ) -> PartiallySignedTransaction { - TxLock::new(&wallet, amount, A, B).await.unwrap().into() + let change = wallet.new_address().await.unwrap(); + TxLock::new(&wallet, amount, A, B, change) + .await + .unwrap() + .into() } fn alice_and_bob() -> (PublicKey, PublicKey) { diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 1447930c..18c72329 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -16,7 +16,6 @@ use reqwest::Url; use rust_decimal::prelude::*; use rust_decimal::Decimal; use rust_decimal_macros::dec; -use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; use std::fmt; @@ -310,11 +309,18 @@ where &self, address: Address, amount: Amount, + change_override: Option
, ) -> Result { if self.network != address.network { bail!("Cannot build PSBT because network of given address is {} but wallet is on network {}", address.network, self.network); } + if let Some(change) = change_override.as_ref() { + if self.network != change.network { + bail!("Cannot build PSBT because network of given address is {} but wallet is on network {}", change.network, self.network); + } + } + let wallet = self.wallet.lock().await; let client = self.client.lock().await; let fee_rate = client.estimate_feerate(self.target_block)?; @@ -326,22 +332,31 @@ where let (psbt, _details) = tx_builder.finish()?; let mut psbt: PartiallySignedTransaction = psbt; - // When subscribing to transactions we depend on the relevant script being at - // output index 0, thus we ensure the relevant output to be at index `0`. - psbt.outputs.sort_by(|a, _| { - if a.witness_script.as_ref() == Some(&script) { - Ordering::Less - } else { - Ordering::Greater + match psbt.global.unsigned_tx.output.as_mut_slice() { + // our primary output is the 2nd one? reverse the vectors + [_, second_txout] if second_txout.script_pubkey == script => { + psbt.outputs.reverse(); + psbt.global.unsigned_tx.output.reverse(); + } + [first_txout, _] if first_txout.script_pubkey == script => { + // no need to do anything } - }); - psbt.global.unsigned_tx.output.sort_by(|a, _| { - if a.script_pubkey == script { - Ordering::Less - } else { - Ordering::Greater + [_] => { + // single output, no need do anything } - }); + _ => bail!("Unexpected transaction layout"), + } + + if let ([_, change], [_, psbt_output], Some(change_override)) = ( + &mut psbt.global.unsigned_tx.output.as_mut_slice(), + &mut psbt.outputs.as_mut_slice(), + change_override, + ) { + change.script_pubkey = change_override.script_pubkey(); + // Might be populated based on the previously set change address, but for the + // overwrite we don't know unless we ask the user for more information. + psbt_output.bip32_derivation.clear(); + } Ok(psbt) } @@ -1062,7 +1077,8 @@ mod tests { // if the change output is below dust it will be dropped by the BDK for amount in above_dust..(balance - (above_dust - 1)) { let (A, B) = (PublicKey::random(), PublicKey::random()); - let txlock = TxLock::new(&wallet, bitcoin::Amount::from_sat(amount), A, B) + let change = wallet.new_address().await.unwrap(); + let txlock = TxLock::new(&wallet, bitcoin::Amount::from_sat(amount), A, B, change) .await .unwrap(); let txlock_output = txlock.script_pubkey(); @@ -1077,4 +1093,30 @@ mod tests { ); } } + + #[tokio::test] + async fn can_override_change_address() { + let wallet = Wallet::new_funded_default_fees(50_000); + let custom_change = "bcrt1q08pfqpsyrt7acllzyjm8q5qsz5capvyahm49rw" + .parse::
() + .unwrap(); + + let psbt = wallet + .send_to_address( + wallet.new_address().await.unwrap(), + Amount::from_sat(10_000), + Some(custom_change.clone()), + ) + .await + .unwrap(); + let transaction = wallet.sign_and_finalize(psbt).await.unwrap(); + + match transaction.output.as_slice() { + [first, change] => { + assert_eq!(first.value, 10_000); + assert_eq!(change.script_pubkey, custom_change.script_pubkey()); + } + _ => panic!("expected exactly two outputs"), + } + } } diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 08896045..87b77523 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -74,11 +74,11 @@ where bitcoin_electrum_rpc_url, bitcoin_target_block, }, - monero: - Monero { - monero_receive_address, - monero_daemon_address, - }, + bitcoin_change_address, + monero: Monero { + monero_daemon_address, + }, + monero_receive_address, tor: Tor { tor_socks5_port }, } => Arguments { env_config: env_config_from(is_testnet), @@ -92,6 +92,7 @@ where is_testnet, )?, bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet), + bitcoin_change_address, monero_receive_address: validate_monero_address( monero_receive_address, is_testnet, @@ -117,11 +118,9 @@ where bitcoin_electrum_rpc_url, bitcoin_target_block, }, - monero: - Monero { - monero_receive_address, - monero_daemon_address, - }, + monero: Monero { + monero_daemon_address, + }, tor: Tor { tor_socks5_port }, } => Arguments { env_config: env_config_from(is_testnet), @@ -135,7 +134,6 @@ where is_testnet, )?, bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet), - monero_receive_address, monero_daemon_address: monero_daemon_address_from( monero_daemon_address, is_testnet, @@ -214,6 +212,7 @@ pub enum Command { seller: Multiaddr, bitcoin_electrum_rpc_url: Url, bitcoin_target_block: usize, + bitcoin_change_address: bitcoin::Address, monero_receive_address: monero::Address, monero_daemon_address: String, tor_socks5_port: u16, @@ -223,7 +222,6 @@ pub enum Command { swap_id: Uuid, bitcoin_electrum_rpc_url: Url, bitcoin_target_block: usize, - monero_receive_address: monero::Address, monero_daemon_address: String, tor_socks5_port: u16, }, @@ -287,9 +285,21 @@ pub enum RawCommand { #[structopt(flatten)] bitcoin: Bitcoin, + #[structopt( + long = "change-address", + help = "The bitcoin address where any form of change or excess funds should be sent to" + )] + bitcoin_change_address: bitcoin::Address, + #[structopt(flatten)] monero: Monero, + #[structopt(long = "receive-address", + help = "The monero address where you would like to receive monero", + parse(try_from_str = parse_monero_address) + )] + monero_receive_address: monero::Address, + #[structopt(flatten)] tor: Tor, }, @@ -346,12 +356,6 @@ pub enum RawCommand { #[derive(structopt::StructOpt, Debug)] pub struct Monero { - #[structopt(long = "receive-address", - help = "Provide the monero address where you would like to receive monero", - parse(try_from_str = parse_monero_address) - )] - pub monero_receive_address: monero::Address, - #[structopt( long = "monero-daemon-address", help = "Specify to connect to a monero daemon of your choice: :" @@ -520,7 +524,9 @@ mod tests { const MAINNET: &str = "mainnet"; const MONERO_STAGENET_ADDRESS: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"; + const BITCOIN_TESTNET_ADDRESS: &str = "tb1qr3em6k3gfnyl8r7q0v7t4tlnyxzgxma3lressv"; const MONERO_MAINNET_ADDRESS: &str = "44Ato7HveWidJYUAVw5QffEcEtSH1DwzSP3FPPkHxNAS4LX9CqgucphTisH978FLHE34YNEx7FcbBfQLQUU8m3NUC4VqsRa"; + const BITCOIN_MAINNET_ADDRESS: &str = "bc1qe4epnfklcaa0mun26yz5g8k24em5u9f92hy325"; const MULTI_ADDRESS: &str = "/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b"; @@ -532,6 +538,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_MAINNET_ADDRESS, + "--change-address", + BITCOIN_MAINNET_ADDRESS, "--seller", MULTI_ADDRESS, ]; @@ -550,6 +558,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_STAGENET_ADDRESS, + "--change-address", + BITCOIN_TESTNET_ADDRESS, "--seller", MULTI_ADDRESS, ]; @@ -569,6 +579,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_STAGENET_ADDRESS, + "--change-address", + BITCOIN_TESTNET_ADDRESS, "--seller", MULTI_ADDRESS, ]; @@ -592,6 +604,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_MAINNET_ADDRESS, + "--change-address", + BITCOIN_MAINNET_ADDRESS, "--seller", MULTI_ADDRESS, ]; @@ -609,14 +623,7 @@ mod tests { #[test] fn given_resume_on_mainnet_then_defaults_to_mainnet() { - let raw_ars = vec![ - BINARY_NAME, - "resume", - "--swap-id", - SWAP_ID, - "--receive-address", - MONERO_MAINNET_ADDRESS, - ]; + let raw_ars = vec![BINARY_NAME, "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -628,15 +635,7 @@ mod tests { #[test] fn given_resume_on_testnet_then_defaults_to_testnet() { - let raw_ars = vec![ - BINARY_NAME, - "--testnet", - "resume", - "--swap-id", - SWAP_ID, - "--receive-address", - MONERO_STAGENET_ADDRESS, - ]; + let raw_ars = vec![BINARY_NAME, "--testnet", "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -703,6 +702,8 @@ mod tests { "--data-dir", data_dir, "buy-xmr", + "--change-address", + BITCOIN_MAINNET_ADDRESS, "--receive-address", MONERO_MAINNET_ADDRESS, "--seller", @@ -725,6 +726,8 @@ mod tests { "--data-dir", data_dir, "buy-xmr", + "--change-address", + BITCOIN_TESTNET_ADDRESS, "--receive-address", MONERO_STAGENET_ADDRESS, "--seller", @@ -748,8 +751,6 @@ mod tests { "resume", "--swap-id", SWAP_ID, - "--receive-address", - MONERO_MAINNET_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -770,8 +771,6 @@ mod tests { "resume", "--swap-id", SWAP_ID, - "--receive-address", - MONERO_STAGENET_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -791,6 +790,8 @@ mod tests { BINARY_NAME, "--debug", "buy-xmr", + "--change-address", + BITCOIN_MAINNET_ADDRESS, "--receive-address", MONERO_MAINNET_ADDRESS, "--seller", @@ -808,6 +809,8 @@ mod tests { "--testnet", "--debug", "buy-xmr", + "--change-address", + BITCOIN_TESTNET_ADDRESS, "--receive-address", MONERO_STAGENET_ADDRESS, "--seller", @@ -820,15 +823,7 @@ mod tests { ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().with_debug()) ); - let raw_ars = vec![ - BINARY_NAME, - "--debug", - "resume", - "--swap-id", - SWAP_ID, - "--receive-address", - MONERO_MAINNET_ADDRESS, - ]; + let raw_ars = vec![BINARY_NAME, "--debug", "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); assert_eq!( @@ -843,8 +838,6 @@ mod tests { "resume", "--swap-id", SWAP_ID, - "--receive-address", - MONERO_STAGENET_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -860,6 +853,8 @@ mod tests { BINARY_NAME, "--json", "buy-xmr", + "--change-address", + BITCOIN_MAINNET_ADDRESS, "--receive-address", MONERO_MAINNET_ADDRESS, "--seller", @@ -877,6 +872,8 @@ mod tests { "--testnet", "--json", "buy-xmr", + "--change-address", + BITCOIN_TESTNET_ADDRESS, "--receive-address", MONERO_STAGENET_ADDRESS, "--seller", @@ -889,15 +886,7 @@ mod tests { ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().with_json()) ); - let raw_ars = vec![ - BINARY_NAME, - "--json", - "resume", - "--swap-id", - SWAP_ID, - "--receive-address", - MONERO_MAINNET_ADDRESS, - ]; + let raw_ars = vec![BINARY_NAME, "--json", "resume", "--swap-id", SWAP_ID]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); assert_eq!( @@ -912,8 +901,6 @@ mod tests { "resume", "--swap-id", SWAP_ID, - "--receive-address", - MONERO_STAGENET_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -935,6 +922,7 @@ mod tests { bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) .unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, + bitcoin_change_address: BITCOIN_TESTNET_ADDRESS.parse().unwrap(), monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS) .unwrap(), monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(), @@ -953,6 +941,7 @@ mod tests { seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(), bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, + bitcoin_change_address: BITCOIN_MAINNET_ADDRESS.parse().unwrap(), monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS) .unwrap(), monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(), @@ -972,8 +961,6 @@ mod tests { bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) .unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, - monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS) - .unwrap(), monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(), tor_socks5_port: DEFAULT_SOCKS5_PORT, }, @@ -990,8 +977,6 @@ mod tests { swap_id: Uuid::from_str(SWAP_ID).unwrap(), bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, - monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS) - .unwrap(), monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(), tor_socks5_port: DEFAULT_SOCKS5_PORT, }, diff --git a/swap/src/database.rs b/swap/src/database.rs index 34225c2f..339a1e35 100644 --- a/swap/src/database.rs +++ b/swap/src/database.rs @@ -69,6 +69,7 @@ pub struct Database { swaps: sled::Tree, peers: sled::Tree, addresses: sled::Tree, + monero_addresses: sled::Tree, } impl Database { @@ -81,11 +82,13 @@ impl Database { let swaps = db.open_tree("swaps")?; let peers = db.open_tree("peers")?; let addresses = db.open_tree("addresses")?; + let monero_addresses = db.open_tree("monero_addresses")?; Ok(Database { swaps, peers, addresses, + monero_addresses, }) } @@ -116,6 +119,39 @@ impl Database { Ok(PeerId::from_str(peer_id.as_str())?) } + pub async fn insert_monero_address( + &self, + swap_id: Uuid, + address: monero::Address, + ) -> Result<()> { + let key = swap_id.as_bytes(); + let value = serialize(&address)?; + + self.monero_addresses.insert(key, value)?; + + self.monero_addresses + .flush_async() + .await + .map(|_| ()) + .context("Could not flush db") + } + + pub fn get_monero_address(&self, swap_id: Uuid) -> Result { + let encoded = self + .monero_addresses + .get(swap_id.as_bytes())? + .ok_or_else(|| { + anyhow!( + "No Monero address found for swap id {} in database", + swap_id + ) + })?; + + let monero_address = deserialize(&encoded)?; + + Ok(monero_address) + } + pub async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> { let key = peer_id.to_bytes(); @@ -425,4 +461,17 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn save_and_load_monero_address() -> Result<()> { + let db_dir = tempfile::tempdir()?; + let swap_id = Uuid::new_v4(); + + Database::open(db_dir.path())?.insert_monero_address(swap_id, "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a".parse()?).await?; + let loaded_monero_address = Database::open(db_dir.path())?.get_monero_address(swap_id)?; + + assert_eq!(loaded_monero_address.to_string(), "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"); + + Ok(()) + } } diff --git a/swap/src/database/bob.rs b/swap/src/database/bob.rs index b0258b00..82c6b848 100644 --- a/swap/src/database/bob.rs +++ b/swap/src/database/bob.rs @@ -3,13 +3,17 @@ use crate::protocol::bob; use crate::protocol::bob::BobState; use monero_rpc::wallet::BlockHeight; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; use std::fmt; +#[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum Bob { Started { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc_amount: bitcoin::Amount, + #[serde_as(as = "DisplayFromStr")] + change_address: bitcoin::Address, }, ExecutionSetupDone { state2: bob::State2, @@ -45,7 +49,13 @@ pub enum BobEndState { impl From for Bob { fn from(bob_state: BobState) -> Self { match bob_state { - BobState::Started { btc_amount } => Bob::Started { btc_amount }, + BobState::Started { + btc_amount, + change_address, + } => Bob::Started { + btc_amount, + change_address, + }, BobState::SwapSetupCompleted(state2) => Bob::ExecutionSetupDone { state2 }, BobState::BtcLocked(state3) => Bob::BtcLocked { state3 }, BobState::XmrLockProofReceived { @@ -77,7 +87,13 @@ impl From for Bob { impl From for BobState { fn from(db_state: Bob) -> Self { match db_state { - Bob::Started { btc_amount } => BobState::Started { btc_amount }, + Bob::Started { + btc_amount, + change_address, + } => BobState::Started { + btc_amount, + change_address, + }, Bob::ExecutionSetupDone { state2 } => BobState::SwapSetupCompleted(state2), Bob::BtcLocked { state3 } => BobState::BtcLocked(state3), Bob::XmrLockProofReceived { diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 347f86d8..dc8f7aec 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -20,7 +20,7 @@ pub struct Swap { pub monero_wallet: Arc, pub env_config: env::Config, pub id: Uuid, - pub receive_monero_address: monero::Address, + pub monero_receive_address: monero::Address, } impl Swap { @@ -32,21 +32,26 @@ impl Swap { monero_wallet: Arc, env_config: env::Config, event_loop_handle: cli::EventLoopHandle, - receive_monero_address: monero::Address, + monero_receive_address: monero::Address, + bitcoin_change_address: bitcoin::Address, btc_amount: bitcoin::Amount, ) -> Self { Self { - state: BobState::Started { btc_amount }, + state: BobState::Started { + btc_amount, + change_address: bitcoin_change_address, + }, event_loop_handle, db, bitcoin_wallet, monero_wallet, env_config, id, - receive_monero_address, + monero_receive_address, } } + #[allow(clippy::too_many_arguments)] pub fn from_db( db: Database, id: Uuid, @@ -54,7 +59,7 @@ impl Swap { monero_wallet: Arc, env_config: env::Config, event_loop_handle: cli::EventLoopHandle, - receive_monero_address: monero::Address, + monero_receive_address: monero::Address, ) -> Result { let state = db.get_state(id)?.try_into_bob()?.into(); @@ -66,7 +71,7 @@ impl Swap { monero_wallet, env_config, id, - receive_monero_address, + monero_receive_address, }) } } diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index e80634b6..8e0bac9a 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -25,6 +25,7 @@ use uuid::Uuid; pub enum BobState { Started { btc_amount: bitcoin::Amount, + change_address: bitcoin::Address, }, SwapSetupCompleted(State2), BtcLocked(State3), @@ -169,7 +170,14 @@ impl State0 { bail!("Alice's dleq proof doesn't verify") } - let tx_lock = bitcoin::TxLock::new(wallet, self.btc, msg.A, self.b.public()).await?; + let tx_lock = bitcoin::TxLock::new( + wallet, + self.btc, + msg.A, + self.b.public(), + self.refund_address.clone(), + ) + .await?; let v = msg.v_a + self.v_b; Ok(State1 { diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 23ab6388..9083b56e 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -37,7 +37,7 @@ pub async fn run_until( &mut swap.event_loop_handle, swap.bitcoin_wallet.as_ref(), swap.monero_wallet.as_ref(), - swap.receive_monero_address, + swap.monero_receive_address, ) .await?; @@ -56,13 +56,15 @@ async fn next_state( event_loop_handle: &mut EventLoopHandle, bitcoin_wallet: &bitcoin::Wallet, monero_wallet: &monero::Wallet, - receive_monero_address: monero::Address, + monero_receive_address: monero::Address, ) -> Result { tracing::trace!(%state, "Advancing state"); Ok(match state { - BobState::Started { btc_amount } => { - let bitcoin_refund_address = bitcoin_wallet.new_address().await?; + BobState::Started { + btc_amount, + change_address, + } => { let tx_refund_fee = bitcoin_wallet .estimate_fee(TxRefund::weight(), btc_amount) .await?; @@ -76,7 +78,7 @@ async fn next_state( btc: btc_amount, tx_refund_fee, tx_cancel_fee, - bitcoin_refund_address, + bitcoin_refund_address: change_address, }) .await?; @@ -224,10 +226,10 @@ async fn next_state( // Ensure that the generated wallet is synced so we have a proper balance monero_wallet.refresh().await?; // Sweep (transfer all funds) to the given address - let tx_hashes = monero_wallet.sweep_all(receive_monero_address).await?; + let tx_hashes = monero_wallet.sweep_all(monero_receive_address).await?; for tx_hash in tx_hashes { - tracing::info!(%receive_monero_address, txid=%tx_hash.0, "Sent XMR to"); + tracing::info!(%monero_receive_address, txid=%tx_hash.0, "Sent XMR to"); } BobState::XmrRedeemed { diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 7a03db2d..ad43bd9e 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -434,6 +434,7 @@ impl BobParams { self.env_config, handle, self.monero_wallet.get_main_address(), + self.bitcoin_wallet.new_address().await?, btc_amount, );