From f806ad7231a741b7fd0474538573385aec63eed6 Mon Sep 17 00:00:00 2001 From: rishflab Date: Thu, 10 Dec 2020 14:02:13 +1100 Subject: [PATCH 1/8] Assert balances after punish --- swap/tests/e2e.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 4a0d62c5..b78c0c91 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -182,9 +182,9 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { ) .await; - let bob_xmr_locked_fut = bob::swap::run_until( + let bob_btc_locked_fut = bob::swap::run_until( bob_state, - bob::swap::is_xmr_locked, + bob::swap::is_btc_locked, bob_swarm_handle, bob_db, bob_btc_wallet.clone(), @@ -205,12 +205,35 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { let _alice_swarm_fut = tokio::spawn(async move { alice_swarm.run().await }); - // Wait until alice has locked xmr and bob h as locked btc - let ((alice_state, _), _bob_state) = try_join(alice_fut, bob_xmr_locked_fut).await.unwrap(); + // Wait until alice has locked xmr and bob has locked btc + let ((alice_state, _), bob_state) = try_join(alice_fut, bob_btc_locked_fut).await.unwrap(); assert!(matches!(alice_state, AliceState::Punished)); + let bob_state3 = if let BobState::BtcLocked(state3, ..) = bob_state { + state3 + } else { + panic!("Bob in unexpected state"); + }; - // todo: Add balance assertions + let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); + let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); + + // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal + // to TX_FEE + let lock_tx_bitcoin_fee = bob_btc_wallet + .transaction_fee(bob_state3.tx_lock_id()) + .await + .unwrap(); + + assert_eq!( + btc_alice_final, + alice_btc_starting_balance + btc_to_swap - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE) + ); + + assert_eq!( + btc_bob_final, + bob_btc_starting_balance - btc_to_swap - lock_tx_bitcoin_fee + ); } #[allow(clippy::too_many_arguments)] From 78c6a1a78f631cded1cf0531dbc1e264273e5a05 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 11 Dec 2020 12:34:20 +1100 Subject: [PATCH 2/8] Add Bob refund execution --- swap/src/bob/swap.rs | 46 ++++++++++++++++++++++------- xmr-btc/src/bob.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 11 deletions(-) diff --git a/swap/src/bob/swap.rs b/swap/src/bob/swap.rs index 4221b1e9..5eb5dc57 100644 --- a/swap/src/bob/swap.rs +++ b/swap/src/bob/swap.rs @@ -10,7 +10,7 @@ use rand::{CryptoRng, RngCore}; use std::{fmt, sync::Arc}; use tracing::info; use uuid::Uuid; -use xmr_btc::bob::{self}; +use xmr_btc::bob::{self, Epoch}; // The same data structure is used for swap execution and recovery. // This allows for a seamless transition from a failed swap to recovery. @@ -259,17 +259,41 @@ where ) .await } - BobState::Cancelled(_state) => { + BobState::Cancelled(state) => { // Bob has cancelled the swap - // If panic!("Cancelled before t1??? Something is really wrong"), + Epoch::T1 => { + state.refund_btc(bitcoin_wallet.as_ref()).await?; + run_until( + BobState::BtcRefunded, + is_target_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await + } + Epoch::T2 => { + // todo: If t2 has elapsed should we check whether Alice has punished? If + // not can we still refund? + run_until( + BobState::Punished, + is_target_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await + } + } } BobState::BtcRefunded => Ok(BobState::BtcRefunded), BobState::Punished => Ok(BobState::Punished), diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index dbc6b53c..f0a84b14 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -799,11 +799,80 @@ impl State4 { t1_timeout.await; Ok(()) } + + pub async fn current_epoch(&self, bitcoin_wallet: &W) -> Result + where + W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + { + let current_block_height = bitcoin_wallet.block_height().await; + let t0 = bitcoin_wallet + .transaction_block_height(self.tx_lock.txid()) + .await; + let t1 = t0 + self.refund_timelock; + let t2 = t1 + self.punish_timelock; + + match (current_block_height < t1, current_block_height < t2) { + (true, _) => Ok(Epoch::T0), + (false, true) => Ok(Epoch::T1), + (false, false) => Ok(Epoch::T2), + } + } + + pub async fn refund_btc( + &self, + bitcoin_wallet: &W, + ) -> Result<()> { + let tx_cancel = + bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); + let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); + + { + let sig_b = self.b.sign(tx_cancel.digest()); + let sig_a = self.tx_cancel_sig_a.clone(); + + let signed_tx_cancel = tx_cancel.clone().add_signatures( + &self.tx_lock, + (self.A, sig_a), + (self.b.public(), sig_b), + )?; + + let _ = bitcoin_wallet + .broadcast_signed_transaction(signed_tx_cancel) + .await?; + } + + { + let adaptor = Adaptor::>::default(); + + let sig_b = self.b.sign(tx_refund.digest()); + let sig_a = adaptor + .decrypt_signature(&self.s_b.into_secp256k1(), 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 _ = bitcoin_wallet + .broadcast_signed_transaction(signed_tx_refund) + .await?; + } + Ok(()) + } + pub fn tx_lock_id(&self) -> bitcoin::Txid { self.tx_lock.txid() } } +#[derive(Debug, Clone, Copy)] +pub enum Epoch { + T0, + T1, + T2, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct State5 { A: bitcoin::PublicKey, From 773390886b414749e64e22aaf92af550b5b1813f Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 11 Dec 2020 12:36:03 +1100 Subject: [PATCH 3/8] Test that Bob refunds if Alice fails to act --- swap/tests/e2e.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index b78c0c91..0fe3441d 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -236,6 +236,96 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { ); } +// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice +// then also refunds. +#[tokio::test] +async fn both_refund() { + use tracing_subscriber::util::SubscriberInitExt as _; + let _guard = tracing_subscriber::fmt() + .with_env_filter("swap=info,xmr_btc=info") + .with_ansi(false) + .set_default(); + + let cli = Cli::default(); + let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); + let _ = bitcoind.init(5).await; + let (monero, _container) = + Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) + .await + .unwrap(); + + let btc_to_swap = bitcoin::Amount::from_sat(1_000_000); + let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000); + + let bob_btc_starting_balance = btc_to_swap * 10; + let bob_xmr_starting_balance = xmr_btc::monero::Amount::from_piconero(0); + + let alice_btc_starting_balance = bitcoin::Amount::ZERO; + let alice_xmr_starting_balance = xmr_to_swap * 10; + + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877" + .parse() + .expect("failed to parse Alice's address"); + + let ( + alice_state, + mut alice_swarm, + alice_swarm_handle, + alice_btc_wallet, + alice_xmr_wallet, + alice_peer_id, + ) = init_alice( + &bitcoind, + &monero, + btc_to_swap, + alice_btc_starting_balance, + xmr_to_swap, + alice_xmr_starting_balance, + alice_multiaddr.clone(), + ) + .await; + + let (bob_state, bob_swarm_driver, bob_swarm_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) = + init_bob( + alice_multiaddr, + alice_peer_id, + &bitcoind, + &monero, + btc_to_swap, + bob_btc_starting_balance, + xmr_to_swap, + bob_xmr_starting_balance, + ) + .await; + + let bob_fut = bob::swap::swap( + bob_state, + bob_swarm_handle, + bob_db, + bob_btc_wallet.clone(), + bob_xmr_wallet.clone(), + OsRng, + Uuid::new_v4(), + ); + + tokio::spawn(async move { bob_swarm_driver.run().await }); + + let alice_fut = alice::swap::run_until( + alice_state, + alice::swap::is_xmr_locked, + alice_swarm_handle, + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + Config::regtest(), + ); + + tokio::spawn(async move { alice_swarm.run().await }); + + let ((_alice_state, _), bob_state) = try_join(alice_fut, bob_fut).await.unwrap(); + + assert!(matches!(bob_state, BobState::BtcRefunded)); +} + #[allow(clippy::too_many_arguments)] async fn init_alice( bitcoind: &Bitcoind<'_>, From 7af4b6980a12952d8d14012f6550cb296ca713f0 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 11 Dec 2020 16:49:19 +1100 Subject: [PATCH 4/8] Test Alice refunds after Bob refund Reworked Alice XmrLocked state transition handler to handle the scenario when Alice received the encsig but Bob refunds. Previously Alice was trying to redeem after receiving the encsig without checking if t1 had elapsed. --- swap/src/alice/swap.rs | 73 ++++++++++++++++++++++++++---------------- swap/src/bob/swap.rs | 23 +++++++------ swap/tests/e2e.rs | 64 ++++++++++++++++++++++++++++++++---- xmr-btc/src/alice.rs | 34 +++++++++++++++++++- xmr-btc/src/bob.rs | 10 +----- xmr-btc/src/lib.rs | 7 ++++ 6 files changed, 157 insertions(+), 54 deletions(-) diff --git a/swap/src/alice/swap.rs b/swap/src/alice/swap.rs index 680c49a5..91242e7d 100644 --- a/swap/src/alice/swap.rs +++ b/swap/src/alice/swap.rs @@ -29,6 +29,7 @@ use xmr_btc::{ bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction}, config::Config, monero::CreateWalletForOutput, + Epoch, }; trait Rng: RngCore + CryptoRng + Send {} @@ -75,7 +76,7 @@ pub enum AliceState { state3: State3, }, XmrRefunded, - WaitingToCancel { + Cancelling { state3: State3, }, Punished, @@ -89,7 +90,7 @@ impl fmt::Display for AliceState { AliceState::Negotiated { .. } => write!(f, "negotiated"), AliceState::BtcLocked { .. } => write!(f, "btc_locked"), AliceState::XmrLocked { .. } => write!(f, "xmr_locked"), - AliceState::EncSignLearned { .. } => write!(f, "encsig_sent"), + AliceState::EncSignLearned { .. } => write!(f, "encsig_learnt"), AliceState::BtcRedeemed => write!(f, "btc_redeemed"), AliceState::BtcCancelled { .. } => write!(f, "btc_cancelled"), AliceState::BtcRefunded { .. } => write!(f, "btc_refunded"), @@ -97,7 +98,7 @@ impl fmt::Display for AliceState { AliceState::SafelyAborted => write!(f, "safely_aborted"), AliceState::BtcPunishable { .. } => write!(f, "btc_punishable"), AliceState::XmrRefunded => write!(f, "xmr_refunded"), - AliceState::WaitingToCancel { .. } => write!(f, "waiting_to_cancel"), + AliceState::Cancelling { .. } => write!(f, "cancelling"), } } } @@ -218,31 +219,46 @@ pub async fn run_until( .await } AliceState::XmrLocked { state3 } => { - // Our Monero is locked, we need to go through the cancellation process if this - // step fails - match wait_for_bitcoin_encrypted_signature( - &mut event_loop_handle, - config.monero_max_finality_time, - ) - .await - { - Ok(encrypted_signature) => { - run_until( - AliceState::EncSignLearned { - state3, - encrypted_signature, - }, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - config, - ) - .await + // todo: match statement and wait for t1 can probably expressed more cleanly + match state3.current_epoch(bitcoin_wallet.as_ref()).await? { + Epoch::T0 => { + let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature( + &mut event_loop_handle, + config.monero_max_finality_time, + ); + let t1_timeout = state3.wait_for_t1(bitcoin_wallet.as_ref()); + + tokio::select! { + _ = t1_timeout => { + run_until( + AliceState::Cancelling { state3 }, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } + enc_sig = wait_for_enc_sig => { + run_until( + AliceState::EncSignLearned { + state3, + encrypted_signature: enc_sig?, + }, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } + } } - Err(_) => { + _ => { run_until( - AliceState::WaitingToCancel { state3 }, + AliceState::Cancelling { state3 }, is_target_state, event_loop_handle, bitcoin_wallet, @@ -253,6 +269,7 @@ pub async fn run_until( } } } + AliceState::EncSignLearned { state3, encrypted_signature, @@ -268,7 +285,7 @@ pub async fn run_until( Ok(tx) => tx, Err(_) => { return run_until( - AliceState::WaitingToCancel { state3 }, + AliceState::Cancelling { state3 }, is_target_state, event_loop_handle, bitcoin_wallet, @@ -299,7 +316,7 @@ pub async fn run_until( ) .await } - AliceState::WaitingToCancel { state3 } => { + AliceState::Cancelling { state3 } => { let tx_cancel = publish_cancel_transaction( state3.tx_lock.clone(), state3.a.clone(), diff --git a/swap/src/bob/swap.rs b/swap/src/bob/swap.rs index 5eb5dc57..8dd5a797 100644 --- a/swap/src/bob/swap.rs +++ b/swap/src/bob/swap.rs @@ -10,7 +10,10 @@ use rand::{CryptoRng, RngCore}; use std::{fmt, sync::Arc}; use tracing::info; use uuid::Uuid; -use xmr_btc::bob::{self, Epoch}; +use xmr_btc::{ + bob::{self}, + Epoch, +}; // The same data structure is used for swap execution and recovery. // This allows for a seamless transition from a failed swap to recovery. @@ -27,7 +30,7 @@ pub enum BobState { EncSigSent(bob::State4, PeerId), BtcRedeemed(bob::State5), Cancelled(bob::State4), - BtcRefunded, + BtcRefunded(bob::State4), XmrRedeemed, Punished, SafelyAborted, @@ -41,9 +44,9 @@ impl fmt::Display for BobState { BobState::BtcLocked(..) => write!(f, "btc_locked"), BobState::XmrLocked(..) => write!(f, "xmr_locked"), BobState::EncSigSent(..) => write!(f, "encsig_sent"), - BobState::BtcRedeemed(_) => write!(f, "btc_redeemed"), - BobState::Cancelled(_) => write!(f, "cancelled"), - BobState::BtcRefunded => write!(f, "btc_refunded"), + BobState::BtcRedeemed(..) => write!(f, "btc_redeemed"), + BobState::Cancelled(..) => write!(f, "cancelled"), + BobState::BtcRefunded(..) => write!(f, "btc_refunded"), BobState::XmrRedeemed => write!(f, "xmr_redeemed"), BobState::Punished => write!(f, "punished"), BobState::SafelyAborted => write!(f, "safely_aborted"), @@ -79,7 +82,7 @@ where pub fn is_complete(state: &BobState) -> bool { matches!( state, - BobState::BtcRefunded + BobState::BtcRefunded(..) | BobState::XmrRedeemed | BobState::Punished | BobState::SafelyAborted @@ -267,9 +270,9 @@ where Epoch::T1 => { state.refund_btc(bitcoin_wallet.as_ref()).await?; run_until( - BobState::BtcRefunded, + BobState::BtcRefunded(state), is_target_state, - swarm, + event_loop_handle, db, bitcoin_wallet, monero_wallet, @@ -284,7 +287,7 @@ where run_until( BobState::Punished, is_target_state, - swarm, + event_loop_handle, db, bitcoin_wallet, monero_wallet, @@ -295,7 +298,7 @@ where } } } - BobState::BtcRefunded => Ok(BobState::BtcRefunded), + BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)), BobState::Punished => Ok(BobState::Punished), BobState::SafelyAborted => Ok(BobState::SafelyAborted), BobState::XmrRedeemed => Ok(BobState::XmrRedeemed), diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 0fe3441d..9fa7aa09 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -269,7 +269,7 @@ async fn both_refund() { let ( alice_state, - mut alice_swarm, + mut alice_swarm_driver, alice_swarm_handle, alice_btc_wallet, alice_xmr_wallet, @@ -282,6 +282,7 @@ async fn both_refund() { xmr_to_swap, alice_xmr_starting_balance, alice_multiaddr.clone(), + Config::regtest(), ) .await; @@ -295,6 +296,7 @@ async fn both_refund() { bob_btc_starting_balance, xmr_to_swap, bob_xmr_starting_balance, + Config::regtest(), ) .await; @@ -310,7 +312,7 @@ async fn both_refund() { tokio::spawn(async move { bob_swarm_driver.run().await }); - let alice_fut = alice::swap::run_until( + let alice_xmr_locked_fut = alice::swap::run_until( alice_state, alice::swap::is_xmr_locked, alice_swarm_handle, @@ -319,11 +321,62 @@ async fn both_refund() { Config::regtest(), ); - tokio::spawn(async move { alice_swarm.run().await }); + tokio::spawn(async move { alice_swarm_driver.run().await }); - let ((_alice_state, _), bob_state) = try_join(alice_fut, bob_fut).await.unwrap(); + // Wait until alice has locked xmr and bob has locked btc + let (bob_state, (alice_state, alice_swarm_handle)) = + try_join(bob_fut, alice_xmr_locked_fut).await.unwrap(); - assert!(matches!(bob_state, BobState::BtcRefunded)); + let bob_state4 = if let BobState::BtcRefunded(state4) = bob_state { + state4 + } else { + panic!("Bob in unexpected state"); + }; + + let (alice_state, _) = alice::swap::swap( + alice_state, + alice_swarm_handle, + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + Config::regtest(), + ) + .await + .unwrap(); + + assert!(matches!(alice_state, AliceState::XmrRefunded)); + + let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); + let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); + + // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal + // to TX_FEE + let lock_tx_bitcoin_fee = bob_btc_wallet + .transaction_fee(bob_state4.tx_lock_id()) + .await + .unwrap(); + + assert_eq!(btc_alice_final, alice_btc_starting_balance); + + // Alice or Bob could publish TxCancel. This means Bob could pay tx fees for + // TxCancel and TxRefund or only TxRefund + let btc_bob_final_alice_submitted_cancel = btc_bob_final + == bob_btc_starting_balance + - lock_tx_bitcoin_fee + - bitcoin::Amount::from_sat(bitcoin::TX_FEE); + + let btc_bob_final_bob_submitted_cancel = btc_bob_final + == bob_btc_starting_balance + - lock_tx_bitcoin_fee + - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE); + assert!(btc_bob_final_alice_submitted_cancel || btc_bob_final_bob_submitted_cancel); + + alice_xmr_wallet.as_ref().0.refresh().await.unwrap(); + let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); + assert_eq!(xmr_alice_final, xmr_to_swap); + + bob_xmr_wallet.as_ref().0.refresh().await.unwrap(); + let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); + assert_eq!(xmr_bob_final, bob_xmr_starting_balance); } #[allow(clippy::too_many_arguments)] @@ -383,7 +436,6 @@ async fn init_alice( punish_address, ); - // let msg0 = AliceToBob::Message0(self.state.next_message(&mut OsRng)); ( AliceState::Started { amounts, diff --git a/xmr-btc/src/alice.rs b/xmr-btc/src/alice.rs index 9a1ea914..26f157d3 100644 --- a/xmr-btc/src/alice.rs +++ b/xmr-btc/src/alice.rs @@ -4,6 +4,7 @@ use crate::{ bob, monero, monero::{CreateWalletForOutput, Transfer}, transport::{ReceiveMessage, SendMessage}, + Epoch, }; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -26,8 +27,8 @@ use std::{ }; use tokio::{sync::Mutex, time::timeout}; use tracing::{error, info}; - pub mod message; +use crate::bitcoin::{BlockHeight, TransactionBlockHeight}; pub use message::{Message, Message0, Message1, Message2}; #[derive(Debug)] @@ -678,6 +679,37 @@ impl State3 { tx_cancel_sig_bob: self.tx_cancel_sig_bob, }) } + + pub async fn wait_for_t1(&self, bitcoin_wallet: &W) -> Result<()> + where + W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + { + let tx_id = self.tx_lock.txid(); + let tx_lock_height = bitcoin_wallet.transaction_block_height(tx_id).await; + + let t1_timeout = + poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + self.refund_timelock); + t1_timeout.await; + Ok(()) + } + + pub async fn current_epoch(&self, bitcoin_wallet: &W) -> Result + where + W: WatchForRawTransaction + TransactionBlockHeight + BlockHeight, + { + let current_block_height = bitcoin_wallet.block_height().await; + let t0 = bitcoin_wallet + .transaction_block_height(self.tx_lock.txid()) + .await; + let t1 = t0 + self.refund_timelock; + let t2 = t1 + self.punish_timelock; + + match (current_block_height < t1, current_block_height < t2) { + (true, _) => Ok(Epoch::T0), + (false, true) => Ok(Epoch::T1), + (false, false) => Ok(Epoch::T2), + } + } } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index f0a84b14..9927be7f 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -7,6 +7,7 @@ use crate::{ monero, serde::monero_private_key, transport::{ReceiveMessage, SendMessage}, + Epoch, }; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -708,7 +709,6 @@ impl State4 { let tx_cancel = bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); - // todo: check if this is correct let sig_a = self.tx_cancel_sig_a.clone(); let sig_b = self.b.sign(tx_cancel.digest()); @@ -732,7 +732,6 @@ impl State4 { let tx_cancel = bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); - // todo: check if this is correct let sig_a = self.tx_cancel_sig_a.clone(); let sig_b = self.b.sign(tx_cancel.digest()); @@ -866,13 +865,6 @@ impl State4 { } } -#[derive(Debug, Clone, Copy)] -pub enum Epoch { - T0, - T1, - T2, -} - #[derive(Debug, Clone, Deserialize, Serialize)] pub struct State5 { A: bitcoin::PublicKey, diff --git a/xmr-btc/src/lib.rs b/xmr-btc/src/lib.rs index 7d200891..0c9e8728 100644 --- a/xmr-btc/src/lib.rs +++ b/xmr-btc/src/lib.rs @@ -14,6 +14,13 @@ #![forbid(unsafe_code)] #![allow(non_snake_case)] +#[derive(Debug, Clone, Copy)] +pub enum Epoch { + T0, + T1, + T2, +} + #[macro_use] mod utils { From 9b7b44ceba81390bfabc06c7f9ce55e01cb6472a Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 11 Dec 2020 17:19:46 +1100 Subject: [PATCH 5/8] Remove old refund test --- Cargo.lock | 58 +------ xmr-btc/Cargo.toml | 10 -- xmr-btc/tests/e2e.rs | 98 ------------ xmr-btc/tests/harness/mod.rs | 202 ------------------------ xmr-btc/tests/harness/node.rs | 92 ----------- xmr-btc/tests/harness/transport.rs | 45 ------ xmr-btc/tests/harness/wallet/bitcoin.rs | 170 -------------------- xmr-btc/tests/harness/wallet/mod.rs | 2 - xmr-btc/tests/harness/wallet/monero.rs | 126 --------------- 9 files changed, 2 insertions(+), 801 deletions(-) delete mode 100644 xmr-btc/tests/e2e.rs delete mode 100644 xmr-btc/tests/harness/mod.rs delete mode 100644 xmr-btc/tests/harness/node.rs delete mode 100644 xmr-btc/tests/harness/transport.rs delete mode 100644 xmr-btc/tests/harness/wallet/bitcoin.rs delete mode 100644 xmr-btc/tests/harness/wallet/mod.rs delete mode 100644 xmr-btc/tests/harness/wallet/monero.rs diff --git a/Cargo.lock b/Cargo.lock index 363e9b51..c782210a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,19 +500,6 @@ dependencies = [ "zeroize", ] -[[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", - "time 0.1.44", - "winapi 0.3.9", -] - [[package]] name = "clap" version = "2.33.3" @@ -1247,7 +1234,7 @@ checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if 0.1.10", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -3427,7 +3414,7 @@ dependencies = [ "structopt", "tempfile", "testcontainers", - "time 0.2.23", + "time", "tokio", "torut", "tracing", @@ -3565,17 +3552,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", -] - [[package]] name = "time" version = "0.2.23" @@ -3789,16 +3765,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.2.15" @@ -3806,19 +3772,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" dependencies = [ "ansi_term 0.12.1", - "chrono", "lazy_static", "matchers", "regex", - "serde", - "serde_json", "sharded-slab", - "smallvec", "thread_local", "tracing", "tracing-core", - "tracing-log", - "tracing-serde", ] [[package]] @@ -3998,12 +3958,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasm-bindgen" version = "0.2.68" @@ -4176,10 +4130,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "backoff", - "base64 0.12.3", "bitcoin", - "bitcoin-harness", "conquer-once", "cross-curve-dleq", "curve25519-dalek 2.1.0", @@ -4189,19 +4140,14 @@ dependencies = [ "genawaiter", "miniscript", "monero", - "monero-harness", "rand 0.7.3", - "reqwest", "rust_decimal", "serde", "serde_cbor", "sha2 0.9.2", - "tempfile", - "testcontainers", "thiserror", "tokio", "tracing", - "tracing-subscriber", ] [[package]] diff --git a/xmr-btc/Cargo.toml b/xmr-btc/Cargo.toml index bfaf5f9e..57e134b5 100644 --- a/xmr-btc/Cargo.toml +++ b/xmr-btc/Cargo.toml @@ -28,14 +28,4 @@ tokio = { version = "0.2", default-features = false, features = ["time"] } tracing = "0.1" [dev-dependencies] -backoff = { version = "0.2", features = ["tokio"] } -base64 = "0.12" -bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "864b55fcba2e770105f135781dd2e3002c503d12" } -futures = "0.3" -monero-harness = { path = "../monero-harness" } -reqwest = { version = "0.10", default-features = false } serde_cbor = "0.11" -tempfile = "3" -testcontainers = "0.11" -tracing = "0.1" -tracing-subscriber = "0.2" diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs deleted file mode 100644 index 6ca819d0..00000000 --- a/xmr-btc/tests/e2e.rs +++ /dev/null @@ -1,98 +0,0 @@ -pub mod harness; - -mod tests { - use crate::{ - harness, - harness::{ - init_bitcoind, init_test, - node::{run_alice_until, run_bob_until}, - }, - }; - use futures::future; - use monero_harness::Monero; - use rand::rngs::OsRng; - use std::convert::TryInto; - use testcontainers::clients::Cli; - use xmr_btc::{ - alice, - bitcoin::{Amount, TX_FEE}, - bob, - }; - - #[tokio::test] - async fn both_refund() { - let cli = Cli::default(); - let (monero, _container) = Monero::new(&cli, Some("br".to_string()), vec![ - "alice".to_string(), - "bob".to_string(), - ]) - .await - .unwrap(); - let bitcoind = init_bitcoind(&cli).await; - - let ( - alice_state0, - bob_state0, - mut alice_node, - mut bob_node, - initial_balances, - swap_amounts, - ) = init_test(&monero, &bitcoind, None, None).await; - - let (alice_state, bob_state) = future::try_join( - run_alice_until( - &mut alice_node, - alice_state0.into(), - harness::alice::is_state5, - &mut OsRng, - ), - run_bob_until( - &mut bob_node, - bob_state0.into(), - harness::bob::is_state3, - &mut OsRng, - ), - ) - .await - .unwrap(); - - let alice_state5: alice::State5 = alice_state.try_into().unwrap(); - let bob_state3: bob::State3 = bob_state.try_into().unwrap(); - - bob_state3 - .refund_btc(&bob_node.bitcoin_wallet) - .await - .unwrap(); - alice_state5 - .refund_xmr(&alice_node.bitcoin_wallet, &alice_node.monero_wallet) - .await - .unwrap(); - - let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap(); - let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap(); - - // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal - // to TX_FEE - let lock_tx_bitcoin_fee = bob_node - .bitcoin_wallet - .transaction_fee(bob_state3.tx_lock_id()) - .await - .unwrap(); - - monero.wallet("alice").unwrap().refresh().await.unwrap(); - let alice_final_xmr_balance = alice_node.monero_wallet.get_balance().await.unwrap(); - let bob_final_xmr_balance = bob_node.monero_wallet.get_balance().await.unwrap(); - - assert_eq!(alice_final_btc_balance, initial_balances.alice_btc); - assert_eq!( - bob_final_btc_balance, - // The 2 * TX_FEE corresponds to tx_refund and tx_cancel. - initial_balances.bob_btc - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee - ); - - // Because we create a new wallet when claiming Monero, we can only assert on - // this new wallet owning all of `xmr_amount` after refund - assert_eq!(alice_final_xmr_balance, swap_amounts.xmr); - assert_eq!(bob_final_xmr_balance, initial_balances.bob_xmr); - } -} diff --git a/xmr-btc/tests/harness/mod.rs b/xmr-btc/tests/harness/mod.rs deleted file mode 100644 index c1efa323..00000000 --- a/xmr-btc/tests/harness/mod.rs +++ /dev/null @@ -1,202 +0,0 @@ -pub mod node; -pub mod transport; -pub mod wallet; - -pub mod bob { - use xmr_btc::bob::State; - - pub fn is_state2(state: &State) -> bool { - matches!(state, State::State2 { .. }) - } - - // TODO: use macro or generics - pub fn is_state5(state: &State) -> bool { - matches!(state, State::State5 { .. }) - } - - // TODO: use macro or generics - pub fn is_state3(state: &State) -> bool { - matches!(state, State::State3 { .. }) - } -} - -pub mod alice { - use xmr_btc::alice::State; - - pub fn is_state3(state: &State) -> bool { - matches!(state, State::State3 { .. }) - } - - // TODO: use macro or generics - pub fn is_state4(state: &State) -> bool { - matches!(state, State::State4 { .. }) - } - - // TODO: use macro or generics - pub fn is_state5(state: &State) -> bool { - matches!(state, State::State5 { .. }) - } - - // TODO: use macro or generics - pub fn is_state6(state: &State) -> bool { - matches!(state, State::State6 { .. }) - } -} - -use bitcoin_harness::Bitcoind; -use monero_harness::Monero; -use node::{AliceNode, BobNode}; -use rand::rngs::OsRng; -use testcontainers::clients::Cli; -use tokio::sync::{ - mpsc, - mpsc::{Receiver, Sender}, -}; -use transport::Transport; -use xmr_btc::{bitcoin, monero}; - -const TEN_XMR: u64 = 10_000_000_000_000; -const RELATIVE_REFUND_TIMELOCK: u32 = 1; -const RELATIVE_PUNISH_TIMELOCK: u32 = 1; -pub const ALICE_TEST_DB_FOLDER: &str = "../target/e2e-test-alice-recover"; -pub const BOB_TEST_DB_FOLDER: &str = "../target/e2e-test-bob-recover"; - -pub async fn init_bitcoind(tc_client: &Cli) -> Bitcoind<'_> { - let bitcoind = Bitcoind::new(tc_client, "0.19.1").expect("failed to create bitcoind"); - let _ = bitcoind.init(5).await; - - bitcoind -} - -pub struct InitialBalances { - pub alice_xmr: monero::Amount, - pub alice_btc: bitcoin::Amount, - pub bob_xmr: monero::Amount, - pub bob_btc: bitcoin::Amount, -} - -pub struct SwapAmounts { - pub xmr: monero::Amount, - pub btc: bitcoin::Amount, -} - -pub fn init_alice_and_bob_transports() -> ( - Transport, - Transport, -) { - let (a_sender, b_receiver): ( - Sender, - Receiver, - ) = mpsc::channel(5); - let (b_sender, a_receiver): ( - Sender, - Receiver, - ) = mpsc::channel(5); - - let a_transport = Transport { - sender: a_sender, - receiver: a_receiver, - }; - - let b_transport = Transport { - sender: b_sender, - receiver: b_receiver, - }; - - (a_transport, b_transport) -} - -pub async fn init_test( - monero: &Monero, - bitcoind: &Bitcoind<'_>, - refund_timelock: Option, - punish_timelock: Option, -) -> ( - xmr_btc::alice::State0, - xmr_btc::bob::State0, - AliceNode, - BobNode, - InitialBalances, - SwapAmounts, -) { - // must be bigger than our hardcoded fee of 10_000 - let btc_amount = bitcoin::Amount::from_sat(10_000_000); - let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000); - - let swap_amounts = SwapAmounts { - xmr: xmr_amount, - btc: btc_amount, - }; - - let fund_alice = TEN_XMR; - let fund_bob = 0; - monero - .init(vec![("alice", fund_alice), ("bob", fund_bob)]) - .await - .unwrap(); - - let alice_monero_wallet = wallet::monero::Wallet(monero.wallet("alice").unwrap().client()); - let bob_monero_wallet = wallet::monero::Wallet(monero.wallet("bob").unwrap().client()); - - let alice_btc_wallet = wallet::bitcoin::Wallet::new("alice", &bitcoind.node_url) - .await - .unwrap(); - let bob_btc_wallet = wallet::bitcoin::make_wallet("bob", &bitcoind, btc_amount) - .await - .unwrap(); - - let (alice_transport, bob_transport) = init_alice_and_bob_transports(); - let alice = AliceNode::new(alice_transport, alice_btc_wallet, alice_monero_wallet); - - let bob = BobNode::new(bob_transport, bob_btc_wallet, bob_monero_wallet); - - let alice_initial_btc_balance = alice.bitcoin_wallet.balance().await.unwrap(); - let bob_initial_btc_balance = bob.bitcoin_wallet.balance().await.unwrap(); - - let alice_initial_xmr_balance = alice.monero_wallet.get_balance().await.unwrap(); - let bob_initial_xmr_balance = bob.monero_wallet.get_balance().await.unwrap(); - - let redeem_address = alice.bitcoin_wallet.new_address().await.unwrap(); - let punish_address = redeem_address.clone(); - let refund_address = bob.bitcoin_wallet.new_address().await.unwrap(); - - let alice_state0 = { - let a = bitcoin::SecretKey::new_random(&mut OsRng); - let s_a = cross_curve_dleq::Scalar::random(&mut OsRng); - let v_a = monero::PrivateViewKey::new_random(&mut OsRng); - - xmr_btc::alice::State0::new( - a, - s_a, - v_a, - btc_amount, - xmr_amount, - refund_timelock.unwrap_or(RELATIVE_REFUND_TIMELOCK), - punish_timelock.unwrap_or(RELATIVE_PUNISH_TIMELOCK), - redeem_address.clone(), - punish_address.clone(), - ) - }; - let bob_state0 = xmr_btc::bob::State0::new( - &mut OsRng, - btc_amount, - xmr_amount, - refund_timelock.unwrap_or(RELATIVE_REFUND_TIMELOCK), - punish_timelock.unwrap_or(RELATIVE_PUNISH_TIMELOCK), - refund_address, - ); - let initial_balances = InitialBalances { - alice_xmr: alice_initial_xmr_balance, - alice_btc: alice_initial_btc_balance, - bob_xmr: bob_initial_xmr_balance, - bob_btc: bob_initial_btc_balance, - }; - ( - alice_state0, - bob_state0, - alice, - bob, - initial_balances, - swap_amounts, - ) -} diff --git a/xmr-btc/tests/harness/node.rs b/xmr-btc/tests/harness/node.rs deleted file mode 100644 index 7b4bf207..00000000 --- a/xmr-btc/tests/harness/node.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::harness::{transport::Transport, wallet}; -use anyhow::Result; -use rand::{CryptoRng, RngCore}; -use xmr_btc::{alice, bob}; - -// TODO: merge this with bob node -// This struct is responsible for I/O -pub struct AliceNode { - transport: Transport, - pub bitcoin_wallet: wallet::bitcoin::Wallet, - pub monero_wallet: wallet::monero::Wallet, -} - -impl AliceNode { - pub fn new( - transport: Transport, - bitcoin_wallet: wallet::bitcoin::Wallet, - monero_wallet: wallet::monero::Wallet, - ) -> AliceNode { - Self { - transport, - bitcoin_wallet, - monero_wallet, - } - } -} - -pub async fn run_alice_until( - alice: &mut AliceNode, - initial_state: alice::State, - is_state: fn(&alice::State) -> bool, - rng: &mut R, -) -> Result { - let mut result = initial_state; - loop { - result = alice::next_state( - &alice.bitcoin_wallet, - &alice.monero_wallet, - &mut alice.transport, - result, - rng, - ) - .await?; - if is_state(&result) { - return Ok(result); - } - } -} - -// TODO: merge this with alice node -// This struct is responsible for I/O -pub struct BobNode { - transport: Transport, - pub bitcoin_wallet: wallet::bitcoin::Wallet, - pub monero_wallet: wallet::monero::Wallet, -} - -impl BobNode { - pub fn new( - transport: Transport, - bitcoin_wallet: wallet::bitcoin::Wallet, - monero_wallet: wallet::monero::Wallet, - ) -> BobNode { - Self { - transport, - bitcoin_wallet, - monero_wallet, - } - } -} - -pub async fn run_bob_until( - bob: &mut BobNode, - initial_state: bob::State, - is_state: fn(&bob::State) -> bool, - rng: &mut R, -) -> Result { - let mut result = initial_state; - loop { - result = bob::next_state( - &bob.bitcoin_wallet, - &bob.monero_wallet, - &mut bob.transport, - result, - rng, - ) - .await?; - if is_state(&result) { - return Ok(result); - } - } -} diff --git a/xmr-btc/tests/harness/transport.rs b/xmr-btc/tests/harness/transport.rs deleted file mode 100644 index 1c912b02..00000000 --- a/xmr-btc/tests/harness/transport.rs +++ /dev/null @@ -1,45 +0,0 @@ -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use tokio::{ - stream::StreamExt, - sync::mpsc::{Receiver, Sender}, -}; -use xmr_btc::transport::{ReceiveMessage, SendMessage}; - -#[derive(Debug)] -pub struct Transport { - pub sender: Sender, - pub receiver: Receiver, -} - -#[async_trait] -impl SendMessage for Transport -where - SendMsg: Send + Sync, - RecvMsg: std::marker::Send, -{ - async fn send_message(&mut self, message: SendMsg) -> Result<()> { - let _ = self - .sender - .send(message) - .await - .map_err(|_| anyhow!("failed to send message"))?; - Ok(()) - } -} - -#[async_trait] -impl ReceiveMessage for Transport -where - SendMsg: std::marker::Send, - RecvMsg: Send + Sync, -{ - async fn receive_message(&mut self) -> Result { - let message = self - .receiver - .next() - .await - .ok_or_else(|| anyhow!("failed to receive message"))?; - Ok(message) - } -} diff --git a/xmr-btc/tests/harness/wallet/bitcoin.rs b/xmr-btc/tests/harness/wallet/bitcoin.rs deleted file mode 100644 index 897322c5..00000000 --- a/xmr-btc/tests/harness/wallet/bitcoin.rs +++ /dev/null @@ -1,170 +0,0 @@ -use anyhow::{Context, Result}; -use async_trait::async_trait; -use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; -use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Amount, Transaction, Txid}; -use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind, BitcoindRpcApi}; -use reqwest::Url; -use std::time::Duration; -use tokio::time; -use xmr_btc::bitcoin::{ - BlockHeight, BroadcastSignedTransaction, BuildTxLockPsbt, Network, SignTxLock, - TransactionBlockHeight, TxLock, WatchForRawTransaction, -}; - -#[derive(Debug)] -pub struct Wallet(pub bitcoin_harness::Wallet); - -impl Wallet { - pub async fn new(name: &str, url: &Url) -> Result { - let wallet = bitcoin_harness::Wallet::new(name, url.clone()).await?; - - Ok(Self(wallet)) - } - - pub async fn balance(&self) -> Result { - let balance = self.0.balance().await?; - Ok(balance) - } - - pub async fn new_address(&self) -> Result
{ - self.0.new_address().await.map_err(Into::into) - } - - pub async fn transaction_fee(&self, txid: Txid) -> Result { - let fee = self - .0 - .get_wallet_transaction(txid) - .await - .map(|res| { - res.fee.map(|signed_amount| { - signed_amount - .abs() - .to_unsigned() - .expect("Absolute value is always positive") - }) - })? - .context("Rpc response did not contain a fee")?; - - Ok(fee) - } -} - -pub async fn make_wallet( - name: &str, - bitcoind: &Bitcoind<'_>, - fund_amount: Amount, -) -> Result { - let wallet = Wallet::new(name, &bitcoind.node_url).await?; - let buffer = Amount::from_btc(1.0).unwrap(); - let amount = fund_amount + buffer; - - let address = wallet.0.new_address().await.unwrap(); - - bitcoind.mint(address, amount).await.unwrap(); - - Ok(wallet) -} - -#[async_trait] -impl BuildTxLockPsbt for Wallet { - async fn build_tx_lock_psbt( - &self, - output_address: Address, - output_amount: Amount, - ) -> Result { - let psbt = self.0.fund_psbt(output_address, output_amount).await?; - let as_hex = base64::decode(psbt)?; - - let psbt = bitcoin::consensus::deserialize(&as_hex)?; - - Ok(psbt) - } -} - -#[async_trait] -impl SignTxLock for Wallet { - async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result { - let psbt = PartiallySignedTransaction::from(tx_lock); - - let psbt = bitcoin::consensus::serialize(&psbt); - let as_base64 = base64::encode(psbt); - - let psbt = self.0.wallet_process_psbt(PsbtBase64(as_base64)).await?; - let PsbtBase64(signed_psbt) = PsbtBase64::from(psbt); - - let as_hex = base64::decode(signed_psbt)?; - let psbt: PartiallySignedTransaction = bitcoin::consensus::deserialize(&as_hex)?; - - let tx = psbt.extract_tx(); - - Ok(tx) - } -} - -#[async_trait] -impl BroadcastSignedTransaction for Wallet { - async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result { - let txid = self.0.send_raw_transaction(transaction).await?; - - // TODO: Instead of guessing how long it will take for the transaction to be - // mined we should ask bitcoind for the number of confirmations on `txid` - - // give time for transaction to be mined - time::delay_for(Duration::from_millis(1100)).await; - - Ok(txid) - } -} - -#[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?) }) - .retry(ConstantBackoff::new(Duration::from_secs(1))) - .await - .expect("transient errors to be retried") - } -} - -#[async_trait] -impl BlockHeight for Wallet { - async fn block_height(&self) -> u32 { - (|| async { Ok(self.0.client.getblockcount().await?) }) - .retry(ConstantBackoff::new(Duration::from_secs(1))) - .await - .expect("transient errors to be retried") - } -} - -#[async_trait] -impl TransactionBlockHeight for Wallet { - async fn transaction_block_height(&self, txid: Txid) -> u32 { - #[derive(Debug)] - enum Error { - Io, - NotYetMined, - } - - (|| async { - let block_height = self - .0 - .transaction_block_height(txid) - .await - .map_err(|_| backoff::Error::Transient(Error::Io))?; - - let block_height = - block_height.ok_or_else(|| backoff::Error::Transient(Error::NotYetMined))?; - - Result::<_, backoff::Error>::Ok(block_height) - }) - .retry(ConstantBackoff::new(Duration::from_secs(1))) - .await - .expect("transient errors to be retried") - } -} - -impl Network for Wallet { - fn get_network(&self) -> bitcoin::Network { - bitcoin::Network::Regtest - } -} diff --git a/xmr-btc/tests/harness/wallet/mod.rs b/xmr-btc/tests/harness/wallet/mod.rs deleted file mode 100644 index c2b89100..00000000 --- a/xmr-btc/tests/harness/wallet/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod bitcoin; -pub mod monero; diff --git a/xmr-btc/tests/harness/wallet/monero.rs b/xmr-btc/tests/harness/wallet/monero.rs deleted file mode 100644 index fbfd4270..00000000 --- a/xmr-btc/tests/harness/wallet/monero.rs +++ /dev/null @@ -1,126 +0,0 @@ -use anyhow::Result; -use async_trait::async_trait; -use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _}; -use monero_harness::rpc::wallet; -use std::{str::FromStr, time::Duration}; -use xmr_btc::monero::{ - Address, Amount, CreateWalletForOutput, InsufficientFunds, Network, PrivateKey, PrivateViewKey, - PublicKey, PublicViewKey, Transfer, TransferProof, TxHash, WatchForTransfer, -}; - -pub struct Wallet(pub wallet::Client); - -impl Wallet { - /// Get the balance of the primary account. - pub async fn get_balance(&self) -> Result { - let amount = self.0.get_balance(0).await?; - - Ok(Amount::from_piconero(amount)) - } -} - -#[async_trait] -impl Transfer for Wallet { - async fn transfer( - &self, - public_spend_key: PublicKey, - public_view_key: PublicViewKey, - amount: Amount, - ) -> Result<(TransferProof, Amount)> { - let destination_address = - Address::standard(Network::Mainnet, public_spend_key, public_view_key.into()); - - let res = self - .0 - .transfer(0, amount.as_piconero(), &destination_address.to_string()) - .await?; - - let tx_hash = TxHash(res.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)) - } -} - -#[async_trait] -impl CreateWalletForOutput for Wallet { - async fn create_and_load_wallet_for_output( - &self, - private_spend_key: PrivateKey, - private_view_key: PrivateViewKey, - ) -> Result<()> { - let public_spend_key = PublicKey::from_private_key(&private_spend_key); - let public_view_key = PublicKey::from_private_key(&private_view_key.into()); - - let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key); - - let _ = self - .0 - .generate_from_keys( - &address.to_string(), - &private_spend_key.to_string(), - &PrivateKey::from(private_view_key).to_string(), - ) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl WatchForTransfer for Wallet { - async fn watch_for_transfer( - &self, - public_spend_key: PublicKey, - public_view_key: PublicViewKey, - transfer_proof: TransferProof, - expected_amount: Amount, - expected_confirmations: u32, - ) -> Result<(), InsufficientFunds> { - enum Error { - TxNotFound, - InsufficientConfirmations, - InsufficientFunds { expected: Amount, actual: Amount }, - } - - let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key.into()); - - let res = (|| async { - // NOTE: Currently, this is conflating IO errors with the transaction not being - // in the blockchain yet, or not having enough confirmations on it. All these - // errors warrant a retry, but the strategy should probably differ per case - let proof = self - .0 - .check_tx_key( - &String::from(transfer_proof.tx_hash()), - &transfer_proof.tx_key().to_string(), - &address.to_string(), - ) - .await - .map_err(|_| backoff::Error::Transient(Error::TxNotFound))?; - - if proof.received != expected_amount.as_piconero() { - return Err(backoff::Error::Permanent(Error::InsufficientFunds { - expected: expected_amount, - actual: Amount::from_piconero(proof.received), - })); - } - - if proof.confirmations < expected_confirmations { - return Err(backoff::Error::Transient(Error::InsufficientConfirmations)); - } - - Ok(proof) - }) - .retry(ConstantBackoff::new(Duration::from_secs(1))) - .await; - - if let Err(Error::InsufficientFunds { expected, actual }) = res { - return Err(InsufficientFunds { expected, actual }); - }; - - Ok(()) - } -} From 082f4eb005dcbccfeca9e099d9714ec25a895261 Mon Sep 17 00:00:00 2001 From: rishflab Date: Fri, 11 Dec 2020 17:35:03 +1100 Subject: [PATCH 6/8] Use unused port in refund test --- swap/tests/e2e.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 9fa7aa09..b53f7645 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -39,6 +39,7 @@ async fn happy_path() { let xmr_alice = xmr_to_swap * 10; let xmr_bob = xmr_btc::monero::Amount::from_piconero(0); + // todo: This should not be hardcoded let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" .parse() .expect("failed to parse Alice's address"); @@ -143,6 +144,7 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { let alice_btc_starting_balance = bitcoin::Amount::ZERO; let alice_xmr_starting_balance = xmr_to_swap * 10; + // todo: This should not be hardcoded let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877" .parse() .expect("failed to parse Alice's address"); @@ -263,7 +265,8 @@ async fn both_refund() { let alice_btc_starting_balance = bitcoin::Amount::ZERO; let alice_xmr_starting_balance = xmr_to_swap * 10; - let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877" + // todo: This should not be hardcoded + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9879" .parse() .expect("failed to parse Alice's address"); From 6ff440d0ccf7161aaffbe2fb4d7879246eaf8d6d Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 14 Dec 2020 11:28:52 +1100 Subject: [PATCH 7/8] Remove unused function --- xmr-btc/src/bob.rs | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 9927be7f..54f22d69 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -621,48 +621,6 @@ impl State3 { }) } - pub async fn refund_btc( - &self, - bitcoin_wallet: &W, - ) -> Result<()> { - let tx_cancel = - bitcoin::TxCancel::new(&self.tx_lock, self.refund_timelock, self.A, self.b.public()); - let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); - - { - let sig_b = self.b.sign(tx_cancel.digest()); - let sig_a = self.tx_cancel_sig_a.clone(); - - let signed_tx_cancel = tx_cancel.clone().add_signatures( - &self.tx_lock, - (self.A, sig_a), - (self.b.public(), sig_b), - )?; - - let _ = bitcoin_wallet - .broadcast_signed_transaction(signed_tx_cancel) - .await?; - } - - { - let adaptor = Adaptor::>::default(); - - let sig_b = self.b.sign(tx_refund.digest()); - let sig_a = adaptor - .decrypt_signature(&self.s_b.into_secp256k1(), 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 _ = bitcoin_wallet - .broadcast_signed_transaction(signed_tx_refund) - .await?; - } - Ok(()) - } pub fn tx_lock_id(&self) -> bitcoin::Txid { self.tx_lock.txid() } From d8d6477ee9acec80a115a757c2dbd6b05a119386 Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 14 Dec 2020 11:56:14 +1100 Subject: [PATCH 8/8] Remove and fix comments --- swap/src/alice/swap.rs | 4 ++-- swap/src/bob/swap.rs | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/swap/src/alice/swap.rs b/swap/src/alice/swap.rs index 91242e7d..87deec4b 100644 --- a/swap/src/alice/swap.rs +++ b/swap/src/alice/swap.rs @@ -90,7 +90,7 @@ impl fmt::Display for AliceState { AliceState::Negotiated { .. } => write!(f, "negotiated"), AliceState::BtcLocked { .. } => write!(f, "btc_locked"), AliceState::XmrLocked { .. } => write!(f, "xmr_locked"), - AliceState::EncSignLearned { .. } => write!(f, "encsig_learnt"), + AliceState::EncSignLearned { .. } => write!(f, "encsig_learned"), AliceState::BtcRedeemed => write!(f, "btc_redeemed"), AliceState::BtcCancelled { .. } => write!(f, "btc_cancelled"), AliceState::BtcRefunded { .. } => write!(f, "btc_refunded"), @@ -219,7 +219,7 @@ pub async fn run_until( .await } AliceState::XmrLocked { state3 } => { - // todo: match statement and wait for t1 can probably expressed more cleanly + // todo: match statement and wait for t1 can probably be expressed more cleanly match state3.current_epoch(bitcoin_wallet.as_ref()).await? { Epoch::T0 => { let wait_for_enc_sig = wait_for_bitcoin_encrypted_signature( diff --git a/swap/src/bob/swap.rs b/swap/src/bob/swap.rs index 8dd5a797..b37fec75 100644 --- a/swap/src/bob/swap.rs +++ b/swap/src/bob/swap.rs @@ -265,7 +265,6 @@ where BobState::Cancelled(state) => { // Bob has cancelled the swap match state.current_epoch(bitcoin_wallet.as_ref()).await? { - // todo: Is this an invalid state?. Should I remove the T0 match branch Epoch::T0 => panic!("Cancelled before t1??? Something is really wrong"), Epoch::T1 => { state.refund_btc(bitcoin_wallet.as_ref()).await?; @@ -282,8 +281,6 @@ where .await } Epoch::T2 => { - // todo: If t2 has elapsed should we check whether Alice has punished? If - // not can we still refund? run_until( BobState::Punished, is_target_state,