From 584cc22024ebb06cf6e9709a96a69fc7cc1759f2 Mon Sep 17 00:00:00 2001 From: rishflab Date: Wed, 2 Dec 2020 12:09:43 +1100 Subject: [PATCH 01/11] Allow Bob to exit execution at a specified state --- Cargo.lock | 1 + swap/src/bob/swap.rs | 276 ++++++++++++++++++++++++------------------- 2 files changed, 157 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 116a079b..8d4ec1a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3369,6 +3369,7 @@ dependencies = [ "bitcoin", "bitcoin-harness", "conquer-once", + "conquer-once", "derivative", "ecdsa_fun", "futures", diff --git a/swap/src/bob/swap.rs b/swap/src/bob/swap.rs index 0d8827e4..602adeb0 100644 --- a/swap/src/bob/swap.rs +++ b/swap/src/bob/swap.rs @@ -33,10 +33,31 @@ pub enum BobState { SafelyAborted, } -// State machine driver for swap execution -#[async_recursion] pub async fn swap( state: BobState, + swarm: Swarm, + db: Database, + bitcoin_wallet: Arc, + monero_wallet: Arc, + rng: R, + swap_id: Uuid, +) -> Result + where + R: RngCore + CryptoRng + Send, +{ + run_until(state, is_complete, swarm, db, bitcoin_wallet, monero_wallet, rng, swap_id).await +} + +// TODO: use macro or generics +pub fn is_complete(state: &BobState) -> bool { + matches!(state, BobState::BtcRefunded| BobState::XmrRedeemed | BobState::Punished | BobState::SafelyAborted) +} + +// State machine driver for swap execution +#[async_recursion] +pub async fn run_until( + state: BobState, + is_state: fn(&BobState) -> bool, mut swarm: Swarm, db: Database, bitcoin_wallet: Arc, @@ -47,112 +68,120 @@ pub async fn swap( where R: RngCore + CryptoRng + Send, { - match state { - BobState::Started { - state0, - amounts, - peer_id, - addr, - } => { - let state2 = negotiate( + if is_state(&state) { + Ok(state) + } else { + match state { + BobState::Started { state0, amounts, - &mut swarm, + peer_id, addr, - &mut rng, - bitcoin_wallet.clone(), - ) - .await?; - swap( - BobState::Negotiated(state2, peer_id), - swarm, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id, - ) - .await - } - BobState::Negotiated(state2, alice_peer_id) => { - // Alice and Bob have exchanged info - let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?; - // db.insert_latest_state(state); - swap( - BobState::BtcLocked(state3, alice_peer_id), - swarm, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id, - ) - .await - } - // Bob has locked Btc - // Watch for Alice to Lock Xmr or for t1 to elapse - BobState::BtcLocked(state3, alice_peer_id) => { - // todo: watch until t1, not indefinetely - let state4 = match swarm.next().await { - OutEvent::Message2(msg) => { - state3 - .watch_for_lock_xmr(monero_wallet.as_ref(), msg) - .await? - } - other => panic!("unexpected event: {:?}", other), - }; - swap( - BobState::XmrLocked(state4, alice_peer_id), - swarm, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id, - ) - .await - } - BobState::XmrLocked(state, alice_peer_id) => { - // Alice has locked Xmr - // Bob sends Alice his key - let tx_redeem_encsig = state.tx_redeem_encsig(); - // Do we have to wait for a response? - // What if Alice fails to receive this? Should we always resend? - // todo: If we cannot dial Alice we should go to EncSigSent. Maybe dialing - // should happen in this arm? - swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig); + } => { + let state2 = negotiate( + state0, + amounts, + &mut swarm, + addr, + &mut rng, + bitcoin_wallet.clone(), + ) + .await?; + run_until( + BobState::Negotiated(state2, peer_id), + is_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await + } + BobState::Negotiated(state2, alice_peer_id) => { + // Alice and Bob have exchanged info + let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?; + // db.insert_latest_state(state); + run_until( + BobState::BtcLocked(state3, alice_peer_id), + is_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await + } + // Bob has locked Btc + // Watch for Alice to Lock Xmr or for t1 to elapse + BobState::BtcLocked(state3, alice_peer_id) => { + // todo: watch until t1, not indefinetely + let state4 = match swarm.next().await { + OutEvent::Message2(msg) => { + state3 + .watch_for_lock_xmr(monero_wallet.as_ref(), msg) + .await? + } + other => panic!("unexpected event: {:?}", other), + }; + run_until( + BobState::XmrLocked(state4, alice_peer_id), + is_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await + } + BobState::XmrLocked(state, alice_peer_id) => { + // Alice has locked Xmr + // Bob sends Alice his key + let tx_redeem_encsig = state.tx_redeem_encsig(); + // Do we have to wait for a response? + // What if Alice fails to receive this? Should we always resend? + // todo: If we cannot dial Alice we should go to EncSigSent. Maybe dialing + // should happen in this arm? + swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig); - // Sadly we have to poll the swarm to get make sure the message is sent? - // FIXME: Having to wait for Alice's response here is a big problem, because - // we're stuck if she doesn't send her response back. I believe this is - // currently necessary, so we may have to rework this and/or how we use libp2p - match swarm.next().await { - OutEvent::Message3 => { - debug!("Got Message3 empty response"); - } - other => panic!("unexpected event: {:?}", other), - }; + // Sadly we have to poll the swarm to get make sure the message is sent? + // FIXME: Having to wait for Alice's response here is a big problem, because + // we're stuck if she doesn't send her response back. I believe this is + // currently necessary, so we may have to rework this and/or how we use libp2p + match swarm.next().await { + OutEvent::Message3 => { + debug!("Got Message3 empty response"); + } + other => panic!("unexpected event: {:?}", other), + }; - swap( - BobState::EncSigSent(state, alice_peer_id), - swarm, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id, - ) - .await - } - BobState::EncSigSent(state, ..) => { - // Watch for redeem - let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref()); - let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref()); + run_until( + BobState::EncSigSent(state, alice_peer_id), + is_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await + } + BobState::EncSigSent(state, ..) => { + // Watch for redeem + let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref()); + let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref()); - tokio::select! { + tokio::select! { val = redeem_watcher => { - swap( + run_until( BobState::BtcRedeemed(val?), + is_state, swarm, db, bitcoin_wallet, @@ -169,8 +198,9 @@ where state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; } - swap( + run_until( BobState::Cancelled(state), + is_state, swarm, db, bitcoin_wallet, @@ -182,29 +212,35 @@ where } } + } + BobState::BtcRedeemed(state) => { + // Bob redeems XMR using revealed s_a + state.claim_xmr(monero_wallet.as_ref()).await?; + run_until( + BobState::XmrRedeemed, + is_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await + } + BobState::Cancelled(_state) => Ok(BobState::BtcRefunded), + BobState::BtcRefunded => Ok(BobState::BtcRefunded), + BobState::Punished => Ok(BobState::Punished), + BobState::SafelyAborted => Ok(BobState::SafelyAborted), + BobState::XmrRedeemed => Ok(BobState::XmrRedeemed), } - BobState::BtcRedeemed(state) => { - // Bob redeems XMR using revealed s_a - state.claim_xmr(monero_wallet.as_ref()).await?; - swap( - BobState::XmrRedeemed, - swarm, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id, - ) - .await - } - BobState::Cancelled(_state) => Ok(BobState::BtcRefunded), - BobState::BtcRefunded => Ok(BobState::BtcRefunded), - BobState::Punished => Ok(BobState::Punished), - BobState::SafelyAborted => Ok(BobState::SafelyAborted), - BobState::XmrRedeemed => Ok(BobState::XmrRedeemed), } } + + + + // // State machine driver for recovery execution // #[async_recursion] // pub async fn abort(state: BobState, io: Io) -> Result { From 5fef68322a7fa41b6015f6c8ac29e545c440236e Mon Sep 17 00:00:00 2001 From: rishflab Date: Wed, 2 Dec 2020 12:34:47 +1100 Subject: [PATCH 02/11] Allow Alice to exit execution at a specified state --- swap/src/alice/swap.rs | 63 ++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/swap/src/alice/swap.rs b/swap/src/alice/swap.rs index 2864eba8..0142527d 100644 --- a/swap/src/alice/swap.rs +++ b/swap/src/alice/swap.rs @@ -86,16 +86,38 @@ pub enum AliceState { SafelyAborted, } +pub async fn swap( + state: AliceState, + swarm: Swarm, + bitcoin_wallet: Arc, + monero_wallet: Arc, +) -> Result + where + R: RngCore + CryptoRng + Send, +{ + run_until(state, is_complete, swarm, bitcoin_wallet, monero_wallet).await +} + +// TODO: use macro or generics +pub fn is_complete(state: &AliceState) -> bool { + matches!(state, AliceState::XmrRefunded| AliceState::BtcRedeemed | AliceState::Punished | AliceState::SafelyAborted) +} + + // State machine driver for swap execution #[async_recursion] -pub async fn swap( +pub async fn run_until( state: AliceState, + is_state: fn(&AliceState) -> bool, mut swarm: Swarm, bitcoin_wallet: Arc, monero_wallet: Arc, config: Config, ) -> Result { - match state { + if is_state(&state) { + Ok(state) + } else { + match state { AliceState::Started { amounts, a, @@ -113,12 +135,13 @@ pub async fn swap( ) .await?; - swap( + run_until( AliceState::Negotiated { channel, amounts, state3, }, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -134,12 +157,13 @@ pub async fn swap( let _ = wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config) .await?; - swap( + run_until( AliceState::BtcLocked { channel, amounts, state3, }, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -161,8 +185,9 @@ pub async fn swap( ) .await?; - swap( + run_until( AliceState::XmrLocked { state3 }, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -175,11 +200,12 @@ pub async fn swap( // step fails match wait_for_bitcoin_encrypted_signature(&mut swarm).await { Ok(encrypted_signature) => { - swap( + run_until( AliceState::EncSignLearned { state3, encrypted_signature, }, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -188,8 +214,9 @@ pub async fn swap( .await } Err(_) => { - swap( + run_until( AliceState::WaitingToCancel { state3 }, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -213,8 +240,9 @@ pub async fn swap( ) { Ok(tx) => tx, Err(_) => { - return swap( + return run_until( AliceState::WaitingToCancel { state3 }, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -230,8 +258,9 @@ pub async fn swap( publish_bitcoin_redeem_transaction(signed_tx_redeem, bitcoin_wallet.clone(), config) .await?; - swap( + run_until( AliceState::BtcRedeemed, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -250,8 +279,9 @@ pub async fn swap( ) .await?; - swap( + run_until( AliceState::BtcCancelled { state3, tx_cancel }, + is_state, swarm, bitcoin_wallet, monero_wallet, @@ -276,8 +306,9 @@ pub async fn swap( // TODO(Franck): Review error handling match published_refund_tx { None => { - swap( + run_until( AliceState::BtcPunishable { tx_refund, state3 }, + is_state, swarm, bitcoin_wallet.clone(), monero_wallet, @@ -286,12 +317,13 @@ pub async fn swap( .await } Some(published_refund_tx) => { - swap( + run_until( AliceState::BtcRefunded { tx_refund, published_refund_tx, state3, }, + is_state, swarm, bitcoin_wallet.clone(), monero_wallet, @@ -345,8 +377,9 @@ pub async fn swap( match select(punish_tx_finalised, refund_tx_seen).await { Either::Left(_) => { - swap( + run_until( AliceState::Punished, + is_state, swarm, bitcoin_wallet.clone(), monero_wallet, @@ -355,12 +388,13 @@ pub async fn swap( .await } Either::Right((published_refund_tx, _)) => { - swap( + run_until( AliceState::BtcRefunded { tx_refund, published_refund_tx, state3, }, + is_state, swarm, bitcoin_wallet.clone(), monero_wallet, @@ -375,4 +409,5 @@ pub async fn swap( AliceState::Punished => Ok(AliceState::Punished), AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), } + } } From c91e9652aa4b6e452dcf9238195f516865f2bc41 Mon Sep 17 00:00:00 2001 From: rishflab Date: Wed, 2 Dec 2020 12:36:47 +1100 Subject: [PATCH 03/11] Add alice punish test Use reusable test init functions for happy path test Extract tracing setup to reusable function Move test initialization to seperate functions Increase stack size in CI Fix monero max finality time Force Bob swarm polling to send message 2 Run Bob state to xmr_locked in punish test to force the sending of message2. Previously Bob state was run until btc_locked. Although this was the right thing to do, message2 was not being sent as the swarm was not polled in btc_locked. Alice punish test passes. Add info logging to executor --- .github/workflows/ci.yml | 1 + Cargo.lock | 1 - swap/src/alice/execution.rs | 15 +- swap/src/alice/swap.rs | 598 +++++++++++++++++++----------------- swap/src/bob/swap.rs | 179 +++++++---- swap/tests/e2e.rs | 463 ++++++++++++++++------------ xmr-btc/src/bob.rs | 3 + xmr-btc/src/config.rs | 11 +- 8 files changed, 731 insertions(+), 540 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea67a01c..0602667f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,6 +104,7 @@ jobs: run: cargo test --workspace --all-features env: MONERO_ADDITIONAL_SLEEP_PERIOD: 60000 + RUST_MIN_STACK: 100000000000 - name: Build binary run: | diff --git a/Cargo.lock b/Cargo.lock index 8d4ec1a7..116a079b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3369,7 +3369,6 @@ dependencies = [ "bitcoin", "bitcoin-harness", "conquer-once", - "conquer-once", "derivative", "ecdsa_fun", "futures", diff --git a/swap/src/alice/execution.rs b/swap/src/alice/execution.rs index 89ffa8fa..7ff638ae 100644 --- a/swap/src/alice/execution.rs +++ b/swap/src/alice/execution.rs @@ -5,7 +5,6 @@ use crate::{ SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, }; use anyhow::{bail, Context, Result}; -use conquer_once::Lazy; use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic}; use futures::{ future::{select, Either}, @@ -29,13 +28,6 @@ use xmr_btc::{ monero::Transfer, }; -// The maximum we assume we need to wait from the moment the monero transaction -// is mined to the moment it reaches finality. We set 15 confirmations for now -// (based on Kraken). 1.5 multiplier in case the blockchain is slower than -// usually. Average of 2 minutes block time -static MONERO_MAX_FINALITY_TIME: Lazy = - Lazy::new(|| Duration::from_secs_f64(15f64 * 1.5 * 2f64 * 60f64)); - pub async fn negotiate( amounts: SwapAmounts, a: bitcoin::SecretKey, @@ -180,8 +172,11 @@ where Ok(()) } -pub async fn wait_for_bitcoin_encrypted_signature(swarm: &mut Swarm) -> Result { - let event = timeout(*MONERO_MAX_FINALITY_TIME, swarm.next()) +pub async fn wait_for_bitcoin_encrypted_signature( + swarm: &mut Swarm, + timeout_duration: Duration, +) -> Result { + let event = timeout(timeout_duration, swarm.next()) .await .context("Failed to receive Bitcoin encrypted signature from Bob")?; diff --git a/swap/src/alice/swap.rs b/swap/src/alice/swap.rs index 0142527d..b96117ba 100644 --- a/swap/src/alice/swap.rs +++ b/swap/src/alice/swap.rs @@ -24,7 +24,8 @@ use futures::{ }; use libp2p::request_response::ResponseChannel; use rand::{CryptoRng, RngCore}; -use std::sync::Arc; +use std::{fmt, sync::Arc}; +use tracing::info; use xmr_btc::{ alice::State3, bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction}, @@ -86,328 +87,377 @@ pub enum AliceState { SafelyAborted, } -pub async fn swap( +impl fmt::Display for AliceState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AliceState::Started { .. } => write!(f, "started"), + AliceState::Negotiated { .. } => write!(f, "negotiated"), + AliceState::BtcLocked { .. } => write!(f, "btc_locked"), + AliceState::XmrLocked { .. } => write!(f, "xmr_locked"), + AliceState::EncSignLearned { .. } => write!(f, "encsig_sent"), + AliceState::BtcRedeemed => write!(f, "btc_redeemed"), + AliceState::BtcCancelled { .. } => write!(f, "btc_cancelled"), + AliceState::BtcRefunded { .. } => write!(f, "btc_refunded"), + AliceState::Punished => write!(f, "punished"), + 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"), + } + } +} + +pub async fn swap( state: AliceState, swarm: Swarm, bitcoin_wallet: Arc, monero_wallet: Arc, -) -> Result - where - R: RngCore + CryptoRng + Send, -{ - run_until(state, is_complete, swarm, bitcoin_wallet, monero_wallet).await + config: Config, +) -> Result<(AliceState, Swarm)> { + run_until( + state, + is_complete, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await } // TODO: use macro or generics pub fn is_complete(state: &AliceState) -> bool { - matches!(state, AliceState::XmrRefunded| AliceState::BtcRedeemed | AliceState::Punished | AliceState::SafelyAborted) + matches!( + state, + AliceState::XmrRefunded + | AliceState::BtcRedeemed + | AliceState::Punished + | AliceState::SafelyAborted + ) } +pub fn is_xmr_locked(state: &AliceState) -> bool { + matches!( + state, + AliceState::XmrLocked{..} + ) +} // State machine driver for swap execution #[async_recursion] pub async fn run_until( state: AliceState, - is_state: fn(&AliceState) -> bool, + is_target_state: fn(&AliceState) -> bool, mut swarm: Swarm, bitcoin_wallet: Arc, monero_wallet: Arc, config: Config, -) -> Result { - if is_state(&state) { - Ok(state) +) -> Result<(AliceState, Swarm)> { + info!("{}", state); + if is_target_state(&state) { + Ok((state, swarm)) } else { match state { - AliceState::Started { - amounts, - a, - s_a, - v_a, - } => { - let (channel, state3) = negotiate( + AliceState::Started { amounts, a, s_a, v_a, - &mut swarm, - bitcoin_wallet.clone(), - config, - ) - .await?; - - run_until( - AliceState::Negotiated { - channel, + } => { + let (channel, state3) = negotiate( amounts, - state3, - }, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) - .await - } - AliceState::Negotiated { - state3, - channel, - amounts, - } => { - let _ = wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config) + a, + s_a, + v_a, + &mut swarm, + bitcoin_wallet.clone(), + config, + ) .await?; - run_until( - AliceState::BtcLocked { - channel, - amounts, - state3, - }, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) - .await - } - AliceState::BtcLocked { - channel, - amounts, - state3, - } => { - lock_xmr( + run_until( + AliceState::Negotiated { + channel, + amounts, + state3, + }, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } + AliceState::Negotiated { + state3, channel, amounts, - state3.clone(), - &mut swarm, - monero_wallet.clone(), - ) - .await?; + } => { + let _ = + wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config) + .await?; - run_until( - AliceState::XmrLocked { state3 }, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) - .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 swarm).await { - Ok(encrypted_signature) => { - run_until( - AliceState::EncSignLearned { - state3, - encrypted_signature, - }, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) - .await - } - Err(_) => { - run_until( - AliceState::WaitingToCancel { state3 }, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) - .await + run_until( + AliceState::BtcLocked { + channel, + amounts, + state3, + }, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } + AliceState::BtcLocked { + channel, + amounts, + state3, + } => { + lock_xmr( + channel, + amounts, + state3.clone(), + &mut swarm, + monero_wallet.clone(), + ) + .await?; + + run_until( + AliceState::XmrLocked { state3 }, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .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 swarm, + config.monero_max_finality_time, + ) + .await + { + Ok(encrypted_signature) => { + run_until( + AliceState::EncSignLearned { + state3, + encrypted_signature, + }, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } + Err(_) => { + run_until( + AliceState::WaitingToCancel { state3 }, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } } } - } - AliceState::EncSignLearned { - state3, - encrypted_signature, - } => { - let signed_tx_redeem = match build_bitcoin_redeem_transaction( + AliceState::EncSignLearned { + state3, encrypted_signature, - &state3.tx_lock, - state3.a.clone(), - state3.s_a, - state3.B, - &state3.redeem_address, - ) { - Ok(tx) => tx, - Err(_) => { - return run_until( - AliceState::WaitingToCancel { state3 }, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) + } => { + let signed_tx_redeem = match build_bitcoin_redeem_transaction( + encrypted_signature, + &state3.tx_lock, + state3.a.clone(), + state3.s_a, + state3.B, + &state3.redeem_address, + ) { + Ok(tx) => tx, + Err(_) => { + return run_until( + AliceState::WaitingToCancel { state3 }, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await; + } + }; + + // TODO(Franck): Error handling is delicate here. + // If Bob sees this transaction he can redeem Monero + // e.g. If the Bitcoin node is down then the user needs to take action. + publish_bitcoin_redeem_transaction( + signed_tx_redeem, + bitcoin_wallet.clone(), + config, + ) + .await?; + + run_until( + AliceState::BtcRedeemed, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } + AliceState::WaitingToCancel { state3 } => { + let tx_cancel = publish_cancel_transaction( + state3.tx_lock.clone(), + state3.a.clone(), + state3.B, + state3.refund_timelock, + state3.tx_cancel_sig_bob.clone(), + bitcoin_wallet.clone(), + ) + .await?; + + run_until( + AliceState::BtcCancelled { state3, tx_cancel }, + is_target_state, + swarm, + bitcoin_wallet, + monero_wallet, + config, + ) + .await + } + AliceState::BtcCancelled { state3, tx_cancel } => { + let tx_cancel_height = bitcoin_wallet + .transaction_block_height(tx_cancel.txid()) .await; - } - }; - // TODO(Franck): Error handling is delicate here. - // If Bob sees this transaction he can redeem Monero - // e.g. If the Bitcoin node is down then the user needs to take action. - publish_bitcoin_redeem_transaction(signed_tx_redeem, bitcoin_wallet.clone(), config) + let (tx_refund, published_refund_tx) = wait_for_bitcoin_refund( + &tx_cancel, + tx_cancel_height, + state3.punish_timelock, + &state3.refund_address, + bitcoin_wallet.clone(), + ) .await?; - run_until( - AliceState::BtcRedeemed, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) - .await - } - AliceState::WaitingToCancel { state3 } => { - let tx_cancel = publish_cancel_transaction( - state3.tx_lock.clone(), - state3.a.clone(), - state3.B, - state3.refund_timelock, - state3.tx_cancel_sig_bob.clone(), - bitcoin_wallet.clone(), - ) - .await?; - - run_until( - AliceState::BtcCancelled { state3, tx_cancel }, - is_state, - swarm, - bitcoin_wallet, - monero_wallet, - config, - ) - .await - } - AliceState::BtcCancelled { state3, tx_cancel } => { - let tx_cancel_height = bitcoin_wallet - .transaction_block_height(tx_cancel.txid()) - .await; - - let (tx_refund, published_refund_tx) = wait_for_bitcoin_refund( - &tx_cancel, - tx_cancel_height, - state3.punish_timelock, - &state3.refund_address, - bitcoin_wallet.clone(), - ) - .await?; - - // TODO(Franck): Review error handling - match published_refund_tx { - None => { - run_until( - AliceState::BtcPunishable { tx_refund, state3 }, - is_state, - swarm, - bitcoin_wallet.clone(), - monero_wallet, - config, - ) - .await - } - Some(published_refund_tx) => { - run_until( - AliceState::BtcRefunded { - tx_refund, - published_refund_tx, - state3, - }, - is_state, - swarm, - bitcoin_wallet.clone(), - monero_wallet, - config, - ) - .await + // TODO(Franck): Review error handling + match published_refund_tx { + None => { + run_until( + AliceState::BtcPunishable { tx_refund, state3 }, + is_target_state, + swarm, + bitcoin_wallet.clone(), + monero_wallet, + config, + ) + .await + } + Some(published_refund_tx) => { + run_until( + AliceState::BtcRefunded { + tx_refund, + published_refund_tx, + state3, + }, + is_target_state, + swarm, + bitcoin_wallet.clone(), + monero_wallet, + config, + ) + .await + } } } - } - AliceState::BtcRefunded { - tx_refund, - published_refund_tx, - state3, - } => { - let spend_key = extract_monero_private_key( - published_refund_tx, + AliceState::BtcRefunded { tx_refund, - state3.s_a, - state3.a.clone(), - state3.S_b_bitcoin, - )?; - let view_key = state3.v; + published_refund_tx, + state3, + } => { + let spend_key = extract_monero_private_key( + published_refund_tx, + tx_refund, + state3.s_a, + state3.a.clone(), + state3.S_b_bitcoin, + )?; + let view_key = state3.v; - monero_wallet - .create_and_load_wallet_for_output(spend_key, view_key) - .await?; + monero_wallet + .create_and_load_wallet_for_output(spend_key, view_key) + .await?; - Ok(AliceState::XmrRefunded) - } - AliceState::BtcPunishable { tx_refund, state3 } => { - let signed_tx_punish = build_bitcoin_punish_transaction( - &state3.tx_lock, - state3.refund_timelock, - &state3.punish_address, - state3.punish_timelock, - state3.tx_punish_sig_bob.clone(), - state3.a.clone(), - state3.B, - )?; + Ok((AliceState::XmrRefunded, swarm)) + } + AliceState::BtcPunishable { tx_refund, state3 } => { + let signed_tx_punish = build_bitcoin_punish_transaction( + &state3.tx_lock, + state3.refund_timelock, + &state3.punish_address, + state3.punish_timelock, + state3.tx_punish_sig_bob.clone(), + state3.a.clone(), + state3.B, + )?; - let punish_tx_finalised = publish_bitcoin_punish_transaction( - signed_tx_punish, - bitcoin_wallet.clone(), - config, - ); + let punish_tx_finalised = publish_bitcoin_punish_transaction( + signed_tx_punish, + bitcoin_wallet.clone(), + config, + ); - let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()); + let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid()); - pin_mut!(punish_tx_finalised); - pin_mut!(refund_tx_seen); + pin_mut!(punish_tx_finalised); + pin_mut!(refund_tx_seen); - match select(punish_tx_finalised, refund_tx_seen).await { - Either::Left(_) => { - run_until( - AliceState::Punished, - is_state, - swarm, - bitcoin_wallet.clone(), - monero_wallet, - config, - ) - .await - } - Either::Right((published_refund_tx, _)) => { - run_until( - AliceState::BtcRefunded { - tx_refund, - published_refund_tx, - state3, - }, - is_state, - swarm, - bitcoin_wallet.clone(), - monero_wallet, - config, - ) - .await + match select(punish_tx_finalised, refund_tx_seen).await { + Either::Left(_) => { + run_until( + AliceState::Punished, + is_target_state, + swarm, + bitcoin_wallet.clone(), + monero_wallet, + config, + ) + .await + } + Either::Right((published_refund_tx, _)) => { + run_until( + AliceState::BtcRefunded { + tx_refund, + published_refund_tx, + state3, + }, + is_target_state, + swarm, + bitcoin_wallet.clone(), + monero_wallet, + config, + ) + .await + } } } + AliceState::XmrRefunded => Ok((AliceState::XmrRefunded, swarm)), + AliceState::BtcRedeemed => Ok((AliceState::BtcRedeemed, swarm)), + AliceState::Punished => Ok((AliceState::Punished, swarm)), + AliceState::SafelyAborted => Ok((AliceState::SafelyAborted, swarm)), } - AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), - AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), - AliceState::Punished => Ok(AliceState::Punished), - AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), - } } } diff --git a/swap/src/bob/swap.rs b/swap/src/bob/swap.rs index 602adeb0..443a86af 100644 --- a/swap/src/bob/swap.rs +++ b/swap/src/bob/swap.rs @@ -7,8 +7,8 @@ use anyhow::Result; use async_recursion::async_recursion; use libp2p::{core::Multiaddr, PeerId}; use rand::{CryptoRng, RngCore}; -use std::sync::Arc; -use tracing::debug; +use std::{fmt, sync::Arc}; +use tracing::{debug, info}; use uuid::Uuid; use xmr_btc::bob::{self}; @@ -33,6 +33,24 @@ pub enum BobState { SafelyAborted, } +impl fmt::Display for BobState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BobState::Started { .. } => write!(f, "started"), + BobState::Negotiated(..) => write!(f, "negotiated"), + 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::XmrRedeemed => write!(f, "xmr_redeemed"), + BobState::Punished => write!(f, "punished"), + BobState::SafelyAborted => write!(f, "safely_aborted"), + } + } +} + pub async fn swap( state: BobState, swarm: Swarm, @@ -42,22 +60,47 @@ pub async fn swap( rng: R, swap_id: Uuid, ) -> Result - where - R: RngCore + CryptoRng + Send, +where + R: RngCore + CryptoRng + Send, { - run_until(state, is_complete, swarm, db, bitcoin_wallet, monero_wallet, rng, swap_id).await + run_until( + state, + is_complete, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await } // TODO: use macro or generics pub fn is_complete(state: &BobState) -> bool { - matches!(state, BobState::BtcRefunded| BobState::XmrRedeemed | BobState::Punished | BobState::SafelyAborted) + matches!( + state, + BobState::BtcRefunded + | BobState::XmrRedeemed + | BobState::Punished + | BobState::SafelyAborted + ) +} + +pub fn is_btc_locked(state: &BobState) -> bool { + matches!(state, BobState::BtcLocked(..)) +} + +pub fn is_xmr_locked(state: &BobState) -> bool { + matches!(state, BobState::XmrLocked(..)) } // State machine driver for swap execution +#[allow(clippy::too_many_arguments)] #[async_recursion] pub async fn run_until( state: BobState, - is_state: fn(&BobState) -> bool, + is_target_state: fn(&BobState) -> bool, mut swarm: Swarm, db: Database, bitcoin_wallet: Arc, @@ -68,7 +111,8 @@ pub async fn run_until( where R: RngCore + CryptoRng + Send, { - if is_state(&state) { + info!("{}", state); + if is_target_state(&state) { Ok(state) } else { match state { @@ -86,10 +130,10 @@ where &mut rng, bitcoin_wallet.clone(), ) - .await?; + .await?; run_until( BobState::Negotiated(state2, peer_id), - is_state, + is_target_state, swarm, db, bitcoin_wallet, @@ -97,7 +141,7 @@ where rng, swap_id, ) - .await + .await } BobState::Negotiated(state2, alice_peer_id) => { // Alice and Bob have exchanged info @@ -105,7 +149,7 @@ where // db.insert_latest_state(state); run_until( BobState::BtcLocked(state3, alice_peer_id), - is_state, + is_target_state, swarm, db, bitcoin_wallet, @@ -113,7 +157,7 @@ where rng, swap_id, ) - .await + .await } // Bob has locked Btc // Watch for Alice to Lock Xmr or for t1 to elapse @@ -129,7 +173,7 @@ where }; run_until( BobState::XmrLocked(state4, alice_peer_id), - is_state, + is_target_state, swarm, db, bitcoin_wallet, @@ -137,7 +181,7 @@ where rng, swap_id, ) - .await + .await } BobState::XmrLocked(state, alice_peer_id) => { // Alice has locked Xmr @@ -162,7 +206,7 @@ where run_until( BobState::EncSigSent(state, alice_peer_id), - is_state, + is_target_state, swarm, db, bitcoin_wallet, @@ -170,7 +214,7 @@ where rng, swap_id, ) - .await + .await } BobState::EncSigSent(state, ..) => { // Watch for redeem @@ -178,47 +222,47 @@ where let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref()); tokio::select! { - val = redeem_watcher => { - run_until( - BobState::BtcRedeemed(val?), - is_state, - swarm, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id, - ) - .await - } - _ = t1_timeout => { - // Check whether TxCancel has been published. - // We should not fail if the transaction is already on the blockchain - if state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await.is_err() { - state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; + val = redeem_watcher => { + run_until( + BobState::BtcRedeemed(val?), + is_target_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id, + ) + .await } + _ = t1_timeout => { + // Check whether TxCancel has been published. + // We should not fail if the transaction is already on the blockchain + if state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await.is_err() { + state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; + } - run_until( - BobState::Cancelled(state), - is_state, - swarm, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id - ) - .await + run_until( + BobState::Cancelled(state), + is_target_state, + swarm, + db, + bitcoin_wallet, + monero_wallet, + rng, + swap_id + ) + .await + } } } - } BobState::BtcRedeemed(state) => { // Bob redeems XMR using revealed s_a state.claim_xmr(monero_wallet.as_ref()).await?; run_until( BobState::XmrRedeemed, - is_state, + is_target_state, swarm, db, bitcoin_wallet, @@ -226,21 +270,40 @@ where rng, swap_id, ) - .await + .await + } + BobState::Cancelled(_state) => { + // Bob has cancelled the swap + // If { + info!("btc refunded"); + Ok(BobState::BtcRefunded) + } + BobState::Punished => { + info!("punished"); + Ok(BobState::Punished) + } + BobState::SafelyAborted => { + info!("safely aborted"); + Ok(BobState::SafelyAborted) + } + BobState::XmrRedeemed => { + info!("xmr redeemed"); + Ok(BobState::XmrRedeemed) } - BobState::Cancelled(_state) => Ok(BobState::BtcRefunded), - BobState::BtcRefunded => Ok(BobState::BtcRefunded), - BobState::Punished => Ok(BobState::Punished), - BobState::SafelyAborted => Ok(BobState::SafelyAborted), - BobState::XmrRedeemed => Ok(BobState::XmrRedeemed), } } } - - - - // // State machine driver for recovery execution // #[async_recursion] // pub async fn abort(state: BobState, io: Io) -> Result { diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 5807a913..f19143fc 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -1,6 +1,6 @@ use bitcoin_harness::Bitcoind; -use futures::{channel::mpsc, future::try_join}; -use libp2p::Multiaddr; +use futures::future::try_join; +use libp2p::{Multiaddr, PeerId}; use monero_harness::Monero; use rand::rngs::OsRng; use std::sync::Arc; @@ -11,25 +11,20 @@ use swap::{ use tempfile::tempdir; use testcontainers::clients::Cli; use uuid::Uuid; -use xmr_btc::{bitcoin, cross_curve_dleq}; +use xmr_btc::{bitcoin, config::Config, cross_curve_dleq}; + +/// Run the following tests with RUST_MIN_STACK=100000000000 -#[ignore] #[tokio::test] -async fn swap() { - 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 alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" - .parse() - .expect("failed to parse Alice's address"); - +async fn happy_path() { + init_tracing(); let cli = Cli::default(); let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); - dbg!(&bitcoind.node_url); let _ = bitcoind.init(5).await; + let (monero, _container) = + Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) + .await + .unwrap(); let btc = bitcoin::Amount::from_sat(1_000_000); let btc_alice = bitcoin::Amount::ZERO; @@ -41,197 +36,35 @@ async fn swap() { let xmr_alice = xmr * 10; let xmr_bob = 0; - let alice_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) - .await - .unwrap(), - ); - let bob_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) - .await - .unwrap(), - ); - bitcoind - .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) - .await - .unwrap(); - - let (monero, _container) = - Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) - .await - .unwrap(); - monero - .init(vec![("alice", xmr_alice), ("bob", xmr_bob)]) - .await - .unwrap(); - - let alice_xmr_wallet = Arc::new(swap::monero::Wallet( - monero.wallet("alice").unwrap().client(), - )); - let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); - - let alice_behaviour = alice::Behaviour::default(); - let alice_transport = build(alice_behaviour.identity()).unwrap(); - - let db = Database::open(std::path::Path::new("../.swap-db/")).unwrap(); - let alice_swap = alice::swap( - alice_btc_wallet.clone(), - alice_xmr_wallet.clone(), - db, - alice_multiaddr.clone(), - alice_transport, - alice_behaviour, - ); - - let db_dir = tempdir().unwrap(); - let db = Database::open(db_dir.path()).unwrap(); - let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); - let (mut rsp_tx, rsp_rx) = mpsc::channel(1); - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(bob_behaviour.identity()).unwrap(); - let bob_swap = bob::swap( - bob_btc_wallet.clone(), - bob_xmr_wallet.clone(), - db, - btc.as_sat(), + let ( + alice_state, + alice_swarm, + alice_btc_wallet, + alice_xmr_wallet, + alice_peer_id, alice_multiaddr, - cmd_tx, - rsp_rx, - bob_transport, - bob_behaviour, - ); + ) = init_alice(&bitcoind, &monero, btc, btc_alice, xmr, xmr_alice).await; - // automate the verification step by accepting any amounts sent over by Alice - rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); - - try_join(alice_swap, bob_swap).await.unwrap(); - - let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); - let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap(); - - let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); - - bob_xmr_wallet.as_ref().0.refresh().await.unwrap(); - let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); - - assert_eq!( - btc_alice_final, - btc_alice + btc - bitcoin::Amount::from_sat(bitcoin::TX_FEE) - ); - assert!(btc_bob_final <= btc_bob - btc); - - assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); - assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); -} - -#[tokio::test] -async fn happy_path_recursive_executor() { - 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 alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" - .parse() - .expect("failed to parse Alice's address"); - - let cli = Cli::default(); - let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); - dbg!(&bitcoind.node_url); - let _ = bitcoind.init(5).await; - - let btc = bitcoin::Amount::from_sat(1_000_000); - let btc_alice = bitcoin::Amount::ZERO; - let btc_bob = btc * 10; - - // this xmr value matches the logic of alice::calculate_amounts i.e. btc * - // 10_000 * 100 - let xmr = 1_000_000_000_000; - let xmr_alice = xmr * 10; - let xmr_bob = 0; - - let alice_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) - .await - .unwrap(), - ); - let bob_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) - .await - .unwrap(), - ); - bitcoind - .mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob) - .await - .unwrap(); - - let (monero, _container) = - Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) - .await - .unwrap(); - monero - .init(vec![("alice", xmr_alice), ("bob", xmr_bob)]) - .await - .unwrap(); - - let alice_xmr_wallet = Arc::new(swap::monero::Wallet( - monero.wallet("alice").unwrap().client(), - )); - let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); - - let amounts = SwapAmounts { + let (bob_state, bob_swarm, bob_btc_wallet, bob_xmr_wallet, bob_db) = init_bob( + alice_multiaddr, + alice_peer_id, + &bitcoind, + &monero, btc, - xmr: xmr_btc::monero::Amount::from_piconero(xmr), - }; + btc_bob, + xmr, + xmr_bob, + ) + .await; - let alice_behaviour = alice::Behaviour::default(); - let alice_peer_id = alice_behaviour.peer_id().clone(); - let alice_transport = build(alice_behaviour.identity()).unwrap(); - let rng = &mut OsRng; - 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(alice_multiaddr.clone(), alice_transport, alice_behaviour).unwrap(); - let config = xmr_btc::config::Config::regtest(); let alice_swap = alice::swap::swap( alice_state, alice_swarm, alice_btc_wallet.clone(), alice_xmr_wallet.clone(), - config, + Config::regtest(), ); - let bob_db_dir = tempdir().unwrap(); - let bob_db = Database::open(bob_db_dir.path()).unwrap(); - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(bob_behaviour.identity()).unwrap(); - - let refund_address = bob_btc_wallet.new_address().await.unwrap(); - let state0 = xmr_btc::bob::State0::new( - rng, - btc, - xmr_btc::monero::Amount::from_piconero(xmr), - REFUND_TIMELOCK, - PUNISH_TIMELOCK, - refund_address, - ); - let bob_state = BobState::Started { - state0, - amounts, - peer_id: alice_peer_id, - addr: alice_multiaddr, - }; - let bob_swarm = bob::new_swarm(bob_transport, bob_behaviour).unwrap(); let bob_swap = bob::swap::swap( bob_state, bob_swarm, @@ -261,3 +94,241 @@ async fn happy_path_recursive_executor() { assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); } + +/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice +/// the encsig and fail to refund or redeem. Alice punishes. +#[tokio::test] +async fn alice_punishes_if_bob_never_acts_after_fund() { + init_tracing(); + 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 = 1_000_000_000_000; + + let bob_btc_starting_balance = btc_to_swap * 10; + let bob_xmr_starting_balance = 0; + + let alice_btc_starting_balance = bitcoin::Amount::ZERO; + let alice_xmr_starting_balance = xmr_to_swap * 10; + + let ( + alice_state, + alice_swarm, + alice_btc_wallet, + alice_xmr_wallet, + alice_peer_id, + alice_multiaddr, + ) = init_alice( + &bitcoind, + &monero, + btc_to_swap, + alice_btc_starting_balance, + xmr_to_swap, + alice_xmr_starting_balance, + ) + .await; + + let (bob_state, bob_swarm, 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_xmr_locked_fut = bob::swap::run_until( + bob_state, + bob::swap::is_xmr_locked, + bob_swarm, + bob_db, + bob_btc_wallet.clone(), + bob_xmr_wallet.clone(), + OsRng, + Uuid::new_v4(), + ); + + let alice_xmr_locked_fut = alice::swap::run_until( + alice_state, + alice::swap::is_xmr_locked, + alice_swarm, + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + Config::regtest(), + ); + + // Wait until alice has locked xmr and bob has locked btc + let ((alice_state, alice_swarm), _bob_state) = + try_join(alice_xmr_locked_fut, bob_xmr_locked_fut) + .await + .unwrap(); + + let (punished, _) = alice::swap::swap( + alice_state, + alice_swarm, + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + Config::regtest(), + ) + .await + .unwrap(); + + assert!(matches!(punished, AliceState::Punished)); + + // todo: Add balance assertions +} + +#[allow(clippy::too_many_arguments)] +async fn init_alice( + bitcoind: &Bitcoind<'_>, + monero: &Monero, + btc_to_swap: bitcoin::Amount, + _btc_starting_balance: bitcoin::Amount, + xmr_to_swap: u64, + xmr_starting_balance: u64, +) -> ( + AliceState, + alice::Swarm, + Arc, + Arc, + PeerId, + Multiaddr, +) { + monero + .init(vec![("alice", xmr_starting_balance)]) + .await + .unwrap(); + + let alice_xmr_wallet = Arc::new(swap::monero::Wallet( + monero.wallet("alice").unwrap().client(), + )); + + let alice_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) + .await + .unwrap(), + ); + + let amounts = SwapAmounts { + btc: btc_to_swap, + xmr: xmr_btc::monero::Amount::from_piconero(xmr_to_swap), + }; + + let alice_behaviour = alice::Behaviour::default(); + let alice_peer_id = alice_behaviour.peer_id(); + let alice_transport = build(alice_behaviour.identity()).unwrap(); + let rng = &mut OsRng; + 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_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" + .parse() + .expect("failed to parse Alice's address"); + + let alice_swarm = + alice::new_swarm(alice_multiaddr.clone(), alice_transport, alice_behaviour).unwrap(); + + ( + alice_state, + alice_swarm, + alice_btc_wallet, + alice_xmr_wallet, + alice_peer_id, + alice_multiaddr, + ) +} + +#[allow(clippy::too_many_arguments)] +async fn init_bob( + alice_multiaddr: Multiaddr, + alice_peer_id: PeerId, + bitcoind: &Bitcoind<'_>, + monero: &Monero, + btc_to_swap: bitcoin::Amount, + btc_starting_balance: bitcoin::Amount, + xmr_to_swap: u64, + xmr_stating_balance: u64, +) -> ( + BobState, + bob::Swarm, + Arc, + Arc, + Database, +) { + let bob_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) + .await + .unwrap(), + ); + bitcoind + .mint( + bob_btc_wallet.0.new_address().await.unwrap(), + btc_starting_balance, + ) + .await + .unwrap(); + + monero + .init(vec![("bob", xmr_stating_balance)]) + .await + .unwrap(); + + let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); + + let amounts = SwapAmounts { + btc: btc_to_swap, + xmr: xmr_btc::monero::Amount::from_piconero(xmr_to_swap), + }; + + let rng = &mut OsRng; + + let bob_db_dir = tempdir().unwrap(); + let bob_db = Database::open(bob_db_dir.path()).unwrap(); + let bob_behaviour = bob::Behaviour::default(); + let bob_transport = build(bob_behaviour.identity()).unwrap(); + + let refund_address = bob_btc_wallet.new_address().await.unwrap(); + let state0 = xmr_btc::bob::State0::new( + rng, + btc_to_swap, + xmr_btc::monero::Amount::from_piconero(xmr_to_swap), + REFUND_TIMELOCK, + PUNISH_TIMELOCK, + refund_address, + ); + let bob_state = BobState::Started { + state0, + amounts, + peer_id: alice_peer_id, + addr: alice_multiaddr, + }; + let bob_swarm = bob::new_swarm(bob_transport, bob_behaviour).unwrap(); + + (bob_state, bob_swarm, bob_btc_wallet, bob_xmr_wallet, bob_db) +} + +fn init_tracing() { + use tracing_subscriber::util::SubscriberInitExt as _; + let _guard = tracing_subscriber::fmt() + .with_env_filter("swap=info,xmr_btc=info") + .with_ansi(false) + .set_default(); +} diff --git a/xmr-btc/src/bob.rs b/xmr-btc/src/bob.rs index 3d0c15ae..8f52b788 100644 --- a/xmr-btc/src/bob.rs +++ b/xmr-btc/src/bob.rs @@ -799,6 +799,9 @@ impl State4 { t1_timeout.await; Ok(()) } + pub fn tx_lock_id(&self) -> bitcoin::Txid { + self.tx_lock.txid() + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/xmr-btc/src/config.rs b/xmr-btc/src/config.rs index cc0a7d5a..48594091 100644 --- a/xmr-btc/src/config.rs +++ b/xmr-btc/src/config.rs @@ -6,6 +6,7 @@ pub struct Config { pub bob_time_to_act: Duration, pub bitcoin_finality_confirmations: u32, pub bitcoin_avg_block_time: Duration, + pub monero_max_finality_time: Duration, } impl Config { @@ -14,6 +15,7 @@ impl Config { bob_time_to_act: *mainnet::BOB_TIME_TO_ACT, bitcoin_finality_confirmations: mainnet::BITCOIN_FINALITY_CONFIRMATIONS, bitcoin_avg_block_time: *mainnet::BITCOIN_AVG_BLOCK_TIME, + monero_max_finality_time: *mainnet::MONERO_MAX_FINALITY_TIME, } } @@ -22,6 +24,7 @@ impl Config { bob_time_to_act: *regtest::BOB_TIME_TO_ACT, bitcoin_finality_confirmations: regtest::BITCOIN_FINALITY_CONFIRMATIONS, bitcoin_avg_block_time: *regtest::BITCOIN_AVG_BLOCK_TIME, + monero_max_finality_time: *regtest::MONERO_MAX_FINALITY_TIME, } } } @@ -35,15 +38,21 @@ mod mainnet { pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 3; pub static BITCOIN_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(10 * 60)); + + pub static MONERO_MAX_FINALITY_TIME: Lazy = + Lazy::new(|| Duration::from_secs_f64(15f64 * 1.5 * 2f64 * 60f64)); } mod regtest { use super::*; // In test, set to 5 seconds to fail fast - pub static BOB_TIME_TO_ACT: Lazy = Lazy::new(|| Duration::from_secs(10)); + pub static BOB_TIME_TO_ACT: Lazy = Lazy::new(|| Duration::from_secs(30)); pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 1; pub static BITCOIN_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(5)); + + pub static MONERO_MAX_FINALITY_TIME: Lazy = + Lazy::new(|| Duration::from_secs_f64(60f64)); } From e4eed7b1a58db542c43c68ef4c6f47d130944926 Mon Sep 17 00:00:00 2001 From: rishflab Date: Sat, 5 Dec 2020 14:06:08 +1100 Subject: [PATCH 04/11] Change multiaddr for test runs --- swap/tests/e2e.rs | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index f19143fc..1c94acb0 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -36,14 +36,20 @@ async fn happy_path() { let xmr_alice = xmr * 10; let xmr_bob = 0; - let ( - alice_state, - alice_swarm, - alice_btc_wallet, - alice_xmr_wallet, - alice_peer_id, - alice_multiaddr, - ) = init_alice(&bitcoind, &monero, btc, btc_alice, xmr, xmr_alice).await; + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" + .parse() + .expect("failed to parse Alice's address"); + + let (alice_state, alice_swarm, alice_btc_wallet, alice_xmr_wallet, alice_peer_id) = init_alice( + &bitcoind, + &monero, + btc, + btc_alice, + xmr, + xmr_alice, + alice_multiaddr.clone(), + ) + .await; let (bob_state, bob_swarm, bob_btc_wallet, bob_xmr_wallet, bob_db) = init_bob( alice_multiaddr, @@ -117,20 +123,18 @@ 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; - let ( - alice_state, - alice_swarm, - alice_btc_wallet, - alice_xmr_wallet, - alice_peer_id, - alice_multiaddr, - ) = init_alice( + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877" + .parse() + .expect("failed to parse Alice's address"); + + let (alice_state, alice_swarm, 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; @@ -195,13 +199,13 @@ async fn init_alice( _btc_starting_balance: bitcoin::Amount, xmr_to_swap: u64, xmr_starting_balance: u64, + alice_multiaddr: Multiaddr, ) -> ( AliceState, alice::Swarm, Arc, Arc, PeerId, - Multiaddr, ) { monero .init(vec![("alice", xmr_starting_balance)]) @@ -239,12 +243,7 @@ async fn init_alice( } }; - let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" - .parse() - .expect("failed to parse Alice's address"); - - let alice_swarm = - alice::new_swarm(alice_multiaddr.clone(), alice_transport, alice_behaviour).unwrap(); + let alice_swarm = alice::new_swarm(alice_multiaddr, alice_transport, alice_behaviour).unwrap(); ( alice_state, @@ -252,7 +251,6 @@ async fn init_alice( alice_btc_wallet, alice_xmr_wallet, alice_peer_id, - alice_multiaddr, ) } From b4ac69fa9c0cdc3b36d77ddf9ea1e0ed2fc9545a Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 7 Dec 2020 09:26:22 +1100 Subject: [PATCH 05/11] Reduce stack size to prevent CI resource issues --- .github/workflows/ci.yml | 2 +- swap/tests/e2e.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0602667f..e73e99a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: run: cargo test --workspace --all-features env: MONERO_ADDITIONAL_SLEEP_PERIOD: 60000 - RUST_MIN_STACK: 100000000000 + RUST_MIN_STACK: 10000000 - name: Build binary run: | diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 1c94acb0..d7cdc950 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -13,7 +13,7 @@ use testcontainers::clients::Cli; use uuid::Uuid; use xmr_btc::{bitcoin, config::Config, cross_curve_dleq}; -/// Run the following tests with RUST_MIN_STACK=100000000000 +/// Run the following tests with RUST_MIN_STACK=10000000 #[tokio::test] async fn happy_path() { From 282997c105bb302b69dca492a60032cf9d265d42 Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 7 Dec 2020 13:43:23 +1100 Subject: [PATCH 06/11] Make monero finality calculation clearer --- xmr-btc/src/config.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/xmr-btc/src/config.rs b/xmr-btc/src/config.rs index 48594091..dc5745b9 100644 --- a/xmr-btc/src/config.rs +++ b/xmr-btc/src/config.rs @@ -15,7 +15,8 @@ impl Config { bob_time_to_act: *mainnet::BOB_TIME_TO_ACT, bitcoin_finality_confirmations: mainnet::BITCOIN_FINALITY_CONFIRMATIONS, bitcoin_avg_block_time: *mainnet::BITCOIN_AVG_BLOCK_TIME, - monero_max_finality_time: *mainnet::MONERO_MAX_FINALITY_TIME, + monero_max_finality_time: *mainnet::MONERO_AVG_BLOCK_TIME + * mainnet::MONERO_FINALITY_CONFIRMATIONS, } } @@ -24,7 +25,8 @@ impl Config { bob_time_to_act: *regtest::BOB_TIME_TO_ACT, bitcoin_finality_confirmations: regtest::BITCOIN_FINALITY_CONFIRMATIONS, bitcoin_avg_block_time: *regtest::BITCOIN_AVG_BLOCK_TIME, - monero_max_finality_time: *regtest::MONERO_MAX_FINALITY_TIME, + monero_max_finality_time: *regtest::MONERO_AVG_BLOCK_TIME + * regtest::MONERO_FINALITY_CONFIRMATIONS, } } } @@ -39,20 +41,22 @@ mod mainnet { pub static BITCOIN_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(10 * 60)); - pub static MONERO_MAX_FINALITY_TIME: Lazy = - Lazy::new(|| Duration::from_secs_f64(15f64 * 1.5 * 2f64 * 60f64)); + pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 10; + + pub static MONERO_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(2 * 60)); } mod regtest { use super::*; - // In test, set to 5 seconds to fail fast + // In test, we set a shorter time to fail fast pub static BOB_TIME_TO_ACT: Lazy = Lazy::new(|| Duration::from_secs(30)); pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 1; pub static BITCOIN_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(5)); - pub static MONERO_MAX_FINALITY_TIME: Lazy = - Lazy::new(|| Duration::from_secs_f64(60f64)); + pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 1; + + pub static MONERO_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(5)); } From 8592ef5f791e2e4c05708d3ac2c5b5f7257e3caf Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 7 Dec 2020 13:55:13 +1100 Subject: [PATCH 07/11] Address PR feedback Add context to logging statements. Use amount type for monero in tests. Remove stale code and comments. --- swap/src/alice/swap.rs | 3 +- swap/src/bob/swap.rs | 62 +----------------------------------------- swap/tests/e2e.rs | 54 ++++++++++++++++++------------------ xmr-btc/src/monero.rs | 10 ++++++- 4 files changed, 37 insertions(+), 92 deletions(-) diff --git a/swap/src/alice/swap.rs b/swap/src/alice/swap.rs index b96117ba..8cac8e6b 100644 --- a/swap/src/alice/swap.rs +++ b/swap/src/alice/swap.rs @@ -125,7 +125,6 @@ pub async fn swap( .await } -// TODO: use macro or generics pub fn is_complete(state: &AliceState) -> bool { matches!( state, @@ -153,7 +152,7 @@ pub async fn run_until( monero_wallet: Arc, config: Config, ) -> Result<(AliceState, Swarm)> { - info!("{}", state); + info!("Current state:{}", state); if is_target_state(&state) { Ok((state, swarm)) } else { diff --git a/swap/src/bob/swap.rs b/swap/src/bob/swap.rs index 443a86af..38e2a7f7 100644 --- a/swap/src/bob/swap.rs +++ b/swap/src/bob/swap.rs @@ -76,7 +76,6 @@ where .await } -// TODO: use macro or generics pub fn is_complete(state: &BobState) -> bool { matches!( state, @@ -111,7 +110,7 @@ pub async fn run_until( where R: RngCore + CryptoRng + Send, { - info!("{}", state); + info!("Current state: {}", state); if is_target_state(&state) { Ok(state) } else { @@ -303,62 +302,3 @@ where } } } - -// // State machine driver for recovery execution -// #[async_recursion] -// pub async fn abort(state: BobState, io: Io) -> Result { -// match state { -// BobState::Started => { -// // Nothing has been commited by either party, abort swap. -// abort(BobState::SafelyAborted, io).await -// } -// BobState::Negotiated => { -// // Nothing has been commited by either party, abort swap. -// abort(BobState::SafelyAborted, io).await -// } -// BobState::BtcLocked => { -// // Bob has locked BTC and must refund it -// // Bob waits for alice to publish TxRedeem or t1 -// if unimplemented!("TxRedeemSeen") { -// // Alice has redeemed revealing s_a -// abort(BobState::BtcRedeemed, io).await -// } else if unimplemented!("T1Elapsed") { -// // publish TxCancel or see if it has been published -// abort(BobState::Cancelled, io).await -// } else { -// Err(unimplemented!()) -// } -// } -// BobState::XmrLocked => { -// // Alice has locked Xmr -// // Wait until t1 -// if unimplemented!(">t1 and t2 -// // submit TxCancel -// abort(BobState::Punished, io).await -// } -// } -// BobState::Cancelled => { -// // Bob has cancelled the swap -// // If { -// // Bob uses revealed s_a to redeem XMR -// abort(BobState::XmrRedeemed, io).await -// } -// BobState::BtcRefunded => Ok(BobState::BtcRefunded), -// 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 d7cdc950..d60ceb80 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -26,15 +26,15 @@ async fn happy_path() { .await .unwrap(); - let btc = bitcoin::Amount::from_sat(1_000_000); + let btc_to_swap = bitcoin::Amount::from_sat(1_000_000); let btc_alice = bitcoin::Amount::ZERO; - let btc_bob = btc * 10; + let btc_bob = btc_to_swap * 10; // this xmr value matches the logic of alice::calculate_amounts i.e. btc * // 10_000 * 100 - let xmr = 1_000_000_000_000; - let xmr_alice = xmr * 10; - let xmr_bob = 0; + let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000); + let xmr_alice = xmr_to_swap * xmr_btc::monero::Amount::from_piconero(10); + let xmr_bob = xmr_btc::monero::Amount::from_piconero(0); let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" .parse() @@ -43,9 +43,9 @@ async fn happy_path() { let (alice_state, alice_swarm, alice_btc_wallet, alice_xmr_wallet, alice_peer_id) = init_alice( &bitcoind, &monero, - btc, + btc_to_swap, btc_alice, - xmr, + xmr_to_swap, xmr_alice, alice_multiaddr.clone(), ) @@ -56,9 +56,9 @@ async fn happy_path() { alice_peer_id, &bitcoind, &monero, - btc, + btc_to_swap, btc_bob, - xmr, + xmr_to_swap, xmr_bob, ) .await; @@ -93,12 +93,12 @@ async fn happy_path() { assert_eq!( btc_alice_final, - btc_alice + btc - bitcoin::Amount::from_sat(bitcoin::TX_FEE) + btc_alice + btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE) ); - assert!(btc_bob_final <= btc_bob - btc); + assert!(btc_bob_final <= btc_bob - btc_to_swap); - assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr); - assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr); + assert!(xmr_alice_final <= xmr_alice - xmr_to_swap); + assert_eq!(xmr_bob_final, xmr_bob + xmr_to_swap); } /// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice @@ -115,13 +115,13 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { .unwrap(); let btc_to_swap = bitcoin::Amount::from_sat(1_000_000); - let xmr_to_swap = 1_000_000_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 = 0; + 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_xmr_starting_balance = xmr_to_swap * xmr_btc::monero::Amount::from_piconero(10); let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877" .parse() @@ -197,8 +197,8 @@ async fn init_alice( monero: &Monero, btc_to_swap: bitcoin::Amount, _btc_starting_balance: bitcoin::Amount, - xmr_to_swap: u64, - xmr_starting_balance: u64, + xmr_to_swap: xmr_btc::monero::Amount, + xmr_starting_balance: xmr_btc::monero::Amount, alice_multiaddr: Multiaddr, ) -> ( AliceState, @@ -208,7 +208,7 @@ async fn init_alice( PeerId, ) { monero - .init(vec![("alice", xmr_starting_balance)]) + .init(vec![("alice", xmr_starting_balance.as_piconero())]) .await .unwrap(); @@ -224,7 +224,7 @@ async fn init_alice( let amounts = SwapAmounts { btc: btc_to_swap, - xmr: xmr_btc::monero::Amount::from_piconero(xmr_to_swap), + xmr: xmr_to_swap, }; let alice_behaviour = alice::Behaviour::default(); @@ -262,8 +262,8 @@ async fn init_bob( monero: &Monero, btc_to_swap: bitcoin::Amount, btc_starting_balance: bitcoin::Amount, - xmr_to_swap: u64, - xmr_stating_balance: u64, + xmr_to_swap: xmr_btc::monero::Amount, + xmr_stating_balance: xmr_btc::monero::Amount, ) -> ( BobState, bob::Swarm, @@ -285,7 +285,7 @@ async fn init_bob( .unwrap(); monero - .init(vec![("bob", xmr_stating_balance)]) + .init(vec![("bob", xmr_stating_balance.as_piconero())]) .await .unwrap(); @@ -293,11 +293,9 @@ async fn init_bob( let amounts = SwapAmounts { btc: btc_to_swap, - xmr: xmr_btc::monero::Amount::from_piconero(xmr_to_swap), + xmr: xmr_to_swap, }; - let rng = &mut OsRng; - let bob_db_dir = tempdir().unwrap(); let bob_db = Database::open(bob_db_dir.path()).unwrap(); let bob_behaviour = bob::Behaviour::default(); @@ -305,9 +303,9 @@ async fn init_bob( let refund_address = bob_btc_wallet.new_address().await.unwrap(); let state0 = xmr_btc::bob::State0::new( - rng, + &mut OsRng, btc_to_swap, - xmr_btc::monero::Amount::from_piconero(xmr_to_swap), + xmr_to_swap, REFUND_TIMELOCK, PUNISH_TIMELOCK, refund_address, diff --git a/xmr-btc/src/monero.rs b/xmr-btc/src/monero.rs index 643c4d32..7196afc4 100644 --- a/xmr-btc/src/monero.rs +++ b/xmr-btc/src/monero.rs @@ -3,7 +3,7 @@ use anyhow::Result; use async_trait::async_trait; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use std::ops::{Add, Sub}; +use std::ops::{Add, Mul, Sub}; pub use curve25519_dalek::scalar::Scalar; pub use monero::*; @@ -97,6 +97,14 @@ impl Sub for Amount { } } +impl Mul for Amount { + type Output = Amount; + + fn mul(self, rhs: Self) -> Self::Output { + Self(self.0 * rhs.0) + } +} + impl From for u64 { fn from(from: Amount) -> u64 { from.0 From 627b1a9f009e1b4c66df1ba2dbb8e44676349e11 Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 7 Dec 2020 14:03:50 +1100 Subject: [PATCH 08/11] Remove unnecessary execution restart --- swap/tests/e2e.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index d60ceb80..e8260f95 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -161,32 +161,18 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { Uuid::new_v4(), ); - let alice_xmr_locked_fut = alice::swap::run_until( + let alice_fut = alice::swap::swap( alice_state, - alice::swap::is_xmr_locked, alice_swarm, alice_btc_wallet.clone(), alice_xmr_wallet.clone(), Config::regtest(), ); - // Wait until alice has locked xmr and bob has locked btc - let ((alice_state, alice_swarm), _bob_state) = - try_join(alice_xmr_locked_fut, bob_xmr_locked_fut) - .await - .unwrap(); + // 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(); - let (punished, _) = alice::swap::swap( - alice_state, - alice_swarm, - alice_btc_wallet.clone(), - alice_xmr_wallet.clone(), - Config::regtest(), - ) - .await - .unwrap(); - - assert!(matches!(punished, AliceState::Punished)); + assert!(matches!(alice_state, AliceState::Punished)); // todo: Add balance assertions } From 883a913f30b045a68a00b5bb46789c8bc7a393df Mon Sep 17 00:00:00 2001 From: rishflab Date: Tue, 8 Dec 2020 16:12:00 +1100 Subject: [PATCH 09/11] Remove nonsensical monero * monero multiplication --- swap/tests/e2e.rs | 4 ++-- xmr-btc/src/monero.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index e8260f95..352aa8f4 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -33,7 +33,7 @@ async fn happy_path() { // this xmr value matches the logic of alice::calculate_amounts i.e. btc * // 10_000 * 100 let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000); - let xmr_alice = xmr_to_swap * xmr_btc::monero::Amount::from_piconero(10); + let xmr_alice = xmr_to_swap * 10; let xmr_bob = xmr_btc::monero::Amount::from_piconero(0); let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" @@ -121,7 +121,7 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { 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 * xmr_btc::monero::Amount::from_piconero(10); + let alice_xmr_starting_balance = xmr_to_swap * 10; let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877" .parse() diff --git a/xmr-btc/src/monero.rs b/xmr-btc/src/monero.rs index 7196afc4..e4215853 100644 --- a/xmr-btc/src/monero.rs +++ b/xmr-btc/src/monero.rs @@ -97,11 +97,11 @@ impl Sub for Amount { } } -impl Mul for Amount { +impl Mul for Amount { type Output = Amount; - fn mul(self, rhs: Self) -> Self::Output { - Self(self.0 * rhs.0) + fn mul(self, rhs: u64) -> Self::Output { + Self(self.0 * rhs) } } From 1a2857af2901ea63a9eafccb49c00f46c01e32da Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 9 Dec 2020 11:48:53 +1100 Subject: [PATCH 10/11] Properly init tracing per test and reverse the filter to be exclusive Tracing should be initialized by test and the `_guard` kept alive within the test. Re-using this code in different tests does not really have any additional value. Instead of specifying what messages we want to include, I went for a filter that excludes noise. That way we get more useful logging. --- swap/tests/e2e.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index 352aa8f4..a7f8fd17 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -12,12 +12,16 @@ use tempfile::tempdir; use testcontainers::clients::Cli; use uuid::Uuid; use xmr_btc::{bitcoin, config::Config, cross_curve_dleq}; +use tracing_subscriber::util::SubscriberInitExt as _; /// Run the following tests with RUST_MIN_STACK=10000000 #[tokio::test] async fn happy_path() { - init_tracing(); + let _guard = tracing_subscriber::fmt() + .with_env_filter("trace,hyper=warn") + .set_default(); + let cli = Cli::default(); let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); let _ = bitcoind.init(5).await; @@ -105,7 +109,11 @@ async fn happy_path() { /// the encsig and fail to refund or redeem. Alice punishes. #[tokio::test] async fn alice_punishes_if_bob_never_acts_after_fund() { - init_tracing(); + + let _guard = tracing_subscriber::fmt() + .with_env_filter("trace,hyper=warn") + .set_default(); + let cli = Cli::default(); let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); let _ = bitcoind.init(5).await; @@ -306,11 +314,3 @@ async fn init_bob( (bob_state, bob_swarm, bob_btc_wallet, bob_xmr_wallet, bob_db) } - -fn init_tracing() { - use tracing_subscriber::util::SubscriberInitExt as _; - let _guard = tracing_subscriber::fmt() - .with_env_filter("swap=info,xmr_btc=info") - .with_ansi(false) - .set_default(); -} From bd8e6e36e0b095444ad048b173b41635ecd34612 Mon Sep 17 00:00:00 2001 From: rishflab Date: Wed, 9 Dec 2020 14:45:09 +1100 Subject: [PATCH 11/11] Fix monero blocktime config --- swap/tests/e2e.rs | 3 +-- xmr-btc/src/config.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index a7f8fd17..237543b2 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -10,9 +10,9 @@ use swap::{ }; use tempfile::tempdir; use testcontainers::clients::Cli; +use tracing_subscriber::util::SubscriberInitExt as _; use uuid::Uuid; use xmr_btc::{bitcoin, config::Config, cross_curve_dleq}; -use tracing_subscriber::util::SubscriberInitExt as _; /// Run the following tests with RUST_MIN_STACK=10000000 @@ -109,7 +109,6 @@ async fn happy_path() { /// the encsig and fail to refund or redeem. Alice punishes. #[tokio::test] async fn alice_punishes_if_bob_never_acts_after_fund() { - let _guard = tracing_subscriber::fmt() .with_env_filter("trace,hyper=warn") .set_default(); diff --git a/xmr-btc/src/config.rs b/xmr-btc/src/config.rs index dc5745b9..3fce9f04 100644 --- a/xmr-btc/src/config.rs +++ b/xmr-btc/src/config.rs @@ -15,7 +15,9 @@ impl Config { bob_time_to_act: *mainnet::BOB_TIME_TO_ACT, bitcoin_finality_confirmations: mainnet::BITCOIN_FINALITY_CONFIRMATIONS, bitcoin_avg_block_time: *mainnet::BITCOIN_AVG_BLOCK_TIME, - monero_max_finality_time: *mainnet::MONERO_AVG_BLOCK_TIME + // We apply a scaling factor (1.5) so that the swap is not aborted when the + // blockchain is slow + monero_max_finality_time: (*mainnet::MONERO_AVG_BLOCK_TIME).mul_f64(1.5) * mainnet::MONERO_FINALITY_CONFIRMATIONS, } } @@ -25,7 +27,9 @@ impl Config { bob_time_to_act: *regtest::BOB_TIME_TO_ACT, bitcoin_finality_confirmations: regtest::BITCOIN_FINALITY_CONFIRMATIONS, bitcoin_avg_block_time: *regtest::BITCOIN_AVG_BLOCK_TIME, - monero_max_finality_time: *regtest::MONERO_AVG_BLOCK_TIME + // We apply a scaling factor (1.5) so that the swap is not aborted when the + // blockchain is slow + monero_max_finality_time: (*regtest::MONERO_AVG_BLOCK_TIME).mul_f64(1.5) * regtest::MONERO_FINALITY_CONFIRMATIONS, } } @@ -41,7 +45,7 @@ mod mainnet { pub static BITCOIN_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(10 * 60)); - pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 10; + pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 15; pub static MONERO_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(2 * 60)); } @@ -58,5 +62,5 @@ mod regtest { pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 1; - pub static MONERO_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(5)); + pub static MONERO_AVG_BLOCK_TIME: Lazy = Lazy::new(|| Duration::from_secs(60)); }