diff --git a/swap/src/bin/swap_cli.rs b/swap/src/bin/swap_cli.rs index 13a2725c..2fc2b64e 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::{read_config, Config}, @@ -35,7 +36,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; @@ -82,7 +83,6 @@ async fn main() -> Result<()> { Command::BuyXmr { alice_peer_id, alice_addr, - send_bitcoin, } => { let (bitcoin_wallet, monero_wallet) = init_wallets( config, @@ -94,22 +94,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.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/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/lock.rs b/swap/src/bitcoin/lock.rs index d817f007..46666fe3 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,16 +13,13 @@ 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) .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/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/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index d157109f..466a5f4c 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, }; @@ -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}; @@ -38,10 +39,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 +70,6 @@ impl Wallet { Ok(Self { inner: Arc::new(Mutex::new(bdk_wallet)), - network, http_url: electrum_http_url, rpc_url: electrum_rpc_url, }) @@ -82,11 +81,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> { @@ -111,29 +108,60 @@ impl Wallet { } pub async fn sync_wallet(&self) -> Result<()> { - tracing::debug!("syncing 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 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(self.select_feerate()); let (psbt, _details) = tx_builder.finish()?; - tracing::debug!("tx lock built"); + 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] @@ -288,13 +316,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/cli/command.rs b/swap/src/cli/command.rs index be571989..7089949a 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; @@ -32,9 +30,6 @@ pub enum Command { default_value = DEFAULT_ALICE_MULTIADDR )] 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), @@ -102,8 +97,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) -} 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..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, ( @@ -459,7 +455,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 +478,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 +558,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) 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)