diff --git a/Cargo.lock b/Cargo.lock index 7c7da167..5453e121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,6 +302,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" +[[package]] +name = "big-bytes" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d73a8ae8ce52d09395e4cafc83b5b81c3deb70a97740e907669c8683c4dd50a" + [[package]] name = "bincode" version = "1.3.1" @@ -560,6 +566,18 @@ dependencies = [ "zeroize 1.2.0", ] +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "winapi 0.3.9", +] + [[package]] name = "clap" version = "2.33.3" @@ -3525,6 +3543,7 @@ dependencies = [ "backoff", "base64 0.12.3", "bdk", + "big-bytes", "bitcoin", "bitcoin-harness", "config", @@ -3943,11 +3962,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" +checksum = "8ab8966ac3ca27126141f7999361cc97dd6fb4b71da04c02044fa9045d98bb96" dependencies = [ "ansi_term 0.12.1", + "chrono", "lazy_static", "matchers", "regex", diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 73f926d3..165e5bae 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -20,6 +20,7 @@ atty = "0.2" backoff = { version = "0.3", features = ["tokio"] } base64 = "0.12" bdk = { version = "0.4" } +big-bytes = "1" bitcoin = { version = "0.26", features = ["rand", "use-serde"] } config = { version = "0.10", default-features = false, features = ["toml"] } conquer-once = "0.3" @@ -57,7 +58,7 @@ toml = "0.5" tracing = { version = "0.1", features = ["attributes"] } tracing-futures = { version = "0.2", features = ["std-future", "futures-03"] } tracing-log = "0.1" -tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter"] } +tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono"] } url = { version = "2.1", features = ["serde"] } uuid = { version = "0.8", features = ["serde", "v4"] } void = "1" diff --git a/swap/src/bin/swap_cli.rs b/swap/src/bin/swap_cli.rs index c2a9ed4c..2c23503a 100644 --- a/swap/src/bin/swap_cli.rs +++ b/swap/src/bin/swap_cli.rs @@ -34,10 +34,9 @@ use swap::{ bob::{cancel::CancelError, Builder}, }, seed::Seed, - trace::init_tracing, }; -use tracing::{debug, error, info, warn}; -use tracing_subscriber::filter::LevelFilter; +use tracing::{debug, error, info, warn, Level}; +use tracing_subscriber::FmtSubscriber; use uuid::Uuid; #[macro_use] @@ -47,17 +46,41 @@ const MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME: &str = "swap-tool-blockchain-mon #[tokio::main] async fn main() -> Result<()> { - init_tracing(LevelFilter::DEBUG).expect("initialize tracing"); + let args = Arguments::from_args(); + + let is_terminal = atty::is(atty::Stream::Stderr); + let base_subscriber = |level| { + FmtSubscriber::builder() + .with_writer(std::io::stderr) + .with_ansi(is_terminal) + .with_target(false) + .with_env_filter(format!("swap={}", level)) + }; + + if args.debug { + let subscriber = base_subscriber(Level::DEBUG) + .with_timer(tracing_subscriber::fmt::time::ChronoLocal::with_format( + "%F %T".to_owned(), + )) + .finish(); - let opt = Arguments::from_args(); + tracing::subscriber::set_global_default(subscriber)?; + } else { + let subscriber = base_subscriber(Level::INFO) + .without_time() + .with_level(false) + .finish(); - let config = match opt.config { + tracing::subscriber::set_global_default(subscriber)?; + } + + let config = match args.config { Some(config_path) => read_config(config_path)??, None => Config::testnet(), }; - info!( - "Database and Seed will be stored in directory: {}", + debug!( + "Database and seed will be stored in {}", config.data.dir.display() ); @@ -79,7 +102,7 @@ async fn main() -> Result<()> { .run(monero_network, "stagenet.community.xmr.to") .await?; - match opt.cmd.unwrap_or_default() { + match args.cmd.unwrap_or_default() { Command::BuyXmr { alice_peer_id, alice_addr, @@ -98,8 +121,8 @@ async fn main() -> Result<()> { // TODO: Also wait for more funds if balance < dust if bitcoin_wallet.balance().await? == Amount::ZERO { - debug!( - "Waiting for BTC at address {}", + info!( + "Please deposit BTC to {}", bitcoin_wallet.new_address().await? ); @@ -110,12 +133,15 @@ async fn main() -> Result<()> { } debug!("Received {}", bitcoin_wallet.balance().await?); + } else { + info!( + "Still got {} left in wallet, swapping ...", + bitcoin_wallet.balance().await? + ); } let send_bitcoin = bitcoin_wallet.max_giveable(TxLock::script_size()).await?; - info!("Swapping {} ...", send_bitcoin); - let bob_factory = Builder::new( seed, db, @@ -233,7 +259,7 @@ async fn main() -> Result<()> { cancel_result = cancel => { match cancel_result? { Ok((txid, _)) => { - info!("Cancel transaction successfully published with id {}", txid) + debug!("Cancel transaction successfully published with id {}", txid) } Err(CancelError::CancelTimelockNotExpiredYet) => error!( "The Cancel Transaction cannot be published yet, \ @@ -318,13 +344,7 @@ async fn init_wallets( bitcoin_wallet .sync_wallet() .await - .expect("Could not sync btc wallet"); - - let bitcoin_balance = bitcoin_wallet.balance().await?; - info!( - "Connection to Bitcoin wallet succeeded, balance: {}", - bitcoin_balance - ); + .context("failed to sync balance of bitcoin wallet")?; let monero_wallet = monero::Wallet::new( monero_wallet_rpc_url.clone(), @@ -346,19 +366,16 @@ async fn init_wallets( monero_wallet_rpc_url ))?; - info!( + debug!( "Created Monero wallet for blockchain monitoring with name {}", MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME ); - } else { - info!( - "Opened Monero wallet for blockchain monitoring with name {}", - MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME - ); } - let _test_wallet_connection = monero_wallet.block_height().await?; - info!("The Monero wallet RPC is set up correctly!"); + let _test_wallet_connection = monero_wallet + .block_height() + .await + .context("failed to validate connection to monero-wallet-rpc")?; Ok((bitcoin_wallet, monero_wallet)) } diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 05d4c7cb..1e6c5494 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -19,13 +19,11 @@ pub use ::bitcoin::{util::amount::Amount, Address, Network, Transaction, Txid}; pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature}; pub use wallet::Wallet; -use crate::execution_params::ExecutionParams; use ::bitcoin::{ hashes::{hex::ToHex, Hash}, secp256k1, SigHash, }; use anyhow::{anyhow, bail, Result}; -use async_trait::async_trait; use ecdsa_fun::{ adaptor::{Adaptor, HashTranscript}, fun::Point, @@ -201,45 +199,6 @@ pub fn build_shared_output_descriptor(A: Point, B: Point) -> Descriptor Result; -} - -#[async_trait] -pub trait BroadcastSignedTransaction { - async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result; -} - -#[async_trait] -pub trait WatchForRawTransaction { - async fn watch_for_raw_transaction(&self, txid: Txid) -> Result; -} - -#[async_trait] -pub trait WaitForTransactionFinality { - async fn wait_for_transaction_finality( - &self, - txid: Txid, - execution_params: ExecutionParams, - ) -> Result<()>; -} - -#[async_trait] -pub trait GetBlockHeight { - async fn get_block_height(&self) -> Result; -} - -#[async_trait] -pub trait TransactionBlockHeight { - async fn transaction_block_height(&self, txid: Txid) -> Result; -} - -#[async_trait] -pub trait GetRawTransaction { - async fn get_raw_transaction(&self, txid: Txid) -> Result; -} - pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result { let adaptor = Adaptor::, Deterministic>::default(); @@ -251,25 +210,22 @@ pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Resu Ok(s) } -pub async fn poll_until_block_height_is_gte(client: &B, target: BlockHeight) -> Result<()> -where - B: GetBlockHeight, -{ +pub async fn poll_until_block_height_is_gte( + client: &crate::bitcoin::Wallet, + target: BlockHeight, +) -> Result<()> { while client.get_block_height().await? < target { tokio::time::sleep(std::time::Duration::from_secs(1)).await; } Ok(()) } -pub async fn current_epoch( - bitcoin_wallet: &W, +pub async fn current_epoch( + bitcoin_wallet: &crate::bitcoin::Wallet, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, lock_tx_id: ::bitcoin::Txid, -) -> Result -where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, -{ +) -> Result { let current_block_height = bitcoin_wallet.get_block_height().await?; let lock_tx_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await?; let cancel_timelock_height = lock_tx_height + cancel_timelock; @@ -285,14 +241,11 @@ where } } -pub async fn wait_for_cancel_timelock_to_expire( - bitcoin_wallet: &W, +pub async fn wait_for_cancel_timelock_to_expire( + bitcoin_wallet: &crate::bitcoin::Wallet, cancel_timelock: CancelTimelock, lock_tx_id: ::bitcoin::Txid, -) -> Result<()> -where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, -{ +) -> Result<()> { let tx_lock_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await?; poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + cancel_timelock).await?; diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 1e018b49..76e17c98 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -1,14 +1,9 @@ use crate::{ - bitcoin::{ - timelocks::BlockHeight, Address, Amount, BroadcastSignedTransaction, GetBlockHeight, - GetRawTransaction, SignTxLock, Transaction, TransactionBlockHeight, TxLock, - WaitForTransactionFinality, WatchForRawTransaction, - }, + bitcoin::{timelocks::BlockHeight, Address, Amount, Transaction}, execution_params::ExecutionParams, }; use ::bitcoin::{util::psbt::PartiallySignedTransaction, Txid}; use anyhow::{anyhow, bail, Context, Result}; -use async_trait::async_trait; use backoff::{backoff::Constant as ConstantBackoff, future::retry}; use bdk::{ blockchain::{noop_progress, Blockchain, ElectrumBlockchain}, @@ -152,43 +147,43 @@ impl Wallet { 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) + /// Broadcast the given transaction to the network and emit a log statement + /// if done so successfully. + pub async fn broadcast(&self, transaction: Transaction, kind: &str) -> Result { + let txid = transaction.txid(); + + self.inner + .lock() + .await + .broadcast(transaction) + .with_context(|| { + format!("failed to broadcast Bitcoin {} transaction {}", kind, txid) + })?; + + tracing::info!("Published Bitcoin {} transaction as {}", txid, kind); + + Ok(txid) } -} -#[async_trait] -impl SignTxLock for Wallet { - async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result { - let txid = tx_lock.txid(); - tracing::debug!("signing tx lock: {}", txid); - let psbt = PartiallySignedTransaction::from(tx_lock); + pub async fn sign_and_finalize(&self, psbt: PartiallySignedTransaction) -> Result { let (signed_psbt, finalized) = self.inner.lock().await.sign(psbt, None)?; + if !finalized { - bail!("Could not finalize TxLock psbt") + bail!("PSBT is not finalized") } + let tx = signed_psbt.extract_tx(); - tracing::debug!("signed tx lock: {}", txid); + Ok(tx) } -} -#[async_trait] -impl BroadcastSignedTransaction for Wallet { - async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result { - tracing::debug!("attempting to broadcast tx: {}", transaction.txid()); - self.inner.lock().await.broadcast(transaction.clone())?; - tracing::info!("Bitcoin tx broadcasted! TXID = {}", transaction.txid()); - Ok(transaction.txid()) + pub async fn get_raw_transaction(&self, txid: Txid) -> Result { + self.get_tx(txid) + .await? + .ok_or_else(|| anyhow!("Could not get raw tx with id: {}", txid)) } -} -#[async_trait] -impl WatchForRawTransaction for Wallet { - async fn watch_for_raw_transaction(&self, txid: Txid) -> Result { + pub async fn watch_for_raw_transaction(&self, txid: Txid) -> Result { tracing::debug!("watching for tx: {}", txid); let tx = retry(ConstantBackoff::new(Duration::from_secs(1)), || async { let client = Client::new(self.rpc_url.as_ref()) @@ -209,20 +204,8 @@ impl WatchForRawTransaction for Wallet { Ok(tx) } -} - -#[async_trait] -impl GetRawTransaction for Wallet { - async fn get_raw_transaction(&self, txid: Txid) -> Result { - self.get_tx(txid) - .await? - .ok_or_else(|| anyhow!("Could not get raw tx with id: {}", txid)) - } -} -#[async_trait] -impl GetBlockHeight for Wallet { - async fn get_block_height(&self) -> Result { + pub async fn get_block_height(&self) -> Result { let url = blocks_tip_height_url(&self.http_url)?; let height = retry(ConstantBackoff::new(Duration::from_secs(1)), || async { let height = reqwest::Client::new() @@ -242,11 +225,8 @@ impl GetBlockHeight for Wallet { Ok(BlockHeight::new(height)) } -} -#[async_trait] -impl TransactionBlockHeight for Wallet { - async fn transaction_block_height(&self, txid: Txid) -> Result { + pub async fn transaction_block_height(&self, txid: Txid) -> Result { let url = tx_status_url(txid, &self.http_url)?; #[derive(Serialize, Deserialize, Debug, Clone)] struct TransactionStatus { @@ -276,11 +256,8 @@ impl TransactionBlockHeight for Wallet { Ok(BlockHeight::new(height)) } -} -#[async_trait] -impl WaitForTransactionFinality for Wallet { - async fn wait_for_transaction_finality( + pub async fn wait_for_transaction_finality( &self, txid: Txid, execution_params: ExecutionParams, @@ -310,6 +287,13 @@ impl WaitForTransactionFinality for Wallet { Ok(()) } + + /// 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) + } } fn tx_status_url(txid: Txid, base_url: &Url) -> Result { diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index ae859cf1..ff61d692 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -14,6 +14,9 @@ pub struct Arguments { )] pub config: Option, + #[structopt(long, help = "Activate debug logging.")] + pub debug: bool, + #[structopt(subcommand)] pub cmd: Option, } diff --git a/swap/src/cli/config.rs b/swap/src/cli/config.rs index 2efa1b35..ce963a25 100644 --- a/swap/src/cli/config.rs +++ b/swap/src/cli/config.rs @@ -6,7 +6,7 @@ use std::{ ffi::OsStr, path::{Path, PathBuf}, }; -use tracing::info; +use tracing::debug; use url::Url; pub const DEFAULT_ELECTRUM_HTTP_URL: &str = "https://blockstream.info/testnet/api/"; @@ -66,7 +66,7 @@ pub struct ConfigNotInitialized {} pub fn read_config(config_path: PathBuf) -> Result> { if config_path.exists() { - info!( + debug!( "Using config file at default path: {}", config_path.display() ); diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 3e7f69ed..90224948 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -190,7 +190,7 @@ pub trait Transfer { public_spend_key: PublicKey, public_view_key: PublicViewKey, amount: Amount, - ) -> Result<(TransferProof, Amount)>; + ) -> Result; } #[async_trait] diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index a21ebe6b..b79ced60 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -12,11 +12,7 @@ use monero_rpc::{ wallet, wallet::{BlockHeight, Refreshed}, }; -use std::{ - str::FromStr, - sync::{atomic::Ordering, Arc}, - time::Duration, -}; +use std::{str::FromStr, sync::atomic::Ordering, time::Duration}; use tokio::sync::Mutex; use tracing::info; use url::Url; @@ -82,7 +78,7 @@ impl Transfer for Wallet { public_spend_key: PublicKey, public_view_key: PublicViewKey, amount: Amount, - ) -> Result<(TransferProof, Amount)> { + ) -> Result { let destination_address = Address::standard(self.network, public_spend_key, public_view_key.into()); @@ -93,16 +89,17 @@ impl Transfer for Wallet { .transfer(0, amount.as_piconero(), &destination_address.to_string()) .await?; - let tx_hash = TxHash(res.tx_hash); - tracing::info!("Monero tx broadcasted!, tx hash: {:?}", tx_hash); - let tx_key = PrivateKey::from_str(&res.tx_key)?; - - let fee = Amount::from_piconero(res.fee); - - let transfer_proof = TransferProof::new(tx_hash, tx_key); - tracing::debug!(" Transfer proof: {:?}", transfer_proof); - - Ok((transfer_proof, fee)) + tracing::debug!( + "sent transfer of {} to {} in {}", + amount, + public_spend_key, + res.tx_hash + ); + + Ok(TransferProof::new( + TxHash(res.tx_hash), + PrivateKey::from_str(&res.tx_key)?, + )) } } @@ -203,7 +200,7 @@ impl WatchForTransfer for Wallet { let address = Address::standard(self.network, public_spend_key, public_view_key.into()); - let confirmations = Arc::new(AtomicU32::new(0u32)); + let confirmations = AtomicU32::new(0u32); let res = retry(ConstantBackoff::new(Duration::from_secs(1)), || async { // NOTE: Currently, this is conflicting IO errors with the transaction not being diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs index 82328210..2cfe1324 100644 --- a/swap/src/monero/wallet_rpc.rs +++ b/swap/src/monero/wallet_rpc.rs @@ -1,8 +1,9 @@ use ::monero::Network; use anyhow::{Context, Result}; use async_compression::tokio::bufread::BzDecoder; +use big_bytes::BigByte; use futures::{StreamExt, TryStreamExt}; -use reqwest::Url; +use reqwest::{header::CONTENT_LENGTH, Url}; use std::{ io::ErrorKind, path::{Path, PathBuf}, @@ -71,8 +72,19 @@ impl WalletRpc { .open(monero_wallet_rpc.tar_path()) .await?; - let byte_stream = reqwest::get(DOWNLOAD_URL) - .await? + let response = reqwest::get(DOWNLOAD_URL).await?; + + let content_length = response.headers()[CONTENT_LENGTH] + .to_str() + .context("failed to convert content-length to string")? + .parse::()?; + + tracing::info!( + "Downloading monero-wallet-rpc ({})", + content_length.big_byte(2) + ); + + let byte_stream = response .bytes_stream() .map_err(|err| std::io::Error::new(ErrorKind::Other, err)); @@ -119,6 +131,8 @@ impl WalletRpc { .local_addr()? .port(); + tracing::debug!("Starting monero-wallet-rpc on port {}", port); + let mut child = Command::new(self.exec_path()) .stdout(Stdio::piped()) .kill_on_drop(true) @@ -148,6 +162,7 @@ impl WalletRpc { break; } } + Ok(WalletRpcProcess { _child: child, port, diff --git a/swap/src/protocol/alice/behaviour.rs b/swap/src/protocol/alice/behaviour.rs index 8aa59904..ae168122 100644 --- a/swap/src/protocol/alice/behaviour.rs +++ b/swap/src/protocol/alice/behaviour.rs @@ -10,7 +10,7 @@ use crate::{ }; use anyhow::{Error, Result}; use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId}; -use tracing::{debug, info}; +use tracing::debug; #[derive(Debug)] pub enum OutEvent { @@ -121,7 +121,6 @@ impl Behaviour { quote_response: QuoteResponse, ) -> Result<()> { self.quote_response.send(channel, quote_response)?; - info!("Sent quote response"); Ok(()) } diff --git a/swap/src/protocol/alice/quote_response.rs b/swap/src/protocol/alice/quote_response.rs index 878dd530..474982a1 100644 --- a/swap/src/protocol/alice/quote_response.rs +++ b/swap/src/protocol/alice/quote_response.rs @@ -59,7 +59,10 @@ impl From> for OutEvent { RequestResponseEvent::OutboundFailure { error, .. } => { OutEvent::Failure(anyhow!("Outbound failure: {:?}", error)) } - RequestResponseEvent::ResponseSent { .. } => OutEvent::ResponseSent, + RequestResponseEvent::ResponseSent { peer, .. } => { + tracing::debug!("successfully sent quote response to {}", peer); + OutEvent::ResponseSent + } } } } @@ -82,7 +85,9 @@ impl Behaviour { ) -> Result<()> { self.rr .send_response(channel, msg) - .map_err(|_| anyhow!("Sending quote response failed")) + .map_err(|_| anyhow!("failed to send quote response"))?; + + Ok(()) } } diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index bc578a4d..1fda6a29 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -2,8 +2,7 @@ use crate::{ bitcoin, bitcoin::{ current_epoch, wait_for_cancel_timelock_to_expire, CancelTimelock, ExpiredTimelocks, - GetBlockHeight, PunishTimelock, TransactionBlockHeight, TxCancel, TxRefund, - WatchForRawTransaction, + PunishTimelock, TxCancel, TxRefund, }, execution_params::ExecutionParams, monero, @@ -325,10 +324,10 @@ pub struct State3 { } impl State3 { - pub async fn wait_for_cancel_timelock_to_expire(&self, bitcoin_wallet: &W) -> Result<()> - where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, - { + pub async fn wait_for_cancel_timelock_to_expire( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result<()> { wait_for_cancel_timelock_to_expire( bitcoin_wallet, self.cancel_timelock, @@ -337,10 +336,10 @@ impl State3 { .await } - pub async fn expired_timelocks(&self, bitcoin_wallet: &W) -> Result - where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, - { + pub async fn expired_timelocks( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result { current_epoch( bitcoin_wallet, self.cancel_timelock, diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index b77479c2..e65567d1 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -1,10 +1,8 @@ use crate::{ bitcoin, bitcoin::{ - poll_until_block_height_is_gte, BlockHeight, BroadcastSignedTransaction, CancelTimelock, - EncryptedSignature, GetBlockHeight, GetRawTransaction, PunishTimelock, - TransactionBlockHeight, TxCancel, TxLock, TxRefund, WaitForTransactionFinality, - WatchForRawTransaction, + poll_until_block_height_is_gte, BlockHeight, CancelTimelock, EncryptedSignature, + PunishTimelock, TxCancel, TxLock, TxRefund, }, execution_params::ExecutionParams, monero, @@ -27,18 +25,14 @@ use libp2p::PeerId; use sha2::Sha256; use std::sync::Arc; use tokio::time::timeout; -use tracing::info; // TODO(Franck): Use helper functions from xmr-btc instead of re-writing them // here -pub async fn wait_for_locked_bitcoin( +pub async fn wait_for_locked_bitcoin( lock_bitcoin_txid: bitcoin::Txid, - bitcoin_wallet: Arc, + bitcoin_wallet: &bitcoin::Wallet, execution_params: ExecutionParams, -) -> Result<()> -where - W: WatchForRawTransaction + WaitForTransactionFinality, -{ +) -> Result<()> { // We assume we will see Bob's transaction in the mempool first. timeout( execution_params.bob_time_to_act, @@ -69,7 +63,7 @@ where let public_spend_key = S_a + state3.S_b_monero; let public_view_key = state3.v.public(); - let (transfer_proof, _) = monero_wallet + let transfer_proof = monero_wallet .transfer(public_spend_key, public_view_key, state3.xmr) .await?; @@ -130,32 +124,14 @@ pub fn build_bitcoin_redeem_transaction( Ok(tx) } -pub async fn publish_bitcoin_redeem_transaction( - redeem_tx: bitcoin::Transaction, - bitcoin_wallet: Arc, -) -> Result<::bitcoin::Txid> -where - W: BroadcastSignedTransaction + WaitForTransactionFinality, -{ - info!("Attempting to publish bitcoin redeem txn"); - let txid = bitcoin_wallet - .broadcast_signed_transaction(redeem_tx) - .await?; - - Ok(txid) -} - -pub async fn publish_cancel_transaction( +pub async fn publish_cancel_transaction( tx_lock: TxLock, a: bitcoin::SecretKey, B: bitcoin::PublicKey, cancel_timelock: CancelTimelock, tx_cancel_sig_bob: bitcoin::Signature, - bitcoin_wallet: Arc, -) -> Result -where - W: GetRawTransaction + TransactionBlockHeight + GetBlockHeight + BroadcastSignedTransaction, -{ + bitcoin_wallet: Arc, +) -> Result { // First wait for cancel timelock to expire let tx_lock_height = bitcoin_wallet .transaction_block_height(tx_lock.txid()) @@ -183,9 +159,7 @@ where .expect("sig_{a,b} to be valid signatures for tx_cancel"); // TODO(Franck): Error handling is delicate, why can't we broadcast? - bitcoin_wallet - .broadcast_signed_transaction(tx_cancel) - .await?; + bitcoin_wallet.broadcast(tx_cancel, "cancel").await?; // TODO(Franck): Wait until transaction is mined and returned mined // block height @@ -194,18 +168,15 @@ where Ok(tx_cancel) } -pub async fn wait_for_bitcoin_refund( +pub async fn wait_for_bitcoin_refund( tx_cancel: &TxCancel, cancel_tx_height: BlockHeight, punish_timelock: PunishTimelock, refund_address: &bitcoin::Address, - bitcoin_wallet: Arc, -) -> Result<(bitcoin::TxRefund, Option)> -where - W: GetBlockHeight + WatchForRawTransaction, -{ + bitcoin_wallet: &bitcoin::Wallet, +) -> Result<(bitcoin::TxRefund, Option)> { let punish_timelock_expired = - poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), cancel_tx_height + punish_timelock); + poll_until_block_height_is_gte(bitcoin_wallet, cancel_tx_height + punish_timelock); let tx_refund = bitcoin::TxRefund::new(tx_cancel, refund_address); @@ -266,22 +237,3 @@ pub fn build_bitcoin_punish_transaction( Ok(signed_tx_punish) } - -pub async fn publish_bitcoin_punish_transaction( - punish_tx: bitcoin::Transaction, - bitcoin_wallet: Arc, - execution_params: ExecutionParams, -) -> Result -where - W: BroadcastSignedTransaction + WaitForTransactionFinality, -{ - let txid = bitcoin_wallet - .broadcast_signed_transaction(punish_tx) - .await?; - - bitcoin_wallet - .wait_for_transaction_finality(txid, execution_params) - .await?; - - Ok(txid) -} diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index ad83b12e..addde0a4 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -2,10 +2,7 @@ //! Alice holds XMR and wishes receive BTC. use crate::{ bitcoin, - bitcoin::{ - ExpiredTimelocks, TransactionBlockHeight, WaitForTransactionFinality, - WatchForRawTransaction, - }, + bitcoin::ExpiredTimelocks, database, database::Database, execution_params::ExecutionParams, @@ -18,8 +15,7 @@ use crate::{ event_loop::EventLoopHandle, steps::{ build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction, - extract_monero_private_key, lock_xmr, publish_bitcoin_punish_transaction, - publish_bitcoin_redeem_transaction, publish_cancel_transaction, + extract_monero_private_key, lock_xmr, publish_cancel_transaction, wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin, }, @@ -98,7 +94,7 @@ async fn run_until_internal( } => { let _ = wait_for_locked_bitcoin( state3.tx_lock.txid(), - bitcoin_wallet.clone(), + &bitcoin_wallet, execution_params, ) .await?; @@ -222,37 +218,31 @@ async fn run_until_internal( state3.B, &state3.redeem_address, ) { - Ok(tx) => { - match publish_bitcoin_redeem_transaction(tx, bitcoin_wallet.clone()) - .await - { - Ok(txid) => { - let publishded_redeem_tx = bitcoin_wallet - .wait_for_transaction_finality(txid, execution_params) - .await; + Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { + Ok(txid) => { + let publishded_redeem_tx = bitcoin_wallet + .wait_for_transaction_finality(txid, execution_params) + .await; - match publishded_redeem_tx { - Ok(_) => AliceState::BtcRedeemed, - Err(e) => { - bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) - } + match publishded_redeem_tx { + Ok(_) => AliceState::BtcRedeemed, + Err(e) => { + bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) } } - Err(e) => { - error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); - state3 - .wait_for_cancel_timelock_to_expire( - bitcoin_wallet.as_ref(), - ) - .await?; + } + Err(e) => { + error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); + state3 + .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) + .await?; - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, } } - } + }, Err(e) => { error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e); state3 @@ -335,7 +325,7 @@ async fn run_until_internal( tx_cancel_height, state3.punish_timelock, &state3.refund_address, - bitcoin_wallet.clone(), + &bitcoin_wallet, ) .await?; @@ -430,11 +420,15 @@ async fn run_until_internal( state3.B, )?; - let punish_tx_finalised = publish_bitcoin_punish_transaction( - signed_tx_punish, - bitcoin_wallet.clone(), - execution_params, - ); + let punish_tx_finalised = async { + let txid = bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?; + + bitcoin_wallet + .wait_for_transaction_finality(txid, execution_params) + .await?; + + Result::<_, anyhow::Error>::Ok(txid) + }; let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()); diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 4a3e8d38..69d155af 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -15,7 +15,7 @@ use crate::{ use anyhow::{bail, Error, Result}; use libp2p::{core::Multiaddr, identity::Keypair, NetworkBehaviour, PeerId}; use std::sync::Arc; -use tracing::{debug, info}; +use tracing::debug; use uuid::Uuid; pub use self::{ @@ -259,8 +259,7 @@ pub struct Behaviour { impl Behaviour { /// Sends a quote request to Alice to retrieve the rate. pub fn send_quote_request(&mut self, alice: PeerId, quote_request: QuoteRequest) { - let _id = self.quote_request.send(alice, quote_request); - info!("Requesting quote from: {}", alice); + let _ = self.quote_request.send(alice, quote_request); } pub fn start_execution_setup( @@ -271,10 +270,8 @@ impl Behaviour { ) { self.execution_setup .run(alice_peer_id, state0, bitcoin_wallet); - info!("Start execution setup with {}", alice_peer_id); } - /// Sends Bob's fourth message to Alice. pub fn send_encrypted_signature( &mut self, alice: PeerId, diff --git a/swap/src/protocol/bob/event_loop.rs b/swap/src/protocol/bob/event_loop.rs index 57a99675..b7e222a7 100644 --- a/swap/src/protocol/bob/event_loop.rs +++ b/swap/src/protocol/bob/event_loop.rs @@ -7,12 +7,12 @@ use crate::{ bob::{Behaviour, OutEvent, QuoteRequest, State0, State2}, }, }; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use futures::FutureExt; use libp2p::{core::Multiaddr, PeerId}; use std::{convert::Infallible, sync::Arc}; use tokio::sync::mpsc::{Receiver, Sender}; -use tracing::{debug, error, info}; +use tracing::{debug, error, trace}; #[derive(Debug)] pub struct Channels { @@ -72,7 +72,6 @@ impl EventLoopHandle { /// Dials other party and wait for the connection to be established. /// Do nothing if we are already connected pub async fn dial(&mut self) -> Result<()> { - debug!("Attempt to dial Alice"); let _ = self.dial_alice.send(()).await?; self.conn_established @@ -201,15 +200,11 @@ impl EventLoop { if option.is_some() { let peer_id = self.alice_peer_id; if self.swarm.pt.is_connected(&peer_id) { - debug!("Already connected to Alice: {}", peer_id); + trace!("Already connected to Alice at {}", peer_id); let _ = self.conn_established.send(peer_id).await; } else { - info!("dialing alice: {}", peer_id); - if let Err(err) = libp2p::Swarm::dial(&mut self.swarm, &peer_id) { - error!("Could not dial alice: {}", err); - // TODO(Franck): If Dial fails then we should report it. - } - + debug!("Dialing alice at {}", peer_id); + libp2p::Swarm::dial(&mut self.swarm, &peer_id).context("failed to dial alice")?; } } }, diff --git a/swap/src/protocol/bob/execution_setup.rs b/swap/src/protocol/bob/execution_setup.rs index aa301b47..39f265a7 100644 --- a/swap/src/protocol/bob/execution_setup.rs +++ b/swap/src/protocol/bob/execution_setup.rs @@ -72,6 +72,8 @@ impl Behaviour { ) { self.inner .do_protocol_dialer(alice, move |mut substream| async move { + tracing::debug!("Starting execution setup with {}", alice); + substream .write_message( &serde_cbor::to_vec(&state0.next_message()) diff --git a/swap/src/protocol/bob/quote_request.rs b/swap/src/protocol/bob/quote_request.rs index 4262a6bf..23d8dd3c 100644 --- a/swap/src/protocol/bob/quote_request.rs +++ b/swap/src/protocol/bob/quote_request.rs @@ -36,6 +36,8 @@ pub struct Behaviour { impl Behaviour { pub fn send(&mut self, alice: PeerId, quote_request: QuoteRequest) -> Result { + debug!("Requesting quote for {}", quote_request.btc_amount); + let id = self.rr.send_request(&alice, quote_request); Ok(id) @@ -67,13 +69,9 @@ impl From> for OutEvent { .. } => OutEvent::Failure(anyhow!("Bob should never get a request from Alice")), RequestResponseEvent::Message { - peer, message: RequestResponseMessage::Response { response, .. }, .. - } => { - debug!("Received quote response from {}", peer); - OutEvent::MsgReceived(response) - } + } => OutEvent::MsgReceived(response), RequestResponseEvent::InboundFailure { error, .. } => { OutEvent::Failure(anyhow!("Inbound failure: {:?}", error)) } diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 10cdc378..f7e562ac 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -1,8 +1,7 @@ use crate::{ bitcoin::{ - self, current_epoch, wait_for_cancel_timelock_to_expire, BroadcastSignedTransaction, - CancelTimelock, ExpiredTimelocks, GetBlockHeight, GetRawTransaction, PunishTimelock, - Transaction, TransactionBlockHeight, TxCancel, Txid, WatchForRawTransaction, + self, current_epoch, wait_for_cancel_timelock_to_expire, CancelTimelock, ExpiredTimelocks, + PunishTimelock, Transaction, TxCancel, Txid, }, execution_params::ExecutionParams, monero, @@ -14,7 +13,7 @@ use crate::{ CROSS_CURVE_PROOF_SYSTEM, }, }; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use ecdsa_fun::{ adaptor::{Adaptor, HashTranscript}, nonce::Deterministic, @@ -269,16 +268,13 @@ impl State2 { } } - pub async fn lock_btc(self, bitcoin_wallet: &W) -> Result - where - W: bitcoin::SignTxLock + bitcoin::BroadcastSignedTransaction, - { - let signed_tx_lock = bitcoin_wallet.sign_tx_lock(self.tx_lock.clone()).await?; + pub async fn lock_btc(self, bitcoin_wallet: &bitcoin::Wallet) -> Result { + let signed_tx = bitcoin_wallet + .sign_and_finalize(self.tx_lock.clone().into()) + .await + .context("failed to sign Bitcoin lock transaction")?; - tracing::info!("{}", self.tx_lock.txid()); - let _ = bitcoin_wallet - .broadcast_signed_transaction(signed_tx_lock) - .await?; + let _ = bitcoin_wallet.broadcast(signed_tx, "lock").await?; Ok(State3 { A: self.A, @@ -363,10 +359,10 @@ impl State3 { })) } - pub async fn wait_for_cancel_timelock_to_expire(&self, bitcoin_wallet: &W) -> Result<()> - where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, - { + pub async fn wait_for_cancel_timelock_to_expire( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result<()> { wait_for_cancel_timelock_to_expire( bitcoin_wallet, self.cancel_timelock, @@ -399,10 +395,10 @@ impl State3 { self.tx_lock.txid() } - pub async fn current_epoch(&self, bitcoin_wallet: &W) -> Result - where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, - { + pub async fn current_epoch( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result { current_epoch( bitcoin_wallet, self.cancel_timelock, @@ -443,10 +439,10 @@ impl State4 { self.b.encsign(self.S_a_bitcoin, tx_redeem.digest()) } - pub async fn check_for_tx_cancel(&self, bitcoin_wallet: &W) -> Result - where - W: GetRawTransaction, - { + pub async fn check_for_tx_cancel( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result { let tx_cancel = bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); @@ -466,10 +462,7 @@ impl State4 { Ok(tx) } - pub async fn submit_tx_cancel(&self, bitcoin_wallet: &W) -> Result - where - W: BroadcastSignedTransaction, - { + pub async fn submit_tx_cancel(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result { let tx_cancel = bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); @@ -484,16 +477,12 @@ impl State4 { tx_cancel", ); - let tx_id = bitcoin_wallet - .broadcast_signed_transaction(tx_cancel) - .await?; + let tx_id = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?; + Ok(tx_id) } - pub async fn watch_for_redeem_btc(&self, bitcoin_wallet: &W) -> Result - where - W: WatchForRawTransaction, - { + pub async fn watch_for_redeem_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result { let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address); let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin, tx_redeem.digest()); @@ -515,10 +504,10 @@ impl State4 { }) } - pub async fn wait_for_cancel_timelock_to_expire(&self, bitcoin_wallet: &W) -> Result<()> - where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, - { + pub async fn wait_for_cancel_timelock_to_expire( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result<()> { wait_for_cancel_timelock_to_expire( bitcoin_wallet, self.cancel_timelock, @@ -527,10 +516,10 @@ impl State4 { .await } - pub async fn expired_timelock(&self, bitcoin_wallet: &W) -> Result - where - W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, - { + pub async fn expired_timelock( + &self, + bitcoin_wallet: &bitcoin::Wallet, + ) -> Result { current_epoch( bitcoin_wallet, self.cancel_timelock, @@ -540,14 +529,11 @@ impl State4 { .await } - pub async fn refund_btc( + pub async fn refund_btc( &self, - bitcoin_wallet: &W, + bitcoin_wallet: &bitcoin::Wallet, execution_params: ExecutionParams, - ) -> Result<()> - where - W: bitcoin::BroadcastSignedTransaction + bitcoin::WaitForTransactionFinality, - { + ) -> Result<()> { let tx_cancel = bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public()); let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); @@ -561,9 +547,7 @@ impl State4 { 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) - .await?; + let txid = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; bitcoin_wallet .wait_for_transaction_finality(txid, execution_params) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 29cac4e8..cb412c2a 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -12,7 +12,7 @@ use async_recursion::async_recursion; use rand::rngs::OsRng; use std::sync::Arc; use tokio::select; -use tracing::info; +use tracing::{trace, warn}; use uuid::Uuid; pub fn is_complete(state: &BobState) -> bool { @@ -30,7 +30,6 @@ pub async fn run(swap: bob::Swap) -> Result { run_until(swap, is_complete).await } -#[tracing::instrument(name = "swap", skip(swap,is_target_state), fields(id = %swap.swap_id))] pub async fn run_until( swap: bob::Swap, is_target_state: fn(&BobState) -> bool, @@ -61,7 +60,7 @@ async fn run_until_internal( swap_id: Uuid, execution_params: ExecutionParams, ) -> Result { - info!("Current state: {}", state); + trace!("Current state: {}", state); if is_target_state(&state) { Ok(state) } else { @@ -186,7 +185,7 @@ async fn run_until_internal( match state4? { Ok(state4) => BobState::XmrLocked(state4), Err(InsufficientFunds {..}) => { - info!("The other party has locked insufficient Monero funds! Waiting for refund..."); + warn!("The other party has locked insufficient Monero funds! Waiting for refund..."); state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()).await?; let state4 = state.cancel(); BobState::CancelTimelockExpired(state4) @@ -389,12 +388,14 @@ pub async fn request_quote_and_setup( .send_quote_request(QuoteRequest { btc_amount }) .await?; - let quote_response = event_loop_handle.recv_quote_response().await?; + let xmr_amount = event_loop_handle.recv_quote_response().await?.xmr_amount; + + tracing::info!("Quote for {} is {}", btc_amount, xmr_amount); let state0 = State0::new( &mut OsRng, btc_amount, - quote_response.xmr_amount, + xmr_amount, execution_params.bitcoin_cancel_timelock, execution_params.bitcoin_punish_timelock, bitcoin_refund_address, diff --git a/swap/src/seed.rs b/swap/src/seed.rs index 48c9d039..ebcf6135 100644 --- a/swap/src/seed.rs +++ b/swap/src/seed.rs @@ -45,7 +45,7 @@ impl Seed { return Self::from_file(&file_path); } - tracing::info!("No seed file found, creating at: {}", file_path.display()); + tracing::debug!("No seed file found, creating at: {}", file_path.display()); let random_seed = Seed::random()?; random_seed.write_to(file_path.to_path_buf())?; @@ -61,7 +61,7 @@ impl Seed { let contents = fs::read_to_string(file)?; let pem = pem::parse(contents)?; - tracing::info!("Read in seed from file: {}", file.display()); + tracing::trace!("Read in seed from {}", file.display()); Self::from_pem(pem) }