From 7d324d966a28d1c67ae881dac3004a60011fc1fe Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 25 Feb 2021 11:51:43 +1100 Subject: [PATCH 1/7] Remove `syncing` wallet log BDK already has a log line for the sync that we could enable if we wanted such a log. Additionally, _we_ are not actually syncing the wallet, bdk is so our log line was lying. It should have said "calling bdk to sync wallet". --- swap/src/bitcoin/wallet.rs | 1 - swap/src/trace.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index d157109f..6f054a7c 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -111,7 +111,6 @@ impl Wallet { } pub async fn sync_wallet(&self) -> Result<()> { - tracing::debug!("syncing wallet"); self.inner.lock().await.sync(noop_progress(), None)?; Ok(()) } diff --git a/swap/src/trace.rs b/swap/src/trace.rs index 60be6213..6ae1d6ef 100644 --- a/swap/src/trace.rs +++ b/swap/src/trace.rs @@ -16,7 +16,7 @@ pub fn init_tracing(level: LevelFilter) -> Result<()> { let subscriber = FmtSubscriber::builder() .with_env_filter(format!( "swap={},monero_harness={},bitcoin_harness={},http=warn,warp=warn", - level, level, level, + level, level, level )) .with_writer(std::io::stderr) .with_ansi(is_terminal) From 1876d17ba456bcbc03b8854f5ed2444243ade75a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 25 Feb 2021 12:51:30 +1100 Subject: [PATCH 2/7] Remove `map_err` in favor of `?` `?` maps the error automatically. --- swap/src/bitcoin/wallet.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 6f054a7c..74fc4983 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -82,11 +82,9 @@ impl Wallet { } pub async fn new_address(&self) -> Result
{ - self.inner - .lock() - .await - .get_new_address() - .map_err(Into::into) + let address = self.inner.lock().await.get_new_address()?; + + Ok(address) } pub async fn get_tx(&self, txid: Txid) -> Result> { From 0f8fbd087ff35c5072d7f1b5ad51a38d01496400 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 25 Feb 2021 13:22:03 +1100 Subject: [PATCH 3/7] Make all fields of `bitcoin::Wallet` private This reveals that the `network` field is actually unused. --- swap/src/bitcoin/wallet.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 74fc4983..2e5badd3 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -38,10 +38,9 @@ enum Error { } pub struct Wallet { - pub inner: Arc>>, - pub network: bitcoin::Network, - pub http_url: Url, - pub rpc_url: Url, + inner: Arc>>, + http_url: Url, + rpc_url: Url, } impl Wallet { @@ -70,7 +69,6 @@ impl Wallet { Ok(Self { inner: Arc::new(Mutex::new(bdk_wallet)), - network, http_url: electrum_http_url, rpc_url: electrum_rpc_url, }) From 6c38d668643a8e1000947411ae05a06ef0234ccd Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 25 Feb 2021 13:52:05 +1100 Subject: [PATCH 4/7] Remove `Tx` arguments from `add_signatures` functions The only reason we need this argument is because we need to access the output descriptor. We can save that one ahead of time at when we construct the type. --- swap/src/bitcoin/cancel.rs | 6 +++--- swap/src/bitcoin/punish.rs | 8 ++++---- swap/src/bitcoin/redeem.rs | 8 ++++---- swap/src/bitcoin/refund.rs | 8 ++++---- swap/src/database/alice.rs | 2 +- swap/src/protocol/alice/state.rs | 2 +- swap/src/protocol/alice/steps.rs | 8 ++++---- swap/src/protocol/alice/swap.rs | 6 +++--- swap/src/protocol/bob/state.rs | 11 ++++------- 9 files changed, 28 insertions(+), 31 deletions(-) diff --git a/swap/src/bitcoin/cancel.rs b/swap/src/bitcoin/cancel.rs index 1fd31777..d2187d45 100644 --- a/swap/src/bitcoin/cancel.rs +++ b/swap/src/bitcoin/cancel.rs @@ -58,6 +58,7 @@ pub struct TxCancel { inner: Transaction, digest: SigHash, pub(in crate::bitcoin) output_descriptor: Descriptor<::bitcoin::PublicKey>, + lock_output_descriptor: Descriptor<::bitcoin::PublicKey>, } impl TxCancel { @@ -99,6 +100,7 @@ impl TxCancel { inner: transaction, digest, output_descriptor: cancel_output_descriptor, + lock_output_descriptor: tx_lock.output_descriptor.clone(), } } @@ -120,7 +122,6 @@ impl TxCancel { pub fn add_signatures( self, - tx_lock: &TxLock, (A, sig_a): (PublicKey, Signature), (B, sig_b): (PublicKey, Signature), ) -> Result { @@ -144,8 +145,7 @@ impl TxCancel { }; let mut tx_cancel = self.inner; - tx_lock - .output_descriptor + self.lock_output_descriptor .satisfy(&mut tx_cancel.input[0], satisfier)?; Ok(tx_cancel) diff --git a/swap/src/bitcoin/punish.rs b/swap/src/bitcoin/punish.rs index c30e3448..08c0f0b9 100644 --- a/swap/src/bitcoin/punish.rs +++ b/swap/src/bitcoin/punish.rs @@ -2,13 +2,14 @@ use crate::bitcoin::{Address, PublicKey, PunishTimelock, Transaction, TxCancel}; use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType}; use anyhow::Result; use ecdsa_fun::Signature; -use miniscript::DescriptorTrait; +use miniscript::{Descriptor, DescriptorTrait}; use std::collections::HashMap; #[derive(Debug)] pub struct TxPunish { inner: Transaction, digest: SigHash, + cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>, } impl TxPunish { @@ -29,6 +30,7 @@ impl TxPunish { Self { inner: tx_punish, digest, + cancel_output_descriptor: tx_cancel.output_descriptor.clone(), } } @@ -38,7 +40,6 @@ impl TxPunish { pub fn add_signatures( self, - tx_cancel: &TxCancel, (A, sig_a): (PublicKey, Signature), (B, sig_b): (PublicKey, Signature), ) -> Result { @@ -62,8 +63,7 @@ impl TxPunish { }; let mut tx_punish = self.inner; - tx_cancel - .output_descriptor + self.cancel_output_descriptor .satisfy(&mut tx_punish.input[0], satisfier)?; Ok(tx_punish) diff --git a/swap/src/bitcoin/redeem.rs b/swap/src/bitcoin/redeem.rs index c9ed27ad..081b0094 100644 --- a/swap/src/bitcoin/redeem.rs +++ b/swap/src/bitcoin/redeem.rs @@ -5,13 +5,14 @@ use crate::bitcoin::{ use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType, Txid}; use anyhow::{bail, Context, Result}; use ecdsa_fun::Signature; -use miniscript::DescriptorTrait; +use miniscript::{Descriptor, DescriptorTrait}; use std::collections::HashMap; #[derive(Debug, Clone)] pub struct TxRedeem { inner: Transaction, digest: SigHash, + lock_output_descriptor: Descriptor<::bitcoin::PublicKey>, } impl TxRedeem { @@ -30,6 +31,7 @@ impl TxRedeem { Self { inner: tx_redeem, digest, + lock_output_descriptor: tx_lock.output_descriptor.clone(), } } @@ -43,7 +45,6 @@ impl TxRedeem { pub fn add_signatures( self, - tx_lock: &TxLock, (A, sig_a): (PublicKey, Signature), (B, sig_b): (PublicKey, Signature), ) -> Result { @@ -67,8 +68,7 @@ impl TxRedeem { }; let mut tx_redeem = self.inner; - tx_lock - .output_descriptor + self.lock_output_descriptor .satisfy(&mut tx_redeem.input[0], satisfier)?; Ok(tx_redeem) diff --git a/swap/src/bitcoin/refund.rs b/swap/src/bitcoin/refund.rs index 18c6af12..e5124e7f 100644 --- a/swap/src/bitcoin/refund.rs +++ b/swap/src/bitcoin/refund.rs @@ -5,13 +5,14 @@ use crate::bitcoin::{ use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType, Txid}; use anyhow::{bail, Context, Result}; use ecdsa_fun::Signature; -use miniscript::DescriptorTrait; +use miniscript::{Descriptor, DescriptorTrait}; use std::collections::HashMap; #[derive(Debug)] pub struct TxRefund { inner: Transaction, digest: SigHash, + cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>, } impl TxRefund { @@ -28,6 +29,7 @@ impl TxRefund { Self { inner: tx_punish, digest, + cancel_output_descriptor: tx_cancel.output_descriptor.clone(), } } @@ -41,7 +43,6 @@ impl TxRefund { pub fn add_signatures( self, - tx_cancel: &TxCancel, (A, sig_a): (PublicKey, Signature), (B, sig_b): (PublicKey, Signature), ) -> Result { @@ -65,8 +66,7 @@ impl TxRefund { }; let mut tx_refund = self.inner; - tx_cancel - .output_descriptor + self.cancel_output_descriptor .satisfy(&mut tx_refund.input[0], satisfier)?; Ok(tx_refund) diff --git a/swap/src/database/alice.rs b/swap/src/database/alice.rs index 61a785af..8a5ba68f 100644 --- a/swap/src/database/alice.rs +++ b/swap/src/database/alice.rs @@ -205,7 +205,7 @@ impl From for AliceState { let tx_refund = TxRefund::new(&tx_cancel, &state3.refund_address); AliceState::BtcPunishable { monero_wallet_restore_blockheight, - tx_refund, + tx_refund: Box::new(tx_refund), state3: Box::new(state3), } } diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index ade62fe1..bc578a4d 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -53,7 +53,7 @@ pub enum AliceState { }, BtcPunishable { monero_wallet_restore_blockheight: BlockHeight, - tx_refund: TxRefund, + tx_refund: Box, state3: Box, }, XmrRefunded, diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index 411abd68..b77479c2 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -124,7 +124,7 @@ pub fn build_bitcoin_redeem_transaction( let sig_b = adaptor.decrypt_signature(&s_a, encrypted_signature); let tx = tx_redeem - .add_signatures(&tx_lock, (a.public(), sig_a), (B, sig_b)) + .add_signatures((a.public(), sig_a), (B, sig_b)) .context("sig_{a,b} are invalid for tx_redeem")?; Ok(tx) @@ -179,7 +179,7 @@ where let tx_cancel = tx_cancel .clone() - .add_signatures(&tx_lock, (a.public(), sig_a), (B, sig_b)) + .add_signatures((a.public(), sig_a), (B, sig_b)) .expect("sig_{a,b} to be valid signatures for tx_cancel"); // TODO(Franck): Error handling is delicate, why can't we broadcast? @@ -224,7 +224,7 @@ where pub fn extract_monero_private_key( published_refund_tx: bitcoin::Transaction, - tx_refund: TxRefund, + tx_refund: &TxRefund, s_a: monero::Scalar, a: bitcoin::SecretKey, S_b_bitcoin: bitcoin::PublicKey, @@ -261,7 +261,7 @@ pub fn build_bitcoin_punish_transaction( let sig_b = tx_punish_sig_bob; let signed_tx_punish = tx_punish - .add_signatures(&tx_cancel, (a.public(), sig_a), (B, sig_b)) + .add_signatures((a.public(), sig_a), (B, sig_b)) .expect("sig_{a,b} to be valid signatures for tx_cancel"); Ok(signed_tx_punish) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 55484983..ad83b12e 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -343,7 +343,7 @@ async fn run_until_internal( match published_refund_tx { None => { let state = AliceState::BtcPunishable { - tx_refund, + tx_refund: Box::new(tx_refund), state3, monero_wallet_restore_blockheight, }; @@ -366,7 +366,7 @@ async fn run_until_internal( Some(published_refund_tx) => { let spend_key = extract_monero_private_key( published_refund_tx, - tx_refund, + &tx_refund, state3.s_a, state3.a.clone(), state3.S_b_bitcoin, @@ -445,7 +445,7 @@ async fn run_until_internal( Either::Left((published_refund_tx, _)) => { let spend_key = extract_monero_private_key( published_refund_tx?, - tx_refund, + &tx_refund, state3.s_a, state3.a.clone(), state3.S_b_bitcoin, diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 3387eb35..e41debfd 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -459,7 +459,7 @@ impl State4 { let tx_cancel = tx_cancel .clone() - .add_signatures(&self.tx_lock, (self.A, sig_a), (self.b.public(), sig_b)) + .add_signatures((self.A, sig_a), (self.b.public(), sig_b)) .expect( "sig_{a,b} to be valid signatures for tx_cancel", @@ -482,7 +482,7 @@ impl State4 { let tx_cancel = tx_cancel .clone() - .add_signatures(&self.tx_lock, (self.A, sig_a), (self.b.public(), sig_b)) + .add_signatures((self.A, sig_a), (self.b.public(), sig_b)) .expect( "sig_{a,b} to be valid signatures for tx_cancel", @@ -562,11 +562,8 @@ impl State4 { let sig_a = adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), self.tx_refund_encsig.clone()); - let signed_tx_refund = tx_refund.add_signatures( - &tx_cancel.clone(), - (self.A, sig_a), - (self.b.public(), sig_b), - )?; + let signed_tx_refund = + tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; let txid = bitcoin_wallet .broadcast_signed_transaction(signed_tx_refund) From 67fe01a2efa5d26e0cb53ff3ff43649cb6914cbe Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Feb 2021 10:14:43 +1100 Subject: [PATCH 5/7] Remove `BuildTxLockPsbt` and `GetNetwork` traits These traits were only used once within the `TxLock` constructor. Looking at the rest of the codebase, we don't really seem to follow any abstractions here where the protocol shouldn't know about the exact types that is being passed in. As such, these types are just noise and might as well be removed in favor of simplicity. --- swap/src/bitcoin.rs | 18 +----------------- swap/src/bitcoin/lock.rs | 8 ++------ swap/src/bitcoin/wallet.rs | 22 ++++++++-------------- swap/src/protocol/bob/state.rs | 10 +++------- 4 files changed, 14 insertions(+), 44 deletions(-) diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index a6d06006..05d4c7cb 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -22,9 +22,7 @@ pub use wallet::Wallet; use crate::execution_params::ExecutionParams; use ::bitcoin::{ hashes::{hex::ToHex, Hash}, - secp256k1, - util::psbt::PartiallySignedTransaction, - SigHash, + secp256k1, SigHash, }; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; @@ -203,15 +201,6 @@ pub fn build_shared_output_descriptor(A: Point, B: Point) -> Descriptor Result; -} - #[async_trait] pub trait SignTxLock { async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result; @@ -251,11 +240,6 @@ pub trait GetRawTransaction { async fn get_raw_transaction(&self, txid: Txid) -> Result; } -#[async_trait] -pub trait GetNetwork { - async fn get_network(&self) -> Network; -} - pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result { let adaptor = Adaptor::, Deterministic>::default(); diff --git a/swap/src/bitcoin/lock.rs b/swap/src/bitcoin/lock.rs index d817f007..c44143c2 100644 --- a/swap/src/bitcoin/lock.rs +++ b/swap/src/bitcoin/lock.rs @@ -1,6 +1,5 @@ use crate::bitcoin::{ - build_shared_output_descriptor, Address, Amount, BuildTxLockPsbt, GetNetwork, PublicKey, - Transaction, TX_FEE, + build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, Wallet, TX_FEE, }; use ::bitcoin::{util::psbt::PartiallySignedTransaction, OutPoint, TxIn, TxOut, Txid}; use anyhow::Result; @@ -14,10 +13,7 @@ pub struct TxLock { } impl TxLock { - pub async fn new(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result - where - W: BuildTxLockPsbt + GetNetwork, - { + pub async fn new(wallet: &Wallet, amount: Amount, A: PublicKey, B: PublicKey) -> Result { let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0); let address = lock_output_descriptor .address(wallet.get_network().await) diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 2e5badd3..6a786dd1 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -1,8 +1,8 @@ use crate::{ bitcoin::{ - timelocks::BlockHeight, Address, Amount, BroadcastSignedTransaction, BuildTxLockPsbt, - GetBlockHeight, GetNetwork, GetRawTransaction, SignTxLock, Transaction, - TransactionBlockHeight, TxLock, WaitForTransactionFinality, WatchForRawTransaction, + timelocks::BlockHeight, Address, Amount, BroadcastSignedTransaction, GetBlockHeight, + GetRawTransaction, SignTxLock, Transaction, TransactionBlockHeight, TxLock, + WaitForTransactionFinality, WatchForRawTransaction, }, execution_params::ExecutionParams, }; @@ -110,11 +110,8 @@ impl Wallet { self.inner.lock().await.sync(noop_progress(), None)?; Ok(()) } -} -#[async_trait] -impl BuildTxLockPsbt for Wallet { - async fn build_tx_lock_psbt( + pub async fn build_tx_lock_psbt( &self, output_address: Address, output_amount: Amount, @@ -129,6 +126,10 @@ impl BuildTxLockPsbt for Wallet { tracing::debug!("tx lock built"); Ok(psbt) } + + pub async fn get_network(&self) -> bitcoin::Network { + self.inner.lock().await.network() + } } #[async_trait] @@ -283,13 +284,6 @@ impl WaitForTransactionFinality for Wallet { } } -#[async_trait] -impl GetNetwork for Wallet { - async fn get_network(&self) -> bitcoin::Network { - self.inner.lock().await.network() - } -} - fn tx_status_url(txid: Txid, base_url: &Url) -> Result { let url = base_url.join(&format!("tx/{}/status", txid))?; Ok(url) diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index e41debfd..10cdc378 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -1,9 +1,8 @@ use crate::{ bitcoin::{ self, current_epoch, wait_for_cancel_timelock_to_expire, BroadcastSignedTransaction, - BuildTxLockPsbt, CancelTimelock, ExpiredTimelocks, GetBlockHeight, GetNetwork, - GetRawTransaction, PunishTimelock, Transaction, TransactionBlockHeight, TxCancel, Txid, - WatchForRawTransaction, + CancelTimelock, ExpiredTimelocks, GetBlockHeight, GetRawTransaction, PunishTimelock, + Transaction, TransactionBlockHeight, TxCancel, Txid, WatchForRawTransaction, }, execution_params::ExecutionParams, monero, @@ -140,10 +139,7 @@ impl State0 { } } - pub async fn receive(self, wallet: &W, msg: Message1) -> Result - where - W: BuildTxLockPsbt + GetNetwork, - { + pub async fn receive(self, wallet: &bitcoin::Wallet, msg: Message1) -> Result { let valid = CROSS_CURVE_PROOF_SYSTEM.verify( &msg.dleq_proof_s_a, ( From 32cb0eb896febed4900ac94a732f9dfeb727fee1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Feb 2021 10:18:47 +1100 Subject: [PATCH 6/7] Rename `build_tx_lock_psbt` to `send_to_address` Being defined on the wallet itself, a more generic name fits better on what this function actually does. --- swap/src/bitcoin/lock.rs | 2 +- swap/src/bitcoin/wallet.rs | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/swap/src/bitcoin/lock.rs b/swap/src/bitcoin/lock.rs index c44143c2..46666fe3 100644 --- a/swap/src/bitcoin/lock.rs +++ b/swap/src/bitcoin/lock.rs @@ -19,7 +19,7 @@ impl TxLock { .address(wallet.get_network().await) .expect("can derive address from descriptor"); - let psbt = wallet.build_tx_lock_psbt(address, amount).await?; + let psbt = wallet.send_to_address(address, amount).await?; Ok(Self { inner: psbt, diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 6a786dd1..212c1bd9 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -111,19 +111,18 @@ impl Wallet { Ok(()) } - pub async fn build_tx_lock_psbt( + pub async fn send_to_address( &self, - output_address: Address, - output_amount: Amount, + address: Address, + amount: Amount, ) -> Result { - tracing::debug!("building tx lock"); let wallet = self.inner.lock().await; let mut tx_builder = wallet.build_tx(); - tx_builder.add_recipient(output_address.script_pubkey(), output_amount.as_sat()); - tx_builder.fee_rate(FeeRate::from_sat_per_vb(5.0)); // todo: get actual fee + tx_builder.add_recipient(address.script_pubkey(), amount.as_sat()); + tx_builder.fee_rate(FeeRate::from_sat_per_vb(5.0)); // todo: make dynamic let (psbt, _details) = tx_builder.finish()?; - tracing::debug!("tx lock built"); + Ok(psbt) } From f47207054679b6ab3cdb81fbaad9d4623654c3bf Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 26 Feb 2021 14:31:09 +1100 Subject: [PATCH 7/7] Remove `--send-btc` in favor of swapping the available balance If the current balance is 0, we wait until the user deposits money to the given address. After that, we simply swap the full balance. Not only does this simplify the interface by removing a parameter, but it also integrates the `deposit` command into the `buy-xmr` command. Syncing a wallet that is backed by electrum includes transactions that are part of the mempool when computing the balance. As such, waiting for a deposit is a very quick action because it allows us to build our lock transaction on top of the yet to be confirmed deposit transactions. This patch introduces another function to the `bitcoin::Wallet` that relies on the currently statically encoded fee rate. To make sure future developers don't forget to adjust both, we extract a function that "selects" a fee rate and return the constant from there. Fixes #196. --- swap/src/bin/swap_cli.rs | 34 +++++++++++++++++++++------------- swap/src/bitcoin/wallet.rs | 35 ++++++++++++++++++++++++++++++++++- swap/src/cli/command.rs | 10 ---------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/swap/src/bin/swap_cli.rs b/swap/src/bin/swap_cli.rs index c4fed8c1..76cbcadf 100644 --- a/swap/src/bin/swap_cli.rs +++ b/swap/src/bin/swap_cli.rs @@ -15,10 +15,11 @@ use anyhow::{Context, Result}; use prettytable::{row, Table}; use reqwest::Url; -use std::{path::Path, sync::Arc}; +use std::{path::Path, sync::Arc, time::Duration}; use structopt::StructOpt; use swap::{ bitcoin, + bitcoin::Amount, cli::{ command::{Arguments, Cancel, Command, Refund, Resume}, config::{ @@ -39,7 +40,7 @@ use swap::{ seed::Seed, trace::init_tracing, }; -use tracing::{error, info, warn}; +use tracing::{debug, error, info, warn}; use tracing_subscriber::filter::LevelFilter; use uuid::Uuid; @@ -95,7 +96,6 @@ async fn main() -> Result<()> { Command::BuyXmr { alice_peer_id, alice_addr, - send_bitcoin, } => { let (bitcoin_wallet, monero_wallet) = init_wallets( config, @@ -107,22 +107,30 @@ async fn main() -> Result<()> { ) .await?; - let swap_id = Uuid::new_v4(); + // TODO: Also wait for more funds if balance < dust + if bitcoin_wallet.balance().await? == Amount::ZERO { + debug!( + "Waiting for BTC at address {}", + bitcoin_wallet.new_address().await? + ); - info!( - "Swap buy XMR with {} started with ID {}", - send_bitcoin, swap_id - ); + while bitcoin_wallet.balance().await? == Amount::ZERO { + bitcoin_wallet.sync_wallet().await?; - info!( - "BTC deposit address: {}", - bitcoin_wallet.new_address().await? - ); + tokio::time::sleep(Duration::from_secs(1)).await; + } + + debug!("Received {}", bitcoin_wallet.balance().await?); + } + + let send_bitcoin = bitcoin_wallet.max_giveable().await?; + + info!("Swapping {} ...", send_bitcoin); let bob_factory = Builder::new( seed, db, - swap_id, + Uuid::new_v4(), Arc::new(bitcoin_wallet), Arc::new(monero_wallet), alice_addr, diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 212c1bd9..466a5f4c 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -16,6 +16,7 @@ use bdk::{ miniscript::bitcoin::PrivateKey, FeeRate, }; +use bitcoin::Script; use reqwest::{Method, Url}; use serde::{Deserialize, Serialize}; use std::{path::Path, sync::Arc, time::Duration}; @@ -120,15 +121,47 @@ impl Wallet { let mut tx_builder = wallet.build_tx(); tx_builder.add_recipient(address.script_pubkey(), amount.as_sat()); - tx_builder.fee_rate(FeeRate::from_sat_per_vb(5.0)); // todo: make dynamic + tx_builder.fee_rate(self.select_feerate()); let (psbt, _details) = tx_builder.finish()?; Ok(psbt) } + /// Calculates the maximum "giveable" amount of this wallet. + /// + /// We define this as the maximum amount we can pay to a single output, + /// already accounting for the fees we need to spend to get the + /// transaction confirmed. + pub async fn max_giveable(&self) -> Result { + let wallet = self.inner.lock().await; + + let mut tx_builder = wallet.build_tx(); + + // create a dummy script to make the txbuilder pass + // we don't intend to send this transaction, we just want to know the max amount + // we can spend + let dummy_script = Script::default(); + tx_builder.set_single_recipient(dummy_script); + + tx_builder.drain_wallet(); + tx_builder.fee_rate(self.select_feerate()); + let (_, details) = tx_builder.finish()?; + + let max_giveable = details.sent - details.fees; + + Ok(Amount::from_sat(max_giveable)) + } + pub async fn get_network(&self) -> bitcoin::Network { self.inner.lock().await.network() } + + /// Selects an appropriate [`FeeRate`] to be used for getting transactions + /// confirmed within a reasonable amount of time. + fn select_feerate(&self) -> FeeRate { + // TODO: This should obviously not be a const :) + FeeRate::from_sat_per_vb(5.0) + } } #[async_trait] diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 88348f21..728a329f 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -1,5 +1,3 @@ -use crate::bitcoin; -use anyhow::Result; use libp2p::{core::Multiaddr, PeerId}; use std::path::PathBuf; use uuid::Uuid; @@ -26,9 +24,6 @@ pub enum Command { #[structopt(long = "connect-addr")] alice_addr: Multiaddr, - - #[structopt(long = "send-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))] - send_bitcoin: bitcoin::Amount, }, History, Resume(Resume), @@ -85,8 +80,3 @@ pub enum Refund { force: bool, }, } - -fn parse_btc(str: &str) -> Result { - let amount = bitcoin::Amount::from_str_in(str, ::bitcoin::Denomination::Bitcoin)?; - Ok(amount) -}