diff --git a/bitcoin-harness/src/wallet.rs b/bitcoin-harness/src/wallet.rs index e54888eb..1cac5a4d 100644 --- a/bitcoin-harness/src/wallet.rs +++ b/bitcoin-harness/src/wallet.rs @@ -269,7 +269,7 @@ mod test { // // let psbt = { // let partial_signed_bitcoin_transaction = - // + // // PartiallySignedTransaction::from_unsigned_tx(transaction).unwrap(); // let hex_vec = // bitcoin::consensus::serialize(&partial_signed_bitcoin_transaction); diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 567b6ff2..d7dfa809 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -16,7 +16,10 @@ use anyhow::Result; use libp2p::Multiaddr; use prettytable::{row, Table}; use rand::rngs::OsRng; -use std::sync::Arc; +use std::{ + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; use structopt::StructOpt; use swap::{ alice, @@ -45,10 +48,18 @@ extern crate prettytable; async fn main() -> Result<()> { init_tracing(LevelFilter::Trace).expect("initialize tracing"); - let opt = Options::from_args(); + let now = SystemTime::now(); + let timestamp = now + .duration_since(UNIX_EPOCH) + .expect("time to move forward"); + let opt = Options::from_args(); // This currently creates the directory if it's not there in the first place - let db = Database::open(std::path::Path::new("./.swap-db/")).unwrap(); + let db = Database::open(std::path::Path::new(&format!( + "./.swap-db/{}/", + timestamp.as_millis() + ))) + .unwrap(); let rng = &mut OsRng; match opt { @@ -97,9 +108,14 @@ async fn main() -> Result<()> { xmr: send_monero, }; - let bitcoin_wallet = bitcoin::Wallet::new(bitcoin_wallet_name.as_str(), bitcoind_url) - .await - .expect("failed to create bitcoin wallet"); + // TODO: network should be configurable through CLI defaulting to mainnet + let bitcoin_wallet = bitcoin::Wallet::new( + bitcoin_wallet_name.as_str(), + bitcoind_url, + ::bitcoin::Network::Bitcoin, + ) + .await + .expect("failed to create bitcoin wallet"); let bitcoin_balance = bitcoin_wallet.balance().await?; info!( @@ -164,9 +180,13 @@ async fn main() -> Result<()> { xmr: receive_monero, }; - let bitcoin_wallet = bitcoin::Wallet::new(bitcoin_wallet_name.as_str(), bitcoind_url) - .await - .expect("failed to create bitcoin wallet"); + let bitcoin_wallet = bitcoin::Wallet::new( + bitcoin_wallet_name.as_str(), + bitcoind_url, + ::bitcoin::Network::Bitcoin, + ) + .await + .expect("failed to create bitcoin wallet"); let bitcoin_balance = bitcoin_wallet.balance().await?; info!( "Connection to Bitcoin wallet succeeded, balance: {}", @@ -231,9 +251,13 @@ async fn main() -> Result<()> { bitcoin_wallet_name, } => { let state = db.get_state(swap_id)?; - let bitcoin_wallet = bitcoin::Wallet::new(bitcoin_wallet_name.as_ref(), bitcoind_url) - .await - .expect("failed to create bitcoin wallet"); + let bitcoin_wallet = bitcoin::Wallet::new( + bitcoin_wallet_name.as_ref(), + bitcoind_url, + ::bitcoin::Network::Bitcoin, + ) + .await + .expect("failed to create bitcoin wallet"); let monero_wallet = monero::Wallet::new(monerod_url); recover(bitcoin_wallet, monero_wallet, state).await?; @@ -271,7 +295,10 @@ pub fn init_tracing(level: log::LevelFilter) -> anyhow::Result<()> { let is_terminal = atty::is(atty::Stream::Stderr); let subscriber = FmtSubscriber::builder() - .with_env_filter(format!("swap={},http=info,warp=info", level,)) + .with_env_filter(format!( + "swap={},xmr-btc={},http=info,warp=info", + level, level + )) .with_writer(std::io::stderr) .with_ansi(is_terminal) .finish(); diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 2622da54..f8f01f71 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -16,22 +16,28 @@ pub use xmr_btc::bitcoin::*; pub const TX_LOCK_MINE_TIMEOUT: u64 = 3600; #[derive(Debug)] -pub struct Wallet(pub bitcoin_harness::Wallet); +pub struct Wallet { + pub inner: bitcoin_harness::Wallet, + pub network: bitcoin::Network, +} impl Wallet { - pub async fn new(name: &str, url: Url) -> Result { + pub async fn new(name: &str, url: Url, network: bitcoin::Network) -> Result { let wallet = bitcoin_harness::Wallet::new(name, url).await?; - Ok(Self(wallet)) + Ok(Self { + inner: wallet, + network, + }) } pub async fn balance(&self) -> Result { - let balance = self.0.balance().await?; + let balance = self.inner.balance().await?; Ok(balance) } pub async fn new_address(&self) -> Result
{ - self.0.new_address().await.map_err(Into::into) + self.inner.new_address().await.map_err(Into::into) } } @@ -42,7 +48,7 @@ impl BuildTxLockPsbt for Wallet { output_address: Address, output_amount: Amount, ) -> Result { - let psbt = self.0.fund_psbt(output_address, output_amount).await?; + let psbt = self.inner.fund_psbt(output_address, output_amount).await?; let as_hex = base64::decode(psbt)?; let psbt = bitcoin::consensus::deserialize(&as_hex)?; @@ -59,7 +65,10 @@ impl SignTxLock for Wallet { let psbt = bitcoin::consensus::serialize(&psbt); let as_base64 = base64::encode(psbt); - let psbt = self.0.wallet_process_psbt(PsbtBase64(as_base64)).await?; + let psbt = self + .inner + .wallet_process_psbt(PsbtBase64(as_base64)) + .await?; let PsbtBase64(signed_psbt) = PsbtBase64::from(psbt); let as_hex = base64::decode(signed_psbt)?; @@ -74,7 +83,9 @@ impl SignTxLock for Wallet { #[async_trait] impl BroadcastSignedTransaction for Wallet { async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result { - Ok(self.0.send_raw_transaction(transaction).await?) + let txid = self.inner.send_raw_transaction(transaction).await?; + tracing::debug!("Bitcoin tx broadcasted! TXID = {}", txid); + Ok(txid) } } @@ -83,7 +94,7 @@ impl BroadcastSignedTransaction for Wallet { #[async_trait] impl WatchForRawTransaction for Wallet { async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction { - (|| async { Ok(self.0.get_raw_transaction(txid).await?) }) + (|| async { Ok(self.inner.get_raw_transaction(txid).await?) }) .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") @@ -94,14 +105,14 @@ impl WatchForRawTransaction for Wallet { impl GetRawTransaction for Wallet { // todo: potentially replace with option async fn get_raw_transaction(&self, txid: Txid) -> Result { - Ok(self.0.get_raw_transaction(txid).await?) + Ok(self.inner.get_raw_transaction(txid).await?) } } #[async_trait] impl BlockHeight for Wallet { async fn block_height(&self) -> u32 { - (|| async { Ok(self.0.block_height().await?) }) + (|| async { Ok(self.inner.block_height().await?) }) .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") @@ -119,7 +130,7 @@ impl TransactionBlockHeight for Wallet { (|| async { let block_height = self - .0 + .inner .transaction_block_height(txid) .await .map_err(|_| backoff::Error::Transient(Error::Io))?; @@ -141,3 +152,9 @@ impl WaitForTransactionFinality for Wallet { todo!() } } + +impl Network for Wallet { + fn get_network(&self) -> bitcoin::Network { + self.network + } +} diff --git a/swap/src/lib.rs b/swap/src/lib.rs index fb1a541a..700dec67 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -14,8 +14,9 @@ pub mod state; pub mod storage; pub mod tor; -// REFUND_TIMELOCK determines the interval between lock-time until TX_cancel is allowed, -// PUNISH_TIMELOCK determines the interval between TX_cancel and TX_punish being allowed. +// REFUND_TIMELOCK determines the interval between lock-time until TX_cancel is +// allowed, PUNISH_TIMELOCK determines the interval between TX_cancel and +// TX_punish being allowed. // // *[1] // |----REFUND_TIMELOCK--| diff --git a/swap/src/recover.rs b/swap/src/recover.rs index 5f41112f..7d33f038 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -60,7 +60,7 @@ pub async fn alice_recover( info!("Checking if the Bitcoin cancel transaction has been published"); if bitcoin_wallet - .0 + .inner .get_raw_transaction(tx_cancel.txid()) .await .is_err() @@ -169,7 +169,7 @@ pub async fn alice_recover( .transaction_block_height(state.tx_lock.txid()) .await; - let block_height = bitcoin_wallet.0.block_height().await?; + let block_height = bitcoin_wallet.inner.block_height().await?; let refund_absolute_expiry = tx_lock_height + state.refund_timelock; info!("Checking refund timelock"); @@ -192,7 +192,7 @@ pub async fn alice_recover( info!("Checking if the Bitcoin cancel transaction has been published"); if bitcoin_wallet - .0 + .inner .get_raw_transaction(tx_cancel.txid()) .await .is_err() @@ -300,7 +300,11 @@ pub async fn alice_recover( // TODO: Protect against transient errors so that we can correctly decide if the // bitcoin has been refunded - match bitcoin_wallet.0.get_raw_transaction(tx_refund.txid()).await { + match bitcoin_wallet + .inner + .get_raw_transaction(tx_refund.txid()) + .await + { Ok(tx_refund_published) => { info!("Bitcoin already refunded"); @@ -387,7 +391,7 @@ pub async fn bob_recover( info!("Checking if the Bitcoin cancel transaction has been published"); if bitcoin_wallet - .0 + .inner .get_raw_transaction(tx_cancel.txid()) .await .is_err() @@ -451,7 +455,7 @@ pub async fn bob_recover( let tx_redeem = bitcoin::TxRedeem::new(&state.tx_lock, &state.redeem_address); let tx_redeem_published = bitcoin_wallet - .0 + .inner .get_raw_transaction(tx_redeem.txid()) .await?; diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 442fba5f..f224a176 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -42,17 +42,25 @@ async fn swap() { let xmr_bob = 0; let alice_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) - .await - .unwrap(), + swap::bitcoin::Wallet::new( + "alice", + bitcoind.node_url.clone(), + ::bitcoin::Network::Regtest, + ) + .await + .unwrap(), ); let bob_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) - .await - .unwrap(), + swap::bitcoin::Wallet::new( + "bob", + bitcoind.node_url.clone(), + ::bitcoin::Network::Regtest, + ) + .await + .unwrap(), ); bitcoind - .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) + .mint(bob_btc_wallet.inner.new_address().await.unwrap(), btc_bob) .await .unwrap(); @@ -152,17 +160,25 @@ async fn happy_path_recursive_executor() { let xmr_bob = 0; let alice_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) - .await - .unwrap(), + swap::bitcoin::Wallet::new( + "alice", + bitcoind.node_url.clone(), + ::bitcoin::Network::Regtest, + ) + .await + .unwrap(), ); let bob_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) - .await - .unwrap(), + swap::bitcoin::Wallet::new( + "bob", + bitcoind.node_url.clone(), + ::bitcoin::Network::Regtest, + ) + .await + .unwrap(), ); bitcoind - .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) + .mint(bob_btc_wallet.inner.new_address().await.unwrap(), btc_bob) .await .unwrap(); diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index e1bd6868..12174521 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -14,13 +14,13 @@ pub use bitcoin::{util::psbt::PartiallySignedTransaction, *}; pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature}; pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; - -// TODO: Configurable tx-fee (note: the parties have to agree on it prior to swapping) -// Current reasoning: -// tx with largest weight (as determined by get_weight() upon broadcast in e2e test) = 609 -// assuming segwit and 60 sat/vB: +// TODO: Configurable tx-fee (note: the parties have to agree on it prior to +// swapping) Current reasoning: +// tx with largest weight (as determined by get_weight() upon broadcast in e2e +// test) = 609 assuming segwit and 60 sat/vB: // (609 / 4) * 60 (sat/vB) = 9135 sats -// Recommended: Overpay a bit to ensure we don't have to wait too long for test runs. +// Recommended: Overpay a bit to ensure we don't have to wait too long for test +// runs. pub const TX_FEE: u64 = 15_000; #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] @@ -218,6 +218,11 @@ pub trait GetRawTransaction { async fn get_raw_transaction(&self, txid: Txid) -> Result; } +#[async_trait] +pub trait Network { + fn get_network(&self) -> bitcoin::Network; +} + pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result { let adaptor = Adaptor::>::default(); diff --git a/xmr-btc/src/bitcoin/transactions.rs b/xmr-btc/src/bitcoin/transactions.rs index 3242f471..bc50d450 100644 --- a/xmr-btc/src/bitcoin/transactions.rs +++ b/xmr-btc/src/bitcoin/transactions.rs @@ -1,10 +1,11 @@ use crate::bitcoin::{ - build_shared_output_descriptor, verify_sig, BuildTxLockPsbt, OutPoint, PublicKey, Txid, TX_FEE, + build_shared_output_descriptor, verify_sig, BuildTxLockPsbt, Network, OutPoint, PublicKey, + Txid, TX_FEE, }; use anyhow::{bail, Context, Result}; use bitcoin::{ util::{bip143::SighashComponents, psbt::PartiallySignedTransaction}, - Address, Amount, Network, SigHash, Transaction, TxIn, TxOut, + Address, Amount, SigHash, Transaction, TxIn, TxOut, }; use ecdsa_fun::Signature; use miniscript::Descriptor; @@ -20,11 +21,11 @@ pub struct TxLock { impl TxLock { pub async fn new(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result where - W: BuildTxLockPsbt, + W: BuildTxLockPsbt + Network, { let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0); let address = lock_output_descriptor - .address(Network::Regtest) + .address(wallet.get_network()) .expect("can derive address from descriptor"); // We construct a psbt for convenience diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 64c45f48..4c6e8ffe 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -33,7 +33,7 @@ use tracing::error; pub mod message; use crate::{ - bitcoin::{BlockHeight, GetRawTransaction, TransactionBlockHeight}, + bitcoin::{BlockHeight, GetRawTransaction, Network, TransactionBlockHeight}, monero::{CreateWalletForOutput, WatchForTransfer}, }; use ::bitcoin::{Transaction, Txid}; @@ -268,7 +268,7 @@ where // send to one receive in the correct order. pub async fn next_state< R: RngCore + CryptoRng, - B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction, + B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction + Network, M: CreateWalletForOutput + WatchForTransfer, T: SendMessage + ReceiveMessage, >( @@ -402,7 +402,7 @@ impl State0 { pub async fn receive(self, wallet: &W, msg: alice::Message0) -> anyhow::Result where - W: BuildTxLockPsbt, + W: BuildTxLockPsbt + Network, { msg.dleq_proof_s_a.verify( msg.S_a_bitcoin.clone().into(), diff --git a/xmr-btc/tests/harness/wallet/bitcoin.rs b/xmr-btc/tests/harness/wallet/bitcoin.rs index 67f6867c..b0946a58 100644 --- a/xmr-btc/tests/harness/wallet/bitcoin.rs +++ b/xmr-btc/tests/harness/wallet/bitcoin.rs @@ -7,8 +7,8 @@ use reqwest::Url; use std::time::Duration; use tokio::time; use xmr_btc::bitcoin::{ - BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, SignTxLock, TransactionBlockHeight, - TxLock, WatchForRawTransaction, + BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, Network, SignTxLock, + TransactionBlockHeight, TxLock, WatchForRawTransaction, }; #[derive(Debug)] @@ -152,3 +152,9 @@ impl TransactionBlockHeight for Wallet { .expect("transient errors to be retried") } } + +impl Network for Wallet { + fn get_network(&self) -> bitcoin::Network { + bitcoin::Network::Regtest + } +}