From 92ccaee3872a8c81420403019fdbc0b3a31e6f58 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Fri, 4 Dec 2020 16:27:17 +1100 Subject: [PATCH] Upgrade CLI, enhance logging, add network to wallet --- swap/src/bin/swap.rs | 271 +++++++++++++----------- swap/src/bitcoin.rs | 45 ++-- swap/src/cli.rs | 82 +++++-- swap/src/lib.rs | 19 +- swap/src/monero.rs | 6 +- swap/src/recover.rs | 16 +- swap/tests/e2e.rs | 22 +- xmr-btc/src/bitcoin.rs | 14 +- xmr-btc/src/bitcoin/transactions.rs | 9 +- xmr-btc/src/bob.rs | 6 +- xmr-btc/tests/harness/wallet/bitcoin.rs | 10 +- 11 files changed, 326 insertions(+), 174 deletions(-) diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index c6e3d82d..b06e39a7 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -13,21 +13,28 @@ #![forbid(unsafe_code)] use anyhow::Result; -use futures::{channel::mpsc, StreamExt}; use libp2p::Multiaddr; use prettytable::{row, Table}; -use std::{io, io::Write, process, sync::Arc}; +use rand::rngs::OsRng; +use std::sync::Arc; use structopt::StructOpt; use swap::{ - alice, bitcoin, bob, + alice, + alice::swap::AliceState, + bitcoin, bob, + bob::swap::BobState, cli::Options, monero, - network::transport::{build, build_tor, SwapTransport}, + network::transport::{build, build_tor}, recover::recover, storage::Database, - Cmd, Rsp, SwapAmounts, + SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; -use tracing::info; +use tracing::{info, log::LevelFilter, subscriber}; +use tracing_log::LogTracer; +use tracing_subscriber::FmtSubscriber; +use uuid::Uuid; +use xmr_btc::{config::Config, cross_curve_dleq}; #[macro_use] extern crate prettytable; @@ -36,21 +43,33 @@ extern crate prettytable; #[tokio::main] async fn main() -> Result<()> { + init_tracing(LevelFilter::Trace).expect("initialize tracing"); + 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 rng = &mut OsRng; + match opt { Options::Alice { bitcoind_url, - monerod_url, + bitcoin_wallet_name, + monero_wallet_rpc_url, listen_addr, tor_port, + send_monero, + receive_bitcoin, } => { info!("running swap node as Alice ..."); let behaviour = alice::Behaviour::default(); + let alice_peer_id = behaviour.peer_id().clone(); + info!( + "Alice Peer ID (to be used by Bob to dial her): {}", + alice_peer_id + ); let local_key_pair = behaviour.identity(); let (listen_addr, _ac, transport) = match tor_port { @@ -72,29 +91,67 @@ async fn main() -> Result<()> { } }; - let bitcoin_wallet = bitcoin::Wallet::new("alice", bitcoind_url) - .await - .expect("failed to create bitcoin wallet"); + let amounts = SwapAmounts { + btc: receive_bitcoin, + xmr: send_monero, + }; + + // 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!( + "Connection to Bitcoin wallet succeeded, balance: {}", + bitcoin_balance + ); let bitcoin_wallet = Arc::new(bitcoin_wallet); - let monero_wallet = Arc::new(monero::Wallet::new(monerod_url)); + let monero_wallet = monero::Wallet::new(monero_wallet_rpc_url); + let monero_balance = monero_wallet.get_balance().await?; + // TODO: impl Display for monero wallet to display proper monero balance + info!( + "Connection to Monero wallet succeeded, balance: {:?}", + monero_balance + ); + let monero_wallet = Arc::new(monero_wallet); - swap_as_alice( - bitcoin_wallet, - monero_wallet, - db, - listen_addr, - transport, - behaviour, + let alice_state = { + let a = bitcoin::SecretKey::new_random(rng); + let s_a = cross_curve_dleq::Scalar::random(rng); + let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng); + AliceState::Started { + amounts, + a, + s_a, + v_a, + } + }; + let alice_swarm = alice::new_swarm(listen_addr.clone(), transport, behaviour).unwrap(); + + alice::swap::swap( + alice_state, + alice_swarm, + bitcoin_wallet.clone(), + monero_wallet.clone(), + Config::mainnet(), ) .await?; } Options::Bob { alice_addr, - satoshis, + alice_peer_id, bitcoind_url, - monerod_url, + bitcoin_wallet_name, + monero_wallet_rpc_url, tor, + send_bitcoin, + receive_monero, } => { info!("running swap node as Bob ..."); @@ -106,21 +163,58 @@ async fn main() -> Result<()> { false => build(local_key_pair)?, }; - let bitcoin_wallet = bitcoin::Wallet::new("bob", bitcoind_url) - .await - .expect("failed to create bitcoin wallet"); + let amounts = SwapAmounts { + btc: send_bitcoin, + xmr: receive_monero, + }; + + 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: {}", + bitcoin_balance + ); let bitcoin_wallet = Arc::new(bitcoin_wallet); - let monero_wallet = Arc::new(monero::Wallet::new(monerod_url)); + let monero_wallet = monero::Wallet::new(monero_wallet_rpc_url); + let monero_balance = monero_wallet.get_balance().await?; + info!( + "Connection to Monero wallet succeeded, balance: {:?}", + monero_balance + ); + let monero_wallet = Arc::new(monero_wallet); - swap_as_bob( - bitcoin_wallet, - monero_wallet, + let refund_address = bitcoin_wallet.new_address().await.unwrap(); + let state0 = xmr_btc::bob::State0::new( + rng, + send_bitcoin, + receive_monero, + REFUND_TIMELOCK, + PUNISH_TIMELOCK, + refund_address, + ); + + let bob_state = BobState::Started { + state0, + amounts, + peer_id: alice_peer_id, + addr: alice_addr, + }; + let bob_swarm = bob::new_swarm(transport, behaviour).unwrap(); + bob::swap::swap( + bob_state, + bob_swarm, db, - satoshis, - alice_addr, - transport, - behaviour, + bitcoin_wallet.clone(), + monero_wallet.clone(), + OsRng, + Uuid::new_v4(), ) .await?; } @@ -140,11 +234,16 @@ async fn main() -> Result<()> { swap_id, bitcoind_url, monerod_url, + bitcoin_wallet_name, } => { let state = db.get_state(swap_id)?; - let bitcoin_wallet = bitcoin::Wallet::new("bob", 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?; @@ -172,94 +271,26 @@ async fn create_tor_service( Ok(authenticated_connection) } -async fn swap_as_alice( - bitcoin_wallet: Arc, - monero_wallet: Arc, - db: Database, - addr: Multiaddr, - transport: SwapTransport, - behaviour: alice::Behaviour, -) -> Result<()> { - alice::swap( - bitcoin_wallet, - monero_wallet, - db, - addr, - transport, - behaviour, - ) - .await -} - -async fn swap_as_bob( - bitcoin_wallet: Arc, - monero_wallet: Arc, - db: Database, - sats: u64, - alice: Multiaddr, - transport: SwapTransport, - behaviour: bob::Behaviour, -) -> Result<()> { - let (cmd_tx, mut cmd_rx) = mpsc::channel(1); - let (mut rsp_tx, rsp_rx) = mpsc::channel(1); - tokio::spawn(bob::swap( - bitcoin_wallet, - monero_wallet, - db, - sats, - alice, - cmd_tx, - rsp_rx, - transport, - behaviour, - )); - - loop { - let read = cmd_rx.next().await; - match read { - Some(cmd) => match cmd { - Cmd::VerifyAmounts(p) => { - let rsp = verify(p); - rsp_tx.try_send(rsp)?; - if rsp == Rsp::Abort { - process::exit(0); - } - } - }, - None => { - info!("Channel closed from other end"); - return Ok(()); - } - } - } -} - -fn verify(amounts: SwapAmounts) -> Rsp { - let mut s = String::new(); - println!("Got rate from Alice for XMR/BTC swap\n"); - println!("{}", amounts); - print!("Would you like to continue with this swap [y/N]: "); - - let _ = io::stdout().flush(); - io::stdin() - .read_line(&mut s) - .expect("Did not enter a correct string"); - - if let Some('\n') = s.chars().next_back() { - s.pop(); - } - if let Some('\r') = s.chars().next_back() { - s.pop(); +pub fn init_tracing(level: log::LevelFilter) -> anyhow::Result<()> { + if level == LevelFilter::Off { + return Ok(()); } - if !is_yes(&s) { - println!("No worries, try again later - Alice updates her rate regularly"); - return Rsp::Abort; - } + // We want upstream library log messages, just only at Info level. + LogTracer::init_with_filter(LevelFilter::Info)?; - Rsp::VerifiedAmounts -} + let is_terminal = atty::is(atty::Stream::Stderr); + let subscriber = FmtSubscriber::builder() + .with_env_filter(format!( + "swap={},xmr-btc={},http=info,warp=info", + level, level + )) + .with_writer(std::io::stderr) + .with_ansi(is_terminal) + .finish(); -fn is_yes(s: &str) -> bool { - matches!(s, "y" | "Y" | "yes" | "YES" | "Yes") + subscriber::set_global_default(subscriber)?; + info!("Initialized tracing with level: {}", level); + + Ok(()) } diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 06017ba3..5dfc8edd 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -20,27 +20,33 @@ 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) } pub async fn transaction_fee(&self, txid: Txid) -> Result { let fee = self - .0 + .inner .get_wallet_transaction(txid) .await .map(|res| { @@ -64,7 +70,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)?; @@ -81,7 +87,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)?; @@ -96,7 +105,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) } } @@ -105,7 +116,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") @@ -116,14 +127,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.client.getblockcount().await?) }) + (|| async { Ok(self.inner.client.getblockcount().await?) }) .retry(ConstantBackoff::new(Duration::from_secs(1))) .await .expect("transient errors to be retried") @@ -141,7 +152,7 @@ impl TransactionBlockHeight for Wallet { (|| async { let block_height = self - .0 + .inner .transaction_block_height(txid) .await .map_err(|_| backoff::Error::Transient(Error::Io))?; @@ -167,7 +178,7 @@ impl WaitForTransactionFinality for Wallet { let mut interval = interval(config.bitcoin_avg_block_time / 4); loop { - let tx = self.0.client.get_raw_transaction_verbose(txid).await?; + let tx = self.inner.client.get_raw_transaction_verbose(txid).await?; if let Some(confirmations) = tx.confirmations { if confirmations >= config.bitcoin_finality_confirmations { break; @@ -179,3 +190,9 @@ impl WaitForTransactionFinality for Wallet { Ok(()) } } + +impl Network for Wallet { + fn get_network(&self) -> bitcoin::Network { + self.network + } +} diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 2d13fcb8..dbee766c 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -1,4 +1,5 @@ -use libp2p::core::Multiaddr; +use libp2p::{core::Multiaddr, PeerId}; +use std::str::FromStr; use url::Url; use uuid::Uuid; @@ -6,33 +7,71 @@ use uuid::Uuid; #[structopt(name = "xmr-btc-swap", about = "Trustless XMR BTC swaps")] pub enum Options { Alice { - #[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")] + #[structopt( + short = "b", + long = "bitcoind", + default_value = "http://127.0.0.1:8332" + )] bitcoind_url: Url, - #[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")] - monerod_url: Url, + #[structopt(short = "n", long = "bitcoin-wallet-name")] + bitcoin_wallet_name: String, - #[structopt(default_value = "/ip4/127.0.0.1/tcp/9876", long = "listen-addr")] + #[structopt( + short = "m", + long = "monero-wallet-rpc", + default_value = "http://127.0.0.1:18083/json_rpc" + )] + monero_wallet_rpc_url: Url, + + #[structopt( + short = "a", + long = "listen-addr", + default_value = "/ip4/127.0.0.1/tcp/9876" + )] listen_addr: Multiaddr, - #[structopt(long = "tor-port")] + #[structopt(short = "t", long = "tor-port")] tor_port: Option, + + #[structopt(short = "s", long = "send-piconeros", parse(try_from_str = parse_pics))] + send_monero: xmr_btc::monero::Amount, + + #[structopt(short = "r", long = "receive-sats", parse(try_from_str = parse_sats))] + receive_bitcoin: bitcoin::Amount, }, Bob { - #[structopt(long = "sats")] - satoshis: u64, - - #[structopt(long = "alice-addr")] + #[structopt(short = "a", long = "alice-addr")] alice_addr: Multiaddr, - #[structopt(default_value = "http://127.0.0.1:8332", long = "bitcoind")] + #[structopt(short = "p", long = "alice-peer-id")] + alice_peer_id: PeerId, + + #[structopt( + short = "b", + long = "bitcoind", + default_value = "http://127.0.0.1:8332" + )] bitcoind_url: Url, - #[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")] - monerod_url: Url, + #[structopt(short = "n", long = "bitcoin-wallet-name")] + bitcoin_wallet_name: String, - #[structopt(long = "tor")] + #[structopt( + short = "m", + long = "monerod", + default_value = "http://127.0.0.1:18083/json_rpc" + )] + monero_wallet_rpc_url: Url, + + #[structopt(short = "t", long = "tor")] tor: bool, + + #[structopt(short = "s", long = "send-sats", parse(try_from_str = parse_sats))] + send_bitcoin: bitcoin::Amount, + + #[structopt(short = "r", long = "receive-piconeros", parse(try_from_str = parse_pics))] + receive_monero: xmr_btc::monero::Amount, }, History, Recover { @@ -44,5 +83,20 @@ pub enum Options { #[structopt(default_value = "http://127.0.0.1:18083/json_rpc", long = "monerod")] monerod_url: Url, + + #[structopt(short = "n", long = "bitcoin-wallet-name")] + bitcoin_wallet_name: String, }, } + +fn parse_sats(str: &str) -> anyhow::Result { + let sats = u64::from_str(str)?; + let amount = bitcoin::Amount::from_sat(sats); + Ok(amount) +} + +fn parse_pics(str: &str) -> anyhow::Result { + let pics = u64::from_str(str)?; + let amount = xmr_btc::monero::Amount::from_piconero(pics); + Ok(amount) +} diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 68fb45c4..6077b482 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -14,8 +14,23 @@ pub mod state; pub mod storage; pub mod tor; -pub const REFUND_TIMELOCK: u32 = 10; // Relative timelock, this is number of blocks. TODO: What should it be? -pub const PUNISH_TIMELOCK: u32 = 10; // FIXME: What should this be? +// 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--| +// *[2] +// |----PUNISH_TIMELOCK----| +// *[3] +// [1] LockTime point +// [2] TX_cancel+TX_Refund point +// [3] TX_punish point +// +// Given the above, setting both to 24 blocks (roughly 4h) is reasonable. +// TODO: More reasoning what are "good" timelocks +pub const REFUND_TIMELOCK: u32 = 24; // Relative timelock, this is number of blocks. +pub const PUNISH_TIMELOCK: u32 = 24; pub type Never = std::convert::Infallible; diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 7d252b69..e97e9d64 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -39,11 +39,15 @@ impl Transfer for Wallet { .await?; let tx_hash = TxHash(res.tx_hash); + tracing::debug!("Monero tx broadcasted!, tx hash: {:?}", tx_hash); let tx_key = PrivateKey::from_str(&res.tx_key)?; let fee = Amount::from_piconero(res.fee); - Ok((TransferProof::new(tx_hash, tx_key), fee)) + let transfer_proof = TransferProof::new(tx_hash, tx_key); + tracing::debug!(" Transfer proof: {:?}", transfer_proof); + + Ok((transfer_proof, fee)) } } diff --git a/swap/src/recover.rs b/swap/src/recover.rs index bd8d40ac..f67ef7a6 100644 --- a/swap/src/recover.rs +++ b/swap/src/recover.rs @@ -61,7 +61,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() @@ -164,7 +164,7 @@ pub async fn alice_recover( .transaction_block_height(state.tx_lock.txid()) .await; - let block_height = bitcoin_wallet.0.client.getblockcount().await?; + let block_height = bitcoin_wallet.inner.client.getblockcount().await?; let refund_absolute_expiry = tx_lock_height + state.refund_timelock; info!("Checking refund timelock"); @@ -187,7 +187,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() @@ -290,7 +290,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"); @@ -375,7 +379,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() @@ -431,7 +435,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 237543b2..d4ad84e6 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -210,9 +210,13 @@ async fn init_alice( )); 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 amounts = SwapAmounts { @@ -265,13 +269,17 @@ async fn init_bob( Database, ) { 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(), + bob_btc_wallet.inner.new_address().await.unwrap(), btc_starting_balance, ) .await diff --git a/xmr-btc/src/bitcoin.rs b/xmr-btc/src/bitcoin.rs index d0dab9ea..562b262f 100644 --- a/xmr-btc/src/bitcoin.rs +++ b/xmr-btc/src/bitcoin.rs @@ -15,7 +15,14 @@ pub use bitcoin::{util::psbt::PartiallySignedTransaction, *}; pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature}; pub use transactions::{TxCancel, TxLock, TxPunish, TxRedeem, TxRefund}; -pub const TX_FEE: u64 = 10_000; +// TODO: Configurable tx-fee (note: parties have to agree 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. +pub const TX_FEE: u64 = 15_000; #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct SecretKey { @@ -212,6 +219,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 aec6bb86..e9b789ad 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::SigHashCache, psbt::PartiallySignedTransaction}, - Address, Amount, Network, SigHash, SigHashType, Transaction, TxIn, TxOut, + Address, Amount, SigHash, SigHashType, Transaction, TxIn, TxOut, }; use ecdsa_fun::Signature; use miniscript::{Descriptor, NullCtx}; @@ -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, NullCtx) + .address(wallet.get_network(), NullCtx) .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 8f52b788..dbc6b53c 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}; @@ -267,7 +267,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, >( @@ -401,7 +401,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 e2ac5faa..897322c5 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)] @@ -162,3 +162,9 @@ impl TransactionBlockHeight for Wallet { .expect("transient errors to be retried") } } + +impl Network for Wallet { + fn get_network(&self) -> bitcoin::Network { + bitcoin::Network::Regtest + } +}