From e4795fa4eee39e5ddaab894dea77852a6fb03a8a Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 18 Jan 2021 18:08:13 +1100 Subject: [PATCH 01/15] Fix recursive call to swap by using run_until We should call run_until instead of swap. --- swap/src/protocol/alice/swap.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index a322c995..3aec20fb 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -360,8 +360,10 @@ pub async fn run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, Swap::Alice(db_state)) .await?; - swap( + + run_until( state, + is_target_state, event_loop_handle, bitcoin_wallet.clone(), monero_wallet, From 8bf467b5504b3019810370005cd8e3693230429e Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 18 Jan 2021 19:56:43 +1100 Subject: [PATCH 02/15] Make the factory code usable in production - Introduce Test abstraction instead of tow harnesses, move test specific data into Test - Change the abstraction from actors to swap, because we are creating swaps, not actors - rename actor::swap to run, because we are running a swap --- swap/src/main.rs | 25 +- swap/src/protocol/alice.rs | 16 +- swap/src/protocol/alice/swap.rs | 114 ++-- swap/src/protocol/bob.rs | 16 +- swap/src/protocol/bob/swap.rs | 68 +-- swap/tests/happy_path.rs | 33 +- swap/tests/happy_path_restart_alice.rs | 66 +- .../happy_path_restart_bob_after_comm.rs | 66 +- .../happy_path_restart_bob_before_comm.rs | 61 +- swap/tests/punish.rs | 67 +- swap/tests/refund_restart_alice.rs | 56 +- swap/tests/testutils/mod.rs | 574 +++++++++++------- 12 files changed, 583 insertions(+), 579 deletions(-) diff --git a/swap/src/main.rs b/swap/src/main.rs index c924f54a..8e692839 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -300,15 +300,17 @@ async fn alice_swap( let (mut event_loop, handle) = alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?; - let swap = alice::swap::swap( + let swap = alice::Swap { state, - handle, - bitcoin_wallet.clone(), - monero_wallet.clone(), + event_loop_handle: handle, + bitcoin_wallet, + monero_wallet, config, swap_id, db, - ); + }; + + let swap = alice::swap::run(swap); tokio::spawn(async move { event_loop.run().await }); swap.await @@ -331,15 +333,16 @@ async fn bob_swap( let (event_loop, handle) = bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)?; - let swap = bob::swap::swap( + let swap = bob::Swap { state, - handle, + event_loop_handle: handle, db, - bitcoin_wallet.clone(), - monero_wallet.clone(), - OsRng, + bitcoin_wallet, + monero_wallet, swap_id, - ); + }; + + let swap = bob::swap::run(swap); tokio::spawn(event_loop.run()); swap.await diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 6ce5fd95..65419783 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -9,6 +9,7 @@ use libp2p::{ use tracing::{debug, info}; use crate::{ + bitcoin, monero, network::{ peer_tracker::{self, PeerTracker}, request_response::AliceToBob, @@ -26,8 +27,11 @@ pub use self::{ message1::Message1, message2::Message2, state::*, - swap::{run_until, swap}, + swap::{run, run_until}, }; +use crate::{config::Config, database::Database}; +use std::sync::Arc; +use uuid::Uuid; mod amounts; pub mod event_loop; @@ -39,6 +43,16 @@ pub mod state; mod steps; pub mod swap; +pub struct Swap { + pub state: AliceState, + pub event_loop_handle: EventLoopHandle, + pub bitcoin_wallet: Arc, + pub monero_wallet: Arc, + pub config: Config, + pub swap_id: Uuid, + pub db: Database, +} + pub type Swarm = libp2p::Swarm; pub fn new_swarm( diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 3aec20fb..f6dcb3e0 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -15,18 +15,23 @@ use crate::{ bitcoin, bitcoin::{TransactionBlockHeight, WatchForRawTransaction}, config::Config, - database::{Database, Swap}, + database, + database::Database, monero, monero::CreateWalletForOutput, - protocol::alice::{ - event_loop::EventLoopHandle, - steps::{ - build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction, - extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction, - publish_bitcoin_redeem_transaction, publish_cancel_transaction, - wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin, + protocol::{ + alice, + alice::{ + event_loop::EventLoopHandle, + steps::{ + build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction, + extract_monero_private_key, lock_xmr, negotiate, + publish_bitcoin_punish_transaction, publish_bitcoin_redeem_transaction, + publish_cancel_transaction, wait_for_bitcoin_encrypted_signature, + wait_for_bitcoin_refund, wait_for_locked_bitcoin, + }, + AliceState, }, - AliceState, }, ExpiredTimelocks, }; @@ -35,28 +40,6 @@ trait Rng: RngCore + CryptoRng + Send {} impl Rng for T where T: RngCore + CryptoRng + Send {} -pub async fn swap( - state: AliceState, - event_loop_handle: EventLoopHandle, - bitcoin_wallet: Arc, - monero_wallet: Arc, - config: Config, - swap_id: Uuid, - db: Database, -) -> Result { - run_until( - state, - is_complete, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - config, - swap_id, - db, - ) - .await -} - pub fn is_complete(state: &AliceState) -> bool { matches!( state, @@ -81,10 +64,31 @@ pub fn is_encsig_learned(state: &AliceState) -> bool { ) } +pub async fn run(swap: alice::Swap) -> Result { + run_until(swap, is_complete).await +} + +pub async fn run_until( + swap: alice::Swap, + is_target_state: fn(&AliceState) -> bool, +) -> Result { + do_run_until( + swap.state, + is_target_state, + swap.event_loop_handle, + swap.bitcoin_wallet, + swap.monero_wallet, + swap.config, + swap.swap_id, + swap.db, + ) + .await +} + // State machine driver for swap execution #[async_recursion] #[allow(clippy::too_many_arguments)] -pub async fn run_until( +async fn do_run_until( state: AliceState, is_target_state: fn(&AliceState) -> bool, mut event_loop_handle: EventLoopHandle, @@ -110,9 +114,9 @@ pub async fn run_until( }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -153,9 +157,9 @@ pub async fn run_until( }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -194,9 +198,9 @@ pub async fn run_until( }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -232,9 +236,9 @@ pub async fn run_until( }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -270,9 +274,9 @@ pub async fn run_until( let state = AliceState::CancelTimelockExpired { state3 }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - return run_until( + return do_run_until( state, is_target_state, event_loop_handle, @@ -298,9 +302,9 @@ pub async fn run_until( let state = AliceState::BtcRedeemed; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -325,9 +329,9 @@ pub async fn run_until( let state = AliceState::BtcCancelled { state3, tx_cancel }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -358,10 +362,10 @@ pub async fn run_until( None => { let state = AliceState::BtcPunishable { tx_refund, state3 }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -384,9 +388,9 @@ pub async fn run_until( let state = AliceState::BtcRefunded { spend_key, state3 }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -409,7 +413,7 @@ pub async fn run_until( let state = AliceState::XmrRefunded; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; Ok(state) } @@ -439,9 +443,9 @@ pub async fn run_until( Either::Left(_) => { let state = AliceState::BtcPunished; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -463,9 +467,9 @@ pub async fn run_until( )?; let state = AliceState::BtcRefunded { spend_key, state3 }; let db_state = (&state).into(); - db.insert_latest_state(swap_id, Swap::Alice(db_state)) + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index ab9bbfc6..5558d616 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -8,7 +8,9 @@ use libp2p::{ use tracing::{debug, info}; use crate::{ + bitcoin, bitcoin::EncryptedSignature, + monero, network::{ peer_tracker::{self, PeerTracker}, transport::SwapTransport, @@ -26,8 +28,11 @@ pub use self::{ message2::Message2, message3::Message3, state::*, - swap::{run_until, swap}, + swap::{run, run_until}, }; +use crate::database::Database; +use std::sync::Arc; +use uuid::Uuid; mod amounts; pub mod event_loop; @@ -38,6 +43,15 @@ mod message3; pub mod state; pub mod swap; +pub struct Swap { + pub state: BobState, + pub event_loop_handle: bob::EventLoopHandle, + pub db: Database, + pub bitcoin_wallet: Arc, + pub monero_wallet: Arc, + pub swap_id: Uuid, +} + pub type Swarm = libp2p::Swarm; pub fn new_swarm(transport: SwapTransport, behaviour: Behaviour) -> Result { diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 9046e5a6..b01a129b 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -13,33 +13,7 @@ use crate::{ protocol::bob::{self, event_loop::EventLoopHandle, state::*}, ExpiredTimelocks, SwapAmounts, }; - -// TODO(Franck): Make this a method on a struct -#[allow(clippy::too_many_arguments)] -pub async fn swap( - state: BobState, - event_loop_handle: EventLoopHandle, - db: Database, - bitcoin_wallet: Arc, - monero_wallet: Arc, - rng: R, - swap_id: Uuid, -) -> Result -where - R: RngCore + CryptoRng + Send, -{ - run_until( - state, - is_complete, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - rng, - swap_id, - ) - .await -} +use ecdsa_fun::fun::rand_core::OsRng; pub fn is_complete(state: &BobState) -> bool { matches!( @@ -63,10 +37,32 @@ pub fn is_encsig_sent(state: &BobState) -> bool { matches!(state, BobState::EncSigSent(..)) } +#[allow(clippy::too_many_arguments)] +pub async fn run(swap: bob::Swap) -> Result { + run_until(swap, is_complete).await +} + +pub async fn run_until( + swap: bob::Swap, + is_target_state: fn(&BobState) -> bool, +) -> Result { + do_run_until( + swap.state, + is_target_state, + swap.event_loop_handle, + swap.db, + swap.bitcoin_wallet, + swap.monero_wallet, + OsRng, + swap.swap_id, + ) + .await +} + // State machine driver for swap execution #[allow(clippy::too_many_arguments)] #[async_recursion] -pub async fn run_until( +async fn do_run_until( state: BobState, is_target_state: fn(&BobState) -> bool, mut event_loop_handle: EventLoopHandle, @@ -99,7 +95,7 @@ where let state = BobState::Negotiated(state2); let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -120,7 +116,7 @@ where let state = BobState::BtcLocked(state3); let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -185,7 +181,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -226,7 +222,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -261,7 +257,7 @@ where let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -282,7 +278,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -307,7 +303,7 @@ where db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) .await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, @@ -336,7 +332,7 @@ where let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + do_run_until( state, is_target_state, event_loop_handle, diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 18d31198..054a9d08 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -1,4 +1,3 @@ -use rand::rngs::OsRng; use swap::protocol::{alice, bob}; use tokio::join; @@ -8,33 +7,17 @@ pub mod testutils; #[tokio::test] async fn happy_path() { - testutils::test(|alice_harness, bob_harness| async move { - let alice = alice_harness.new_alice().await; - let bob = bob_harness.new_bob().await; + testutils::init(|test| async move { + let alice_swap = test.new_swap_as_alice().await; + let bob_swap = test.new_swap_as_bob().await; - let alice_swap = alice::swap( - alice.state, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ); + let alice = alice::run(alice_swap); - let bob_swap = bob::swap( - bob.state, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ); - let (alice_state, bob_state) = join!(alice_swap, bob_swap); + let bob = bob::run(bob_swap); + let (alice_state, bob_state) = join!(alice, bob); - alice_harness.assert_redeemed(alice_state.unwrap()).await; - bob_harness.assert_redeemed(bob_state.unwrap()).await; + test.assert_alice_redeemed(alice_state.unwrap()).await; + test.assert_bob_redeemed(bob_state.unwrap()).await; }) .await; } diff --git a/swap/tests/happy_path_restart_alice.rs b/swap/tests/happy_path_restart_alice.rs index 1e49fdf3..8f84129b 100644 --- a/swap/tests/happy_path_restart_alice.rs +++ b/swap/tests/happy_path_restart_alice.rs @@ -1,58 +1,30 @@ -use rand::rngs::OsRng; use swap::protocol::{alice, alice::AliceState, bob}; pub mod testutils; #[tokio::test] async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { - testutils::test(|alice_harness, bob_harness| async move { - let alice = alice_harness.new_alice().await; - let bob = bob_harness.new_bob().await; - - let bob_swap = bob::swap( - bob.state, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ); - let bob_swap_handle = tokio::spawn(bob_swap); - - let alice_state = alice::run_until( - alice.state, - alice::swap::is_encsig_learned, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ) - .await - .unwrap(); + testutils::init(|test| async move { + let alice_swap = test.new_swap_as_alice().await; + let bob_swap = test.new_swap_as_bob().await; + + let bob = bob::run(bob_swap); + let bob_handle = tokio::spawn(bob); + + let alice_state = alice::run_until(alice_swap, alice::swap::is_encsig_learned) + .await + .unwrap(); assert!(matches!(alice_state, AliceState::EncSigLearned {..})); - let alice = alice_harness.recover_alice_from_db().await; - assert!(matches!(alice.state, AliceState::EncSigLearned {..})); - - let alice_state = alice::swap( - alice.state, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ) - .await - .unwrap(); - - alice_harness.assert_redeemed(alice_state).await; - - let bob_state = bob_swap_handle.await.unwrap(); - bob_harness.assert_redeemed(bob_state.unwrap()).await + let alice_swap = test.recover_alice_from_db().await; + assert!(matches!(alice_swap.state, AliceState::EncSigLearned {..})); + + let alice_state = alice::run(alice_swap).await.unwrap(); + + test.assert_alice_redeemed(alice_state).await; + + let bob_state = bob_handle.await.unwrap(); + test.assert_bob_redeemed(bob_state.unwrap()).await }) .await; } diff --git a/swap/tests/happy_path_restart_bob_after_comm.rs b/swap/tests/happy_path_restart_bob_after_comm.rs index 7c1618b0..d49c939f 100644 --- a/swap/tests/happy_path_restart_bob_after_comm.rs +++ b/swap/tests/happy_path_restart_bob_after_comm.rs @@ -1,59 +1,31 @@ -use rand::rngs::OsRng; use swap::protocol::{alice, bob, bob::BobState}; pub mod testutils; #[tokio::test] async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { - testutils::test(|alice_harness, bob_harness| async move { - let alice = alice_harness.new_alice().await; - let bob = bob_harness.new_bob().await; - - let alice_swap = alice::swap( - alice.state, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ); - let alice_swap_handle = tokio::spawn(alice_swap); - - let bob_state = bob::run_until( - bob.state, - bob::swap::is_encsig_sent, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ) - .await - .unwrap(); + testutils::init(|test| async move { + let alice_swap = test.new_swap_as_alice().await; + let bob_swap = test.new_swap_as_bob().await; + + let alice = alice::run(alice_swap); + let alice_handle = tokio::spawn(alice); + + let bob_state = bob::run_until(bob_swap, bob::swap::is_encsig_sent) + .await + .unwrap(); assert!(matches!(bob_state, BobState::EncSigSent {..})); - let bob = bob_harness.recover_bob_from_db().await; - assert!(matches!(bob.state, BobState::EncSigSent {..})); - - let bob_state = bob::swap( - bob.state, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ) - .await - .unwrap(); - - bob_harness.assert_redeemed(bob_state).await; - - let alice_state = alice_swap_handle.await.unwrap(); - alice_harness.assert_redeemed(alice_state.unwrap()).await; + let bob_swap = test.recover_bob_from_db().await; + assert!(matches!(bob_swap.state, BobState::EncSigSent {..})); + + let bob_state = bob::run(bob_swap).await.unwrap(); + + test.assert_bob_redeemed(bob_state).await; + + let alice_state = alice_handle.await.unwrap(); + test.assert_alice_redeemed(alice_state.unwrap()).await; }) .await; } diff --git a/swap/tests/happy_path_restart_bob_before_comm.rs b/swap/tests/happy_path_restart_bob_before_comm.rs index c206e2fd..1fce0e5c 100644 --- a/swap/tests/happy_path_restart_bob_before_comm.rs +++ b/swap/tests/happy_path_restart_bob_before_comm.rs @@ -1,59 +1,32 @@ -use rand::rngs::OsRng; -use swap::protocol::{alice, bob, bob::BobState}; +use swap::protocol::{ + alice, bob, + bob::{swap::is_xmr_locked, BobState}, +}; pub mod testutils; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { - testutils::test(|alice_harness, bob_harness| async move { - let alice = alice_harness.new_alice().await; - let bob = bob_harness.new_bob().await; - - let alice_swap = alice::swap( - alice.state, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ); - let alice_swap_handle = tokio::spawn(alice_swap); - - let bob_state = bob::run_until( - bob.state, - bob::swap::is_xmr_locked, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ) - .await - .unwrap(); + testutils::init(|test| async move { + let alice_swap = test.new_swap_as_alice().await; + let bob_swap = test.new_swap_as_bob().await; + + let alice_handle = alice::run(alice_swap); + let alice_swap_handle = tokio::spawn(alice_handle); + + let bob_state = bob::run_until(bob_swap, is_xmr_locked).await.unwrap(); assert!(matches!(bob_state, BobState::XmrLocked {..})); - let bob = bob_harness.recover_bob_from_db().await; - assert!(matches!(bob.state, BobState::XmrLocked {..})); + let bob_swap = test.recover_bob_from_db().await; + assert!(matches!(bob_swap.state, BobState::XmrLocked {..})); - let bob_state = bob::swap( - bob.state, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ) - .await - .unwrap(); + let bob_state = bob::run(bob_swap).await.unwrap(); - bob_harness.assert_redeemed(bob_state).await; + test.assert_bob_redeemed(bob_state).await; let alice_state = alice_swap_handle.await.unwrap(); - alice_harness.assert_redeemed(alice_state.unwrap()).await; + test.assert_alice_redeemed(alice_state.unwrap()).await; }) .await; } diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs index e17de0ff..16515569 100644 --- a/swap/tests/punish.rs +++ b/swap/tests/punish.rs @@ -1,5 +1,7 @@ -use rand::rngs::OsRng; -use swap::protocol::{alice, bob, bob::BobState}; +use swap::protocol::{ + alice, bob, + bob::{swap::is_btc_locked, BobState}, +}; pub mod testutils; @@ -7,57 +9,28 @@ pub mod testutils; /// the encsig and fail to refund or redeem. Alice punishes. #[tokio::test] async fn alice_punishes_if_bob_never_acts_after_fund() { - testutils::test(|alice_harness, bob_harness| async move { - let alice = alice_harness.new_alice().await; - let bob = bob_harness.new_bob().await; - - let alice_swap = alice::swap( - alice.state, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ); - let alice_swap_handle = tokio::spawn(alice_swap); - - let bob_state = bob::run_until( - bob.state, - bob::swap::is_btc_locked, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ) - .await - .unwrap(); + testutils::init(|test| async move { + let alice_swap = test.new_swap_as_alice().await; + let bob_swap = test.new_swap_as_bob().await; + + let alice = alice::run(alice_swap); + let alice_handle = tokio::spawn(alice); + + let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap(); assert!(matches!(bob_state, BobState::BtcLocked {..})); - let alice_state = alice_swap_handle.await.unwrap(); - alice_harness.assert_punished(alice_state.unwrap()).await; + let alice_state = alice_handle.await.unwrap(); + test.assert_alice_punished(alice_state.unwrap()).await; // Restart Bob after Alice punished to ensure Bob transitions to // punished and does not run indefinitely - let bob = bob_harness.recover_bob_from_db().await; - assert!(matches!(bob.state, BobState::BtcLocked {..})); - - let bob_state = bob::swap( - bob.state, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ) - .await - .unwrap(); - - bob_harness.assert_punished(bob_state).await; + let bob_swap = test.recover_bob_from_db().await; + assert!(matches!(bob_swap.state, BobState::BtcLocked {..})); + + let bob_state = bob::run(bob_swap).await.unwrap(); + + test.assert_bob_punished(bob_state).await; }) .await; } diff --git a/swap/tests/refund_restart_alice.rs b/swap/tests/refund_restart_alice.rs index 2a1d338c..e5e91ae8 100644 --- a/swap/tests/refund_restart_alice.rs +++ b/swap/tests/refund_restart_alice.rs @@ -1,4 +1,3 @@ -use rand::rngs::OsRng; use swap::protocol::{alice, alice::AliceState, bob}; pub mod testutils; @@ -7,60 +6,33 @@ pub mod testutils; /// then also refunds. #[tokio::test] async fn given_alice_restarts_after_xmr_is_locked_abort_swap() { - testutils::test(|alice_harness, bob_harness| async move { - let alice = alice_harness.new_alice().await; - let bob = bob_harness.new_bob().await; + testutils::init(|test| async move { + let alice_swap = test.new_swap_as_alice().await; + let bob_swap = test.new_swap_as_bob().await; - let bob_swap = bob::swap( - bob.state, - bob.event_loop_handle, - bob.db, - bob.bitcoin_wallet.clone(), - bob.monero_wallet.clone(), - OsRng, - bob.swap_id, - ); - let bob_swap_handle = tokio::spawn(bob_swap); + let bob = bob::run(bob_swap); + let bob_handle = tokio::spawn(bob); - let alice_state = alice::run_until( - alice.state, - alice::swap::is_xmr_locked, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ) - .await - .unwrap(); + let alice_state = alice::run_until(alice_swap, alice::swap::is_xmr_locked) + .await + .unwrap(); assert!(matches!(alice_state, AliceState::XmrLocked {..})); // Alice does not act, Bob refunds - let bob_state = bob_swap_handle.await.unwrap(); + let bob_state = bob_handle.await.unwrap(); // Once bob has finished Alice is restarted and refunds as well - let alice = alice_harness.recover_alice_from_db().await; - assert!(matches!(alice.state, AliceState::XmrLocked {..})); + let alice_swap = test.recover_alice_from_db().await; + assert!(matches!(alice_swap.state, AliceState::XmrLocked {..})); - let alice_state = alice::swap( - alice.state, - alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), - alice.config, - alice.swap_id, - alice.db, - ) - .await - .unwrap(); + let alice_state = alice::run(alice_swap).await.unwrap(); // TODO: The test passes like this, but the assertion should be done after Bob // refunded, not at the end because this can cause side-effects! // We have to properly wait for the refund tx's finality inside the assertion, // which requires storing the refund_tx_id in the the state! - bob_harness.assert_refunded(bob_state.unwrap()).await; - alice_harness.assert_refunded(alice_state).await; + test.assert_bob_refunded(bob_state.unwrap()).await; + test.assert_alice_refunded(alice_state).await; }) .await; } diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 30f69b7b..ef935769 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -22,9 +22,285 @@ use tracing_core::dispatcher::DefaultGuard; use tracing_log::LogTracer; use uuid::Uuid; -pub async fn test(testfn: T) +pub struct Test { + swap_amounts: SwapAmounts, + + alice_swap_factory: AliceSwapFactory, + bob_swap_factory: BobSwapFactory, +} + +impl Test { + pub async fn new_swap_as_alice(&self) -> alice::Swap { + let (swap, mut event_loop) = self + .alice_swap_factory + .new_swap_as_alice(self.swap_amounts) + .await; + + tokio::spawn(async move { event_loop.run().await }); + + swap + } + + pub async fn new_swap_as_bob(&self) -> bob::Swap { + let (swap, event_loop) = self + .bob_swap_factory + .new_swap_as_bob(self.swap_amounts) + .await; + + tokio::spawn(async move { event_loop.run().await }); + + swap + } + + pub async fn recover_alice_from_db(&self) -> alice::Swap { + let (swap, mut event_loop) = self.alice_swap_factory.recover_alice_from_db().await; + + tokio::spawn(async move { event_loop.run().await }); + + swap + } + + pub async fn recover_bob_from_db(&self) -> bob::Swap { + let (swap, event_loop) = self.bob_swap_factory.recover_bob_from_db().await; + + tokio::spawn(async move { event_loop.run().await }); + + swap + } + + pub async fn assert_alice_redeemed(&self, state: AliceState) { + assert!(matches!(state, AliceState::BtcRedeemed)); + + let btc_balance_after_swap = self + .alice_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); + assert_eq!( + btc_balance_after_swap, + self.alice_swap_factory.starting_balances.btc + self.swap_amounts.btc + - bitcoin::Amount::from_sat(bitcoin::TX_FEE) + ); + + let xmr_balance_after_swap = self + .alice_swap_factory + .monero_wallet + .as_ref() + .get_balance() + .await + .unwrap(); + assert!( + xmr_balance_after_swap + <= self.alice_swap_factory.starting_balances.xmr - self.swap_amounts.xmr + ); + } + + pub async fn assert_alice_refunded(&self, state: AliceState) { + assert!(matches!(state, AliceState::XmrRefunded)); + + let btc_balance_after_swap = self + .alice_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); + assert_eq!( + btc_balance_after_swap, + self.alice_swap_factory.starting_balances.btc + ); + + // Ensure that Alice's balance is refreshed as we use a newly created wallet + self.alice_swap_factory + .monero_wallet + .as_ref() + .inner + .refresh() + .await + .unwrap(); + let xmr_balance_after_swap = self + .alice_swap_factory + .monero_wallet + .as_ref() + .get_balance() + .await + .unwrap(); + assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr); + } + + pub async fn assert_alice_punished(&self, state: AliceState) { + assert!(matches!(state, AliceState::BtcPunished)); + + let btc_balance_after_swap = self + .alice_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); + assert_eq!( + btc_balance_after_swap, + self.alice_swap_factory.starting_balances.btc + self.swap_amounts.btc + - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE) + ); + + let xmr_balance_after_swap = self + .alice_swap_factory + .monero_wallet + .as_ref() + .get_balance() + .await + .unwrap(); + assert!( + xmr_balance_after_swap + <= self.alice_swap_factory.starting_balances.xmr - self.swap_amounts.xmr + ); + } + + pub async fn assert_bob_redeemed(&self, state: BobState) { + let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state { + tx_lock_id + } else { + panic!("Bob in unexpected state"); + }; + + let lock_tx_bitcoin_fee = self + .bob_swap_factory + .bitcoin_wallet + .transaction_fee(lock_tx_id) + .await + .unwrap(); + + let btc_balance_after_swap = self + .bob_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); + assert_eq!( + btc_balance_after_swap, + self.bob_swap_factory.starting_balances.btc + - self.swap_amounts.btc + - lock_tx_bitcoin_fee + ); + + // Ensure that Bob's balance is refreshed as we use a newly created wallet + self.bob_swap_factory + .monero_wallet + .as_ref() + .inner + .refresh() + .await + .unwrap(); + let xmr_balance_after_swap = self + .bob_swap_factory + .monero_wallet + .as_ref() + .get_balance() + .await + .unwrap(); + assert_eq!( + xmr_balance_after_swap, + self.bob_swap_factory.starting_balances.xmr + self.swap_amounts.xmr + ); + } + + pub async fn assert_bob_refunded(&self, state: BobState) { + let lock_tx_id = if let BobState::BtcRefunded(state4) = state { + state4.tx_lock_id() + } else { + panic!("Bob in unexpected state"); + }; + let lock_tx_bitcoin_fee = self + .bob_swap_factory + .bitcoin_wallet + .transaction_fee(lock_tx_id) + .await + .unwrap(); + + let btc_balance_after_swap = self + .bob_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); + + let alice_submitted_cancel = btc_balance_after_swap + == self.bob_swap_factory.starting_balances.btc + - lock_tx_bitcoin_fee + - bitcoin::Amount::from_sat(bitcoin::TX_FEE); + + let bob_submitted_cancel = btc_balance_after_swap + == self.bob_swap_factory.starting_balances.btc + - lock_tx_bitcoin_fee + - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE); + + // The cancel tx can be submitted by both Alice and Bob. + // Since we cannot be sure who submitted it we have to assert accordingly + assert!(alice_submitted_cancel || bob_submitted_cancel); + + let xmr_balance_after_swap = self + .bob_swap_factory + .monero_wallet + .as_ref() + .get_balance() + .await + .unwrap(); + assert_eq!( + xmr_balance_after_swap, + self.bob_swap_factory.starting_balances.xmr + ); + } + + pub async fn assert_bob_punished(&self, state: BobState) { + let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state { + tx_lock_id + } else { + panic!("Bob in unexpected state"); + }; + + let lock_tx_bitcoin_fee = self + .bob_swap_factory + .bitcoin_wallet + .transaction_fee(lock_tx_id) + .await + .unwrap(); + + let btc_balance_after_swap = self + .bob_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); + assert_eq!( + btc_balance_after_swap, + self.bob_swap_factory.starting_balances.btc + - self.swap_amounts.btc + - lock_tx_bitcoin_fee + ); + + let xmr_balance_after_swap = self + .bob_swap_factory + .monero_wallet + .as_ref() + .get_balance() + .await + .unwrap(); + assert_eq!( + xmr_balance_after_swap, + self.bob_swap_factory.starting_balances.xmr + ); + } +} + +pub async fn init(testfn: T) where - T: Fn(AliceHarness, BobHarness) -> F, + T: Fn(Test) -> F, F: Future, { let cli = Cli::default(); @@ -44,9 +320,8 @@ where xmr: swap_amounts.xmr * 10, btc: bitcoin::Amount::ZERO, }; - let alice_harness = AliceHarness::new( + let alice_swap_factory = AliceSwapFactory::new( config, - swap_amounts, Uuid::new_v4(), &monero, &containers.bitcoind, @@ -59,32 +334,27 @@ where btc: swap_amounts.btc * 10, }; - let bob_harness = BobHarness::new( + let bob_swap_factory = BobSwapFactory::new( config, - swap_amounts, Uuid::new_v4(), &monero, &containers.bitcoind, bob_starting_balances, - alice_harness.listen_address(), - alice_harness.peer_id(), + alice_swap_factory.listen_address(), + alice_swap_factory.peer_id(), ) .await; - testfn(alice_harness, bob_harness).await -} + let test = Test { + swap_amounts, + alice_swap_factory, + bob_swap_factory, + }; -pub struct Alice { - pub state: AliceState, - pub event_loop_handle: alice::EventLoopHandle, - pub bitcoin_wallet: Arc, - pub monero_wallet: Arc, - pub config: Config, - pub swap_id: Uuid, - pub db: Database, + testfn(test).await } -pub struct AliceHarness { +pub struct AliceSwapFactory { listen_address: Multiaddr, peer_id: PeerId, @@ -92,17 +362,15 @@ pub struct AliceHarness { db_path: PathBuf, swap_id: Uuid, - swap_amounts: SwapAmounts, bitcoin_wallet: Arc, monero_wallet: Arc, config: Config, starting_balances: StartingBalances, } -impl AliceHarness { +impl AliceSwapFactory { async fn new( config: Config, - swap_amounts: SwapAmounts, swap_id: Uuid, monero: &Monero, bitcoind: &Bitcoind<'_>, @@ -132,7 +400,6 @@ impl AliceHarness { listen_address, peer_id, swap_id, - swap_amounts, bitcoin_wallet, monero_wallet, config, @@ -140,39 +407,37 @@ impl AliceHarness { } } - pub async fn new_alice(&self) -> Alice { + pub async fn new_swap_as_alice( + &self, + swap_amounts: SwapAmounts, + ) -> (alice::Swap, alice::EventLoop) { let initial_state = init_alice_state( - self.swap_amounts.btc, - self.swap_amounts.xmr, + swap_amounts.btc, + swap_amounts.xmr, self.bitcoin_wallet.clone(), self.config, ) .await; - let (mut event_loop, event_loop_handle) = + let (event_loop, event_loop_handle) = init_alice_event_loop(self.listen_address.clone(), self.seed); - tokio::spawn(async move { event_loop.run().await }); - let db = Database::open(self.db_path.as_path()).unwrap(); - - Alice { - event_loop_handle, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - config: self.config, - db, - state: initial_state, - swap_id: self.swap_id, - } + ( + alice::Swap { + event_loop_handle, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + config: self.config, + db, + state: initial_state, + swap_id: self.swap_id, + }, + event_loop, + ) } - pub async fn recover_alice_from_db(&self) -> Alice { - // TODO: "simulated restart" issues: - // - create new wallets instead of reusing (hard because of container - // lifetimes) - // - consider aborting the old event loop (currently just keeps running) - + pub async fn recover_alice_from_db(&self) -> (alice::Swap, alice::EventLoop) { // reopen the existing database let db = Database::open(self.db_path.clone().as_path()).unwrap(); @@ -183,60 +448,21 @@ impl AliceHarness { unreachable!() }; - let (mut event_loop, event_loop_handle) = + let (event_loop, event_loop_handle) = init_alice_event_loop(self.listen_address.clone(), self.seed); - tokio::spawn(async move { event_loop.run().await }); - - Alice { - state: resume_state, - event_loop_handle, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - config: self.config, - swap_id: self.swap_id, - db, - } - } - - pub async fn assert_redeemed(&self, state: AliceState) { - assert!(matches!(state, AliceState::BtcRedeemed)); - - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); - assert_eq!( - btc_balance_after_swap, - self.starting_balances.btc + self.swap_amounts.btc - - bitcoin::Amount::from_sat(bitcoin::TX_FEE) - ); - - let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); - assert!(xmr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr); - } - - pub async fn assert_refunded(&self, state: AliceState) { - assert!(matches!(state, AliceState::XmrRefunded)); - - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); - assert_eq!(btc_balance_after_swap, self.starting_balances.btc); - - // Ensure that Alice's balance is refreshed as we use a newly created wallet - self.monero_wallet.as_ref().inner.refresh().await.unwrap(); - let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); - assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr); - } - - pub async fn assert_punished(&self, state: AliceState) { - assert!(matches!(state, AliceState::BtcPunished)); - - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); - assert_eq!( - btc_balance_after_swap, - self.starting_balances.btc + self.swap_amounts.btc - - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE) - ); - - let xnr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); - assert!(xnr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr); + ( + alice::Swap { + state: resume_state, + event_loop_handle, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + config: self.config, + swap_id: self.swap_id, + db, + }, + event_loop, + ) } pub fn peer_id(&self) -> PeerId { @@ -248,20 +474,10 @@ impl AliceHarness { } } -pub struct Bob { - pub state: BobState, - pub event_loop_handle: bob::EventLoopHandle, - pub db: Database, - pub bitcoin_wallet: Arc, - pub monero_wallet: Arc, - pub swap_id: Uuid, -} - -pub struct BobHarness { +pub struct BobSwapFactory { db_path: PathBuf, swap_id: Uuid, - swap_amounts: SwapAmounts, bitcoin_wallet: Arc, monero_wallet: Arc, config: Config, @@ -271,11 +487,10 @@ pub struct BobHarness { alice_connect_peer_id: PeerId, } -impl BobHarness { +impl BobSwapFactory { #[allow(clippy::too_many_arguments)] async fn new( config: Config, - swap_amounts: SwapAmounts, swap_id: Uuid, monero: &Monero, bitcoind: &Bitcoind<'_>, @@ -291,7 +506,6 @@ impl BobHarness { Self { db_path, swap_id, - swap_amounts, bitcoin_wallet, monero_wallet, config, @@ -301,10 +515,10 @@ impl BobHarness { } } - pub async fn new_bob(&self) -> Bob { + pub async fn new_swap_as_bob(&self, swap_amounts: SwapAmounts) -> (bob::Swap, bob::EventLoop) { let initial_state = init_bob_state( - self.swap_amounts.btc, - self.swap_amounts.xmr, + swap_amounts.btc, + swap_amounts.xmr, self.bitcoin_wallet.clone(), self.config, ) @@ -315,26 +529,22 @@ impl BobHarness { self.alice_connect_address.clone(), ); - tokio::spawn(async move { event_loop.run().await }); - let db = Database::open(self.db_path.as_path()).unwrap(); - Bob { - state: initial_state, - event_loop_handle, - db, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - swap_id: self.swap_id, - } + ( + bob::Swap { + state: initial_state, + event_loop_handle, + db, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + swap_id: self.swap_id, + }, + event_loop, + ) } - pub async fn recover_bob_from_db(&self) -> Bob { - // TODO: "simulated restart" issues: - // - create new wallets instead of reusing (hard because of container - // lifetimes) - // - consider aborting the old event loop (currently just keeps running) - + pub async fn recover_bob_from_db(&self) -> (bob::Swap, bob::EventLoop) { // reopen the existing database let db = Database::open(self.db_path.clone().as_path()).unwrap(); @@ -350,99 +560,17 @@ impl BobHarness { self.alice_connect_address.clone(), ); - tokio::spawn(async move { event_loop.run().await }); - - Bob { - state: resume_state, - event_loop_handle, - db, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - swap_id: self.swap_id, - } - } - - pub async fn assert_redeemed(&self, state: BobState) { - let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state { - tx_lock_id - } else { - panic!("Bob in unexpected state"); - }; - - let lock_tx_bitcoin_fee = self - .bitcoin_wallet - .transaction_fee(lock_tx_id) - .await - .unwrap(); - - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); - assert_eq!( - btc_balance_after_swap, - self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee - ); - - // Ensure that Bob's balance is refreshed as we use a newly created wallet - self.monero_wallet.as_ref().inner.refresh().await.unwrap(); - let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); - assert_eq!( - xmr_balance_after_swap, - self.starting_balances.xmr + self.swap_amounts.xmr - ); - } - - pub async fn assert_refunded(&self, state: BobState) { - let lock_tx_id = if let BobState::BtcRefunded(state4) = state { - state4.tx_lock_id() - } else { - panic!("Bob in unexpected state"); - }; - let lock_tx_bitcoin_fee = self - .bitcoin_wallet - .transaction_fee(lock_tx_id) - .await - .unwrap(); - - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); - - let alice_submitted_cancel = btc_balance_after_swap - == self.starting_balances.btc - - lock_tx_bitcoin_fee - - bitcoin::Amount::from_sat(bitcoin::TX_FEE); - - let bob_submitted_cancel = btc_balance_after_swap - == self.starting_balances.btc - - lock_tx_bitcoin_fee - - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE); - - // The cancel tx can be submitted by both Alice and Bob. - // Since we cannot be sure who submitted it we have to assert accordingly - assert!(alice_submitted_cancel || bob_submitted_cancel); - - let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); - assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr); - } - - pub async fn assert_punished(&self, state: BobState) { - let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state { - tx_lock_id - } else { - panic!("Bob in unexpected state"); - }; - - let lock_tx_bitcoin_fee = self - .bitcoin_wallet - .transaction_fee(lock_tx_id) - .await - .unwrap(); - - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); - assert_eq!( - btc_balance_after_swap, - self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee - ); - - let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); - assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr); + ( + bob::Swap { + state: resume_state, + event_loop_handle, + db, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + swap_id: self.swap_id, + }, + event_loop, + ) } } From 0c19af9090ded142ba8328b08e1b34cb641fc312 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 18 Jan 2021 20:33:10 +1100 Subject: [PATCH 03/15] Refactor Alice's peer-id and identity to be handled on the outside Doing this in the behaviour is a weird indirection that is not needed. --- swap/src/main.rs | 13 +++-- swap/src/protocol/alice.rs | 69 ++++++--------------------- swap/src/protocol/alice/event_loop.rs | 9 +--- swap/tests/testutils/mod.rs | 10 ++-- 4 files changed, 31 insertions(+), 70 deletions(-) diff --git a/swap/src/main.rs b/swap/src/main.rs index 8e692839..f04efc48 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -292,13 +292,16 @@ async fn alice_swap( db: Database, seed: Seed, ) -> Result { - let alice_behaviour = alice::Behaviour::new(network::Seed::new(seed)); - let alice_peer_id = alice_behaviour.peer_id(); - info!("Own Peer-ID: {}", alice_peer_id); - let alice_transport = build(alice_behaviour.identity())?; + let identity = network::Seed::new(seed).derive_libp2p_identity(); + + let peer_id = identity.public().into_peer_id(); + + let alice_behaviour = alice::Behaviour::default(); + info!("Own Peer-ID: {}", peer_id); + let alice_transport = build(identity)?; let (mut event_loop, handle) = - alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?; + alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr, peer_id)?; let swap = alice::Swap { state, diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 65419783..a3b12fb5 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -1,11 +1,6 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. -use anyhow::Result; -use libp2p::{ - core::{identity::Keypair, Multiaddr}, - request_response::ResponseChannel, - NetworkBehaviour, PeerId, -}; +use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId}; use tracing::{debug, info}; use crate::{ @@ -13,8 +8,6 @@ use crate::{ network::{ peer_tracker::{self, PeerTracker}, request_response::AliceToBob, - transport::SwapTransport, - Seed, TokioExecutor, }, protocol::bob, SwapAmounts, @@ -55,29 +48,6 @@ pub struct Swap { pub type Swarm = libp2p::Swarm; -pub fn new_swarm( - listen: Multiaddr, - transport: SwapTransport, - behaviour: Behaviour, -) -> Result { - use anyhow::Context as _; - - let local_peer_id = behaviour.peer_id(); - - let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) - .executor(Box::new(TokioExecutor { - handle: tokio::runtime::Handle::current(), - })) - .build(); - - Swarm::listen_on(&mut swarm, listen.clone()) - .with_context(|| format!("Address is not supported: {:#}", listen))?; - - tracing::info!("Initialized swarm: {}", local_peer_id); - - Ok(swarm) -} - #[derive(Debug)] pub enum OutEvent { ConnectionEstablished(PeerId), @@ -162,33 +132,9 @@ pub struct Behaviour { message1: message1::Behaviour, message2: message2::Behaviour, message3: message3::Behaviour, - #[behaviour(ignore)] - identity: Keypair, } impl Behaviour { - pub fn new(seed: Seed) -> Self { - let identity = seed.derive_libp2p_identity(); - - Self { - pt: PeerTracker::default(), - amounts: Amounts::default(), - message0: message0::Behaviour::default(), - message1: message1::Behaviour::default(), - message2: message2::Behaviour::default(), - message3: message3::Behaviour::default(), - identity, - } - } - - pub fn identity(&self) -> Keypair { - self.identity.clone() - } - - pub fn peer_id(&self) -> PeerId { - PeerId::from(self.identity.public()) - } - /// Alice always sends her messages as a response to a request from Bob. pub fn send_amounts(&mut self, channel: ResponseChannel, amounts: SwapAmounts) { let msg = AliceToBob::Amounts(amounts); @@ -214,3 +160,16 @@ impl Behaviour { debug!("Sent Message2"); } } + +impl Default for Behaviour { + fn default() -> Self { + Self { + pt: PeerTracker::default(), + amounts: Amounts::default(), + message0: message0::Behaviour::default(), + message1: message1::Behaviour::default(), + message2: message2::Behaviour::default(), + message3: message3::Behaviour::default(), + } + } +} diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index 3b2f655b..db5eb67f 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -148,10 +148,9 @@ impl EventLoop { transport: SwapTransport, behaviour: Behaviour, listen: Multiaddr, + peer_id: PeerId, ) -> Result<(Self, EventLoopHandle)> { - let local_peer_id = behaviour.peer_id(); - - let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id) + let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id) .executor(Box::new(TokioExecutor { handle: tokio::runtime::Handle::current(), })) @@ -249,8 +248,4 @@ impl EventLoop { } } } - - pub fn peer_id(&self) -> PeerId { - self.swarm.peer_id() - } } diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index ef935769..426ea706 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -666,9 +666,13 @@ fn init_alice_event_loop( alice::event_loop::EventLoop, alice::event_loop::EventLoopHandle, ) { - let alice_behaviour = alice::Behaviour::new(network::Seed::new(seed)); - let alice_transport = build(alice_behaviour.identity()).unwrap(); - alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen).unwrap() + let identity = network::Seed::new(seed).derive_libp2p_identity(); + + let peer_id = identity.public().into_peer_id(); + + let alice_behaviour = alice::Behaviour::default(); + let alice_transport = build(identity).unwrap(); + alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen, peer_id).unwrap() } async fn init_bob_state( From 67e925fe1ff58b8174ef7a4560fb89d5a3f435c8 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 18 Jan 2021 20:41:58 +1100 Subject: [PATCH 04/15] Refactor Bob's peer-id and identity to be handled on the outside Doing this in the behaviour is a weird indirection that is not needed. --- swap/src/main.rs | 16 ++++++--- swap/src/protocol/bob.rs | 53 ++--------------------------- swap/src/protocol/bob/event_loop.rs | 5 ++- swap/tests/testutils/mod.rs | 28 ++++++++++++--- 4 files changed, 39 insertions(+), 63 deletions(-) diff --git a/swap/src/main.rs b/swap/src/main.rs index f04efc48..d9a339bb 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -330,11 +330,19 @@ async fn bob_swap( alice_addr: Multiaddr, seed: Seed, ) -> Result { - let bob_behaviour = bob::Behaviour::new(network::Seed::new(seed)); - let bob_transport = build(bob_behaviour.identity())?; + let identity = network::Seed::new(seed).derive_libp2p_identity(); + let peer_id = identity.public().into_peer_id(); + + let bob_behaviour = bob::Behaviour::default(); + let bob_transport = build(identity)?; - let (event_loop, handle) = - bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)?; + let (event_loop, handle) = bob::event_loop::EventLoop::new( + bob_transport, + bob_behaviour, + peer_id, + alice_peer_id, + alice_addr, + )?; let swap = bob::Swap { state, diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 5558d616..abee4425 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,21 +1,13 @@ //! Run an XMR/BTC swap in the role of Bob. //! Bob holds BTC and wishes receive XMR. -use anyhow::Result; -use libp2p::{ - core::{identity::Keypair, Multiaddr}, - NetworkBehaviour, PeerId, -}; +use libp2p::{core::Multiaddr, NetworkBehaviour, PeerId}; use tracing::{debug, info}; use crate::{ bitcoin, bitcoin::EncryptedSignature, monero, - network::{ - peer_tracker::{self, PeerTracker}, - transport::SwapTransport, - Seed, TokioExecutor, - }, + network::peer_tracker::{self, PeerTracker}, protocol::{alice, bob}, SwapAmounts, }; @@ -54,20 +46,6 @@ pub struct Swap { pub type Swarm = libp2p::Swarm; -pub fn new_swarm(transport: SwapTransport, behaviour: Behaviour) -> Result { - let local_peer_id = behaviour.peer_id(); - - let swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) - .executor(Box::new(TokioExecutor { - handle: tokio::runtime::Handle::current(), - })) - .build(); - - info!("Initialized swarm with identity {}", local_peer_id); - - Ok(swarm) -} - #[derive(Debug, Clone)] pub enum OutEvent { ConnectionEstablished(PeerId), @@ -139,33 +117,9 @@ pub struct Behaviour { message1: message1::Behaviour, message2: message2::Behaviour, message3: message3::Behaviour, - #[behaviour(ignore)] - identity: Keypair, } impl Behaviour { - pub fn new(seed: Seed) -> Self { - let identity = seed.derive_libp2p_identity(); - - Self { - pt: PeerTracker::default(), - amounts: Amounts::default(), - message0: message0::Behaviour::default(), - message1: message1::Behaviour::default(), - message2: message2::Behaviour::default(), - message3: message3::Behaviour::default(), - identity, - } - } - - pub fn identity(&self) -> Keypair { - self.identity.clone() - } - - pub fn peer_id(&self) -> PeerId { - PeerId::from(self.identity.public()) - } - /// Sends a message to Alice to get current amounts based on `btc`. pub fn request_amounts(&mut self, alice: PeerId, btc: u64) { let btc = ::bitcoin::Amount::from_sat(btc); @@ -206,8 +160,6 @@ impl Behaviour { impl Default for Behaviour { fn default() -> Behaviour { - let identity = Keypair::generate_ed25519(); - Self { pt: PeerTracker::default(), amounts: Amounts::default(), @@ -215,7 +167,6 @@ impl Default for Behaviour { message1: message1::Behaviour::default(), message2: message2::Behaviour::default(), message3: message3::Behaviour::default(), - identity, } } } diff --git a/swap/src/protocol/bob/event_loop.rs b/swap/src/protocol/bob/event_loop.rs index b193a5f2..5e95356a 100644 --- a/swap/src/protocol/bob/event_loop.rs +++ b/swap/src/protocol/bob/event_loop.rs @@ -131,12 +131,11 @@ impl EventLoop { pub fn new( transport: SwapTransport, behaviour: Behaviour, + peer_id: PeerId, alice_peer_id: PeerId, alice_addr: Multiaddr, ) -> Result<(Self, EventLoopHandle)> { - let local_peer_id = behaviour.peer_id(); - - let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id) + let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id) .executor(Box::new(TokioExecutor { handle: tokio::runtime::Handle::current(), })) diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 426ea706..9fb22e1c 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -334,7 +334,10 @@ where btc: swap_amounts.btc * 10, }; + let bob_seed = Seed::random().unwrap(); + let bob_swap_factory = BobSwapFactory::new( + bob_seed, config, Uuid::new_v4(), &monero, @@ -475,6 +478,8 @@ impl AliceSwapFactory { } pub struct BobSwapFactory { + seed: Seed, + db_path: PathBuf, swap_id: Uuid, @@ -490,6 +495,7 @@ pub struct BobSwapFactory { impl BobSwapFactory { #[allow(clippy::too_many_arguments)] async fn new( + seed: Seed, config: Config, swap_id: Uuid, monero: &Monero, @@ -504,6 +510,7 @@ impl BobSwapFactory { init_wallets("bob", bitcoind, monero, starting_balances.clone(), config).await; Self { + seed, db_path, swap_id, bitcoin_wallet, @@ -525,6 +532,7 @@ impl BobSwapFactory { .await; let (event_loop, event_loop_handle) = init_bob_event_loop( + self.seed, self.alice_connect_peer_id.clone(), self.alice_connect_address.clone(), ); @@ -556,6 +564,7 @@ impl BobSwapFactory { }; let (event_loop, event_loop_handle) = init_bob_event_loop( + self.seed, self.alice_connect_peer_id.clone(), self.alice_connect_address.clone(), ); @@ -701,14 +710,23 @@ async fn init_bob_state( } fn init_bob_event_loop( + seed: Seed, alice_peer_id: PeerId, alice_addr: Multiaddr, ) -> (bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle) { - let seed = Seed::random().unwrap(); - let bob_behaviour = bob::Behaviour::new(network::Seed::new(seed)); - let bob_transport = build(bob_behaviour.identity()).unwrap(); - bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr) - .unwrap() + let identity = network::Seed::new(seed).derive_libp2p_identity(); + let peer_id = identity.public().into_peer_id(); + + let bob_behaviour = bob::Behaviour::default(); + let bob_transport = build(identity).unwrap(); + bob::event_loop::EventLoop::new( + bob_transport, + bob_behaviour, + peer_id, + alice_peer_id, + alice_addr, + ) + .unwrap() } // This is just to keep the containers alive From 3398ef8236fe8cda5e66545a2f12ad06b6aa8a53 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 18 Jan 2021 21:24:13 +1100 Subject: [PATCH 05/15] Use Alice swap factory in production --- swap/src/lib.rs | 6 + swap/src/main.rs | 142 ++++------- swap/src/protocol/alice.rs | 168 ++++++++++++- swap/tests/happy_path.rs | 2 +- swap/tests/happy_path_restart_alice.rs | 2 +- .../happy_path_restart_bob_after_comm.rs | 2 +- .../happy_path_restart_bob_before_comm.rs | 2 +- swap/tests/punish.rs | 2 +- swap/tests/refund_restart_alice.rs | 2 +- swap/tests/testutils/mod.rs | 222 +++--------------- 10 files changed, 259 insertions(+), 291 deletions(-) diff --git a/swap/src/lib.rs b/swap/src/lib.rs index f7020277..7644da0a 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -56,6 +56,12 @@ pub struct SwapAmounts { pub xmr: monero::Amount, } +#[derive(Debug, Clone)] +pub struct StartingBalances { + pub xmr: monero::Amount, + pub btc: bitcoin::Amount, +} + // TODO: Display in XMR and BTC (not picos and sats). impl Display for SwapAmounts { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/swap/src/main.rs b/swap/src/main.rs index d9a339bb..4a7cc7ef 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -26,10 +26,10 @@ use swap::{ database::{Database, Swap}, monero, network, network::transport::build, - protocol::{alice, alice::AliceState, bob, bob::BobState}, + protocol::{alice, bob, bob::BobState}, seed::Seed, trace::init_tracing, - SwapAmounts, + StartingBalances, SwapAmounts, }; use tracing::{info, log::LevelFilter}; use uuid::Uuid; @@ -51,8 +51,9 @@ async fn main() -> Result<()> { opt.data_dir ); let data_dir = std::path::Path::new(opt.data_dir.as_str()).to_path_buf(); - let db = - Database::open(data_dir.join("database").as_path()).context("Could not open database")?; + let db_path = data_dir.join("database"); + + let db = Database::open(db_path.as_path()).context("Could not open database")?; let seed = swap::config::seed::Seed::from_file_or_generate(&data_dir) .expect("Could not retrieve/initialize seed") @@ -67,7 +68,12 @@ async fn main() -> Result<()> { send_monero, receive_bitcoin, } => { - let (bitcoin_wallet, monero_wallet) = setup_wallets( + let swap_amounts = SwapAmounts { + xmr: send_monero, + btc: receive_bitcoin, + }; + + let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets( bitcoind_url, bitcoin_wallet_name.as_str(), monero_wallet_rpc_url, @@ -75,50 +81,23 @@ async fn main() -> Result<()> { ) .await?; - let amounts = SwapAmounts { - btc: receive_bitcoin, - xmr: send_monero, - }; - - let alice_state = { - let rng = &mut OsRng; - let a = bitcoin::SecretKey::new_random(rng); - let s_a = cross_curve_dleq::Scalar::random(rng); - let v_a = monero::PrivateViewKey::new_random(rng); - let redeem_address = bitcoin_wallet.as_ref().new_address().await?; - let punish_address = redeem_address.clone(); - let state0 = alice::state::State0::new( - a, - s_a, - v_a, - amounts.btc, - amounts.xmr, - config.bitcoin_cancel_timelock, - config.bitcoin_punish_timelock, - redeem_address, - punish_address, - ); - - AliceState::Started { amounts, state0 } - }; - let swap_id = Uuid::new_v4(); - info!( - "Swap sending {} and receiving {} started with ID {}", - send_monero, receive_bitcoin, swap_id - ); - alice_swap( + let alice_factory = alice::AliceSwapFactory::new( + seed, + config, swap_id, - alice_state, - listen_addr, bitcoin_wallet, monero_wallet, - config, - db, - seed, + starting_balances, + db_path, + listen_addr, ) - .await?; + .await; + let (swap, mut event_loop) = alice_factory.new_swap_as_alice(swap_amounts).await?; + + tokio::spawn(async move { event_loop.run().await }); + alice::run(swap).await?; } Command::BuyXmr { alice_peer_id, @@ -129,7 +108,7 @@ async fn main() -> Result<()> { send_bitcoin, receive_monero, } => { - let (bitcoin_wallet, monero_wallet) = setup_wallets( + let (bitcoin_wallet, monero_wallet, _) = setup_wallets( bitcoind_url, bitcoin_wallet_name.as_str(), monero_wallet_rpc_url, @@ -192,30 +171,29 @@ async fn main() -> Result<()> { monero_wallet_rpc_url, listen_addr, }) => { - let db_state = if let Swap::Alice(db_state) = db.get_state(swap_id)? { - db_state - } else { - bail!("Swap {} is not sell xmr.", swap_id) - }; - - let (bitcoin_wallet, monero_wallet) = setup_wallets( + let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets( bitcoind_url, bitcoin_wallet_name.as_str(), monero_wallet_rpc_url, config, ) .await?; - alice_swap( + + let alice_factory = alice::AliceSwapFactory::new( + seed, + config, swap_id, - db_state.into(), - listen_addr, bitcoin_wallet, monero_wallet, - config, - db, - seed, + starting_balances, + db_path, + listen_addr, ) - .await?; + .await; + let (swap, mut event_loop) = alice_factory.recover_alice_from_db().await?; + + tokio::spawn(async move { event_loop.run().await }); + alice::run(swap).await?; } Command::Resume(Resume::BuyXmr { swap_id, @@ -231,7 +209,7 @@ async fn main() -> Result<()> { bail!("Swap {} is not buy xmr.", swap_id) }; - let (bitcoin_wallet, monero_wallet) = setup_wallets( + let (bitcoin_wallet, monero_wallet, _) = setup_wallets( bitcoind_url, bitcoin_wallet_name.as_str(), monero_wallet_rpc_url, @@ -260,7 +238,11 @@ async fn setup_wallets( bitcoin_wallet_name: &str, monero_wallet_rpc_url: url::Url, config: Config, -) -> Result<(Arc, Arc)> { +) -> Result<( + Arc, + Arc, + StartingBalances, +)> { let bitcoin_wallet = swap::bitcoin::Wallet::new(bitcoin_wallet_name, bitcoind_url, config.bitcoin_network) .await?; @@ -279,44 +261,12 @@ async fn setup_wallets( ); let monero_wallet = Arc::new(monero_wallet); - Ok((bitcoin_wallet, monero_wallet)) -} -#[allow(clippy::too_many_arguments)] -async fn alice_swap( - swap_id: Uuid, - state: AliceState, - listen_addr: Multiaddr, - bitcoin_wallet: Arc, - monero_wallet: Arc, - config: Config, - db: Database, - seed: Seed, -) -> Result { - let identity = network::Seed::new(seed).derive_libp2p_identity(); - - let peer_id = identity.public().into_peer_id(); - - let alice_behaviour = alice::Behaviour::default(); - info!("Own Peer-ID: {}", peer_id); - let alice_transport = build(identity)?; - - let (mut event_loop, handle) = - alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr, peer_id)?; - - let swap = alice::Swap { - state, - event_loop_handle: handle, - bitcoin_wallet, - monero_wallet, - config, - swap_id, - db, + let starting_balances = StartingBalances { + btc: bitcoin_balance, + xmr: monero_balance, }; - let swap = alice::swap::run(swap); - - tokio::spawn(async move { event_loop.run().await }); - swap.await + Ok((bitcoin_wallet, monero_wallet, starting_balances)) } #[allow(clippy::too_many_arguments)] diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index a3b12fb5..bf06aa47 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -1,16 +1,18 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. +use anyhow::Result; use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId}; use tracing::{debug, info}; use crate::{ - bitcoin, monero, + bitcoin, database, monero, network::{ peer_tracker::{self, PeerTracker}, request_response::AliceToBob, + Seed as NetworkSeed, }, protocol::bob, - SwapAmounts, + StartingBalances, SwapAmounts, }; pub use self::{ @@ -22,8 +24,10 @@ pub use self::{ state::*, swap::{run, run_until}, }; -use crate::{config::Config, database::Database}; -use std::sync::Arc; +use crate::{config::Config, database::Database, network::transport::build, seed::Seed}; +use libp2p::{core::Multiaddr, identity::Keypair}; +use rand::rngs::OsRng; +use std::{path::PathBuf, sync::Arc}; use uuid::Uuid; mod amounts; @@ -46,6 +50,162 @@ pub struct Swap { pub db: Database, } +pub struct AliceSwapFactory { + listen_address: Multiaddr, + identity: Keypair, + peer_id: PeerId, + + db_path: PathBuf, + swap_id: Uuid, + + pub bitcoin_wallet: Arc, + pub monero_wallet: Arc, + config: Config, + pub starting_balances: StartingBalances, +} + +impl AliceSwapFactory { + #[allow(clippy::too_many_arguments)] + pub async fn new( + seed: Seed, + config: Config, + swap_id: Uuid, + bitcoin_wallet: Arc, + monero_wallet: Arc, + starting_balances: StartingBalances, + db_path: PathBuf, + listen_address: Multiaddr, + ) -> Self { + let network_seed = NetworkSeed::new(seed); + let identity = network_seed.derive_libp2p_identity(); + let peer_id = PeerId::from(identity.public()); + + Self { + listen_address, + identity, + peer_id, + db_path, + swap_id, + bitcoin_wallet, + monero_wallet, + config, + starting_balances, + } + } + + pub async fn new_swap_as_alice(&self, swap_amounts: SwapAmounts) -> Result<(Swap, EventLoop)> { + let initial_state = init_alice_state( + swap_amounts.btc, + swap_amounts.xmr, + self.bitcoin_wallet.clone(), + self.config, + ) + .await?; + + let (event_loop, event_loop_handle) = init_alice_event_loop( + self.listen_address.clone(), + self.identity.clone(), + self.peer_id.clone(), + )?; + + let db = Database::open(self.db_path.as_path())?; + + Ok(( + Swap { + event_loop_handle, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + config: self.config, + db, + state: initial_state, + swap_id: self.swap_id, + }, + event_loop, + )) + } + + pub async fn recover_alice_from_db(&self) -> Result<(Swap, EventLoop)> { + // reopen the existing database + let db = Database::open(self.db_path.clone().as_path())?; + + let resume_state = if let database::Swap::Alice(state) = db.get_state(self.swap_id)? { + state.into() + } else { + unreachable!() + }; + + let (event_loop, event_loop_handle) = init_alice_event_loop( + self.listen_address.clone(), + self.identity.clone(), + self.peer_id.clone(), + )?; + + Ok(( + Swap { + state: resume_state, + event_loop_handle, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + config: self.config, + swap_id: self.swap_id, + db, + }, + event_loop, + )) + } + + pub fn peer_id(&self) -> PeerId { + self.peer_id.clone() + } + + pub fn listen_address(&self) -> Multiaddr { + self.listen_address.clone() + } +} + +async fn init_alice_state( + btc_to_swap: bitcoin::Amount, + xmr_to_swap: monero::Amount, + alice_btc_wallet: Arc, + config: Config, +) -> Result { + let rng = &mut OsRng; + + let amounts = SwapAmounts { + btc: btc_to_swap, + xmr: xmr_to_swap, + }; + + let a = bitcoin::SecretKey::new_random(rng); + let s_a = cross_curve_dleq::Scalar::random(rng); + let v_a = monero::PrivateViewKey::new_random(rng); + let redeem_address = alice_btc_wallet.as_ref().new_address().await?; + let punish_address = redeem_address.clone(); + let state0 = State0::new( + a, + s_a, + v_a, + amounts.btc, + amounts.xmr, + config.bitcoin_cancel_timelock, + config.bitcoin_punish_timelock, + redeem_address, + punish_address, + ); + + Ok(AliceState::Started { amounts, state0 }) +} + +fn init_alice_event_loop( + listen: Multiaddr, + identity: Keypair, + peer_id: PeerId, +) -> Result<(EventLoop, EventLoopHandle)> { + let alice_behaviour = Behaviour::default(); + let alice_transport = build(identity)?; + EventLoop::new(alice_transport, alice_behaviour, listen, peer_id) +} + pub type Swarm = libp2p::Swarm; #[derive(Debug)] diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 054a9d08..d3385229 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -7,7 +7,7 @@ pub mod testutils; #[tokio::test] async fn happy_path() { - testutils::init(|test| async move { + testutils::setup_test(|test| async move { let alice_swap = test.new_swap_as_alice().await; let bob_swap = test.new_swap_as_bob().await; diff --git a/swap/tests/happy_path_restart_alice.rs b/swap/tests/happy_path_restart_alice.rs index 8f84129b..6cce546b 100644 --- a/swap/tests/happy_path_restart_alice.rs +++ b/swap/tests/happy_path_restart_alice.rs @@ -4,7 +4,7 @@ pub mod testutils; #[tokio::test] async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { - testutils::init(|test| async move { + testutils::setup_test(|test| async move { let alice_swap = test.new_swap_as_alice().await; let bob_swap = test.new_swap_as_bob().await; diff --git a/swap/tests/happy_path_restart_bob_after_comm.rs b/swap/tests/happy_path_restart_bob_after_comm.rs index d49c939f..194b6349 100644 --- a/swap/tests/happy_path_restart_bob_after_comm.rs +++ b/swap/tests/happy_path_restart_bob_after_comm.rs @@ -4,7 +4,7 @@ pub mod testutils; #[tokio::test] async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { - testutils::init(|test| async move { + testutils::setup_test(|test| async move { let alice_swap = test.new_swap_as_alice().await; let bob_swap = test.new_swap_as_bob().await; diff --git a/swap/tests/happy_path_restart_bob_before_comm.rs b/swap/tests/happy_path_restart_bob_before_comm.rs index 1fce0e5c..aea07c52 100644 --- a/swap/tests/happy_path_restart_bob_before_comm.rs +++ b/swap/tests/happy_path_restart_bob_before_comm.rs @@ -7,7 +7,7 @@ pub mod testutils; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { - testutils::init(|test| async move { + testutils::setup_test(|test| async move { let alice_swap = test.new_swap_as_alice().await; let bob_swap = test.new_swap_as_bob().await; diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs index 16515569..aa4524d4 100644 --- a/swap/tests/punish.rs +++ b/swap/tests/punish.rs @@ -9,7 +9,7 @@ pub mod testutils; /// the encsig and fail to refund or redeem. Alice punishes. #[tokio::test] async fn alice_punishes_if_bob_never_acts_after_fund() { - testutils::init(|test| async move { + testutils::setup_test(|test| async move { let alice_swap = test.new_swap_as_alice().await; let bob_swap = test.new_swap_as_bob().await; diff --git a/swap/tests/refund_restart_alice.rs b/swap/tests/refund_restart_alice.rs index e5e91ae8..48bc09e0 100644 --- a/swap/tests/refund_restart_alice.rs +++ b/swap/tests/refund_restart_alice.rs @@ -6,7 +6,7 @@ pub mod testutils; /// then also refunds. #[tokio::test] async fn given_alice_restarts_after_xmr_is_locked_abort_swap() { - testutils::init(|test| async move { + testutils::setup_test(|test| async move { let alice_swap = test.new_swap_as_alice().await; let bob_swap = test.new_swap_as_bob().await; diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 9fb22e1c..7bb05f8a 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -12,9 +12,14 @@ use swap::{ database::Database, monero, network, network::transport::build, - protocol::{alice, alice::AliceState, bob, bob::BobState}, + protocol::{ + alice, + alice::{AliceState, AliceSwapFactory}, + bob, + bob::BobState, + }, seed::Seed, - SwapAmounts, + StartingBalances, SwapAmounts, }; use tempfile::tempdir; use testcontainers::{clients::Cli, Container}; @@ -34,7 +39,8 @@ impl Test { let (swap, mut event_loop) = self .alice_swap_factory .new_swap_as_alice(self.swap_amounts) - .await; + .await + .unwrap(); tokio::spawn(async move { event_loop.run().await }); @@ -53,7 +59,11 @@ impl Test { } pub async fn recover_alice_from_db(&self) -> alice::Swap { - let (swap, mut event_loop) = self.alice_swap_factory.recover_alice_from_db().await; + let (swap, mut event_loop) = self + .alice_swap_factory + .recover_alice_from_db() + .await + .unwrap(); tokio::spawn(async move { event_loop.run().await }); @@ -298,7 +308,7 @@ impl Test { } } -pub async fn init(testfn: T) +pub async fn setup_test(testfn: T) where T: Fn(Test) -> F, F: Future, @@ -320,12 +330,31 @@ where xmr: swap_amounts.xmr * 10, btc: bitcoin::Amount::ZERO, }; + + let port = get_port().expect("Failed to find a free port"); + + let listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port) + .parse() + .expect("failed to parse Alice's address"); + + let (alice_bitcoin_wallet, alice_monero_wallet) = init_wallets( + "alice", + &containers.bitcoind, + &monero, + alice_starting_balances.clone(), + config, + ) + .await; + let alice_swap_factory = AliceSwapFactory::new( + Seed::random().unwrap(), config, Uuid::new_v4(), - &monero, - &containers.bitcoind, + alice_bitcoin_wallet, + alice_monero_wallet, alice_starting_balances, + tempdir().unwrap().path().to_path_buf(), + listen_address, ) .await; @@ -334,10 +363,8 @@ where btc: swap_amounts.btc * 10, }; - let bob_seed = Seed::random().unwrap(); - let bob_swap_factory = BobSwapFactory::new( - bob_seed, + Seed::random().unwrap(), config, Uuid::new_v4(), &monero, @@ -357,126 +384,6 @@ where testfn(test).await } -pub struct AliceSwapFactory { - listen_address: Multiaddr, - peer_id: PeerId, - - seed: Seed, - db_path: PathBuf, - swap_id: Uuid, - - bitcoin_wallet: Arc, - monero_wallet: Arc, - config: Config, - starting_balances: StartingBalances, -} - -impl AliceSwapFactory { - async fn new( - config: Config, - swap_id: Uuid, - monero: &Monero, - bitcoind: &Bitcoind<'_>, - starting_balances: StartingBalances, - ) -> Self { - let port = get_port().expect("Failed to find a free port"); - - let listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port) - .parse() - .expect("failed to parse Alice's address"); - - let seed = Seed::random().unwrap(); - - let db_path = tempdir().unwrap().path().to_path_buf(); - - let (bitcoin_wallet, monero_wallet) = - init_wallets("alice", bitcoind, monero, starting_balances.clone(), config).await; - - // TODO: This should be done by changing the production code - let network_seed = network::Seed::new(seed); - let identity = network_seed.derive_libp2p_identity(); - let peer_id = PeerId::from(identity.public()); - - Self { - seed, - db_path, - listen_address, - peer_id, - swap_id, - bitcoin_wallet, - monero_wallet, - config, - starting_balances, - } - } - - pub async fn new_swap_as_alice( - &self, - swap_amounts: SwapAmounts, - ) -> (alice::Swap, alice::EventLoop) { - let initial_state = init_alice_state( - swap_amounts.btc, - swap_amounts.xmr, - self.bitcoin_wallet.clone(), - self.config, - ) - .await; - - let (event_loop, event_loop_handle) = - init_alice_event_loop(self.listen_address.clone(), self.seed); - - let db = Database::open(self.db_path.as_path()).unwrap(); - ( - alice::Swap { - event_loop_handle, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - config: self.config, - db, - state: initial_state, - swap_id: self.swap_id, - }, - event_loop, - ) - } - - pub async fn recover_alice_from_db(&self) -> (alice::Swap, alice::EventLoop) { - // reopen the existing database - let db = Database::open(self.db_path.clone().as_path()).unwrap(); - - let resume_state = - if let swap::database::Swap::Alice(state) = db.get_state(self.swap_id).unwrap() { - state.into() - } else { - unreachable!() - }; - - let (event_loop, event_loop_handle) = - init_alice_event_loop(self.listen_address.clone(), self.seed); - - ( - alice::Swap { - state: resume_state, - event_loop_handle, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - config: self.config, - swap_id: self.swap_id, - db, - }, - event_loop, - ) - } - - pub fn peer_id(&self) -> PeerId { - self.peer_id.clone() - } - - pub fn listen_address(&self) -> Multiaddr { - self.listen_address.clone() - } -} - pub struct BobSwapFactory { seed: Seed, @@ -583,12 +490,6 @@ impl BobSwapFactory { } } -#[derive(Debug, Clone)] -struct StartingBalances { - pub xmr: monero::Amount, - pub btc: bitcoin::Amount, -} - async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) { let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); let _ = bitcoind.init(5).await; @@ -635,55 +536,6 @@ async fn init_wallets( (btc_wallet, xmr_wallet) } -async fn init_alice_state( - btc_to_swap: bitcoin::Amount, - xmr_to_swap: monero::Amount, - alice_btc_wallet: Arc, - config: Config, -) -> AliceState { - let rng = &mut OsRng; - - let amounts = SwapAmounts { - btc: btc_to_swap, - xmr: xmr_to_swap, - }; - - let a = bitcoin::SecretKey::new_random(rng); - let s_a = cross_curve_dleq::Scalar::random(rng); - let v_a = monero::PrivateViewKey::new_random(rng); - let redeem_address = alice_btc_wallet.as_ref().new_address().await.unwrap(); - let punish_address = redeem_address.clone(); - let state0 = alice::State0::new( - a, - s_a, - v_a, - amounts.btc, - amounts.xmr, - config.bitcoin_cancel_timelock, - config.bitcoin_punish_timelock, - redeem_address, - punish_address, - ); - - AliceState::Started { amounts, state0 } -} - -fn init_alice_event_loop( - listen: Multiaddr, - seed: Seed, -) -> ( - alice::event_loop::EventLoop, - alice::event_loop::EventLoopHandle, -) { - let identity = network::Seed::new(seed).derive_libp2p_identity(); - - let peer_id = identity.public().into_peer_id(); - - let alice_behaviour = alice::Behaviour::default(); - let alice_transport = build(identity).unwrap(); - alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen, peer_id).unwrap() -} - async fn init_bob_state( btc_to_swap: bitcoin::Amount, xmr_to_swap: monero::Amount, From e56c48ee234202cbf8cfd436745bf10438c3b436 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 18 Jan 2021 21:27:59 +1100 Subject: [PATCH 06/15] Remove unused type --- swap/src/protocol/alice.rs | 2 -- swap/src/protocol/bob.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index bf06aa47..04ff737b 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -206,8 +206,6 @@ fn init_alice_event_loop( EventLoop::new(alice_transport, alice_behaviour, listen, peer_id) } -pub type Swarm = libp2p::Swarm; - #[derive(Debug)] pub enum OutEvent { ConnectionEstablished(PeerId), diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index abee4425..fb2f09aa 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -44,8 +44,6 @@ pub struct Swap { pub swap_id: Uuid, } -pub type Swarm = libp2p::Swarm; - #[derive(Debug, Clone)] pub enum OutEvent { ConnectionEstablished(PeerId), From 75f89f3b2544cec95d66c585c9d280619bd2a93b Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 18 Jan 2021 21:57:17 +1100 Subject: [PATCH 07/15] Use Bob swap factory in production --- swap/src/main.rs | 134 +++++++++----------------- swap/src/protocol/bob.rs | 165 +++++++++++++++++++++++++++++++- swap/tests/testutils/mod.rs | 186 ++++-------------------------------- 3 files changed, 225 insertions(+), 260 deletions(-) diff --git a/swap/src/main.rs b/swap/src/main.rs index 4a7cc7ef..891c7954 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -14,20 +14,16 @@ #![allow(non_snake_case)] use crate::cli::{Command, Options, Resume}; -use anyhow::{bail, Context, Result}; -use libp2p::{core::Multiaddr, PeerId}; +use anyhow::{Context, Result}; use prettytable::{row, Table}; -use rand::rngs::OsRng; use std::sync::Arc; use structopt::StructOpt; use swap::{ bitcoin, config::Config, - database::{Database, Swap}, - monero, network, - network::transport::build, - protocol::{alice, bob, bob::BobState}, - seed::Seed, + database::Database, + monero, + protocol::{alice, bob, bob::BobSwapFactory}, trace::init_tracing, StartingBalances, SwapAmounts, }; @@ -53,8 +49,6 @@ async fn main() -> Result<()> { let data_dir = std::path::Path::new(opt.data_dir.as_str()).to_path_buf(); let db_path = data_dir.join("database"); - let db = Database::open(db_path.as_path()).context("Could not open database")?; - let seed = swap::config::seed::Seed::from_file_or_generate(&data_dir) .expect("Could not retrieve/initialize seed") .into(); @@ -83,6 +77,11 @@ async fn main() -> Result<()> { let swap_id = Uuid::new_v4(); + info!( + "Swap sending {} and receiving {} starting with ID {}", + send_monero, receive_bitcoin, swap_id + ); + let alice_factory = alice::AliceSwapFactory::new( seed, config, @@ -108,7 +107,12 @@ async fn main() -> Result<()> { send_bitcoin, receive_monero, } => { - let (bitcoin_wallet, monero_wallet, _) = setup_wallets( + let swap_amounts = SwapAmounts { + btc: send_bitcoin, + xmr: receive_monero, + }; + + let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets( bitcoind_url, bitcoin_wallet_name.as_str(), monero_wallet_rpc_url, @@ -116,47 +120,36 @@ async fn main() -> Result<()> { ) .await?; - let refund_address = bitcoin_wallet.new_address().await?; - let state0 = bob::state::State0::new( - &mut OsRng, - send_bitcoin, - receive_monero, - config.bitcoin_cancel_timelock, - config.bitcoin_punish_timelock, - refund_address, - config.monero_finality_confirmations, - ); - - let amounts = SwapAmounts { - btc: send_bitcoin, - xmr: receive_monero, - }; - - let bob_state = BobState::Started { state0, amounts }; - let swap_id = Uuid::new_v4(); + info!( - "Swap sending {} and receiving {} started with ID {}", + "Swap sending {} and receiving {} starting with ID {}", send_bitcoin, receive_monero, swap_id ); - bob_swap( + let bob_factory = BobSwapFactory::new( + seed, + db_path, swap_id, - bob_state, bitcoin_wallet, monero_wallet, - db, - alice_peer_id, + config, + starting_balances, alice_addr, - seed, - ) - .await?; + alice_peer_id, + ); + let (swap, event_loop) = bob_factory.new_swap_as_bob(swap_amounts).await?; + + tokio::spawn(async move { event_loop.run().await }); + bob::run(swap).await?; } Command::History => { let mut table = Table::new(); table.add_row(row!["SWAP ID", "STATE"]); + let db = Database::open(db_path.as_path()).context("Could not open database")?; + for (swap_id, state) in db.all()? { table.add_row(row![swap_id, state]); } @@ -203,30 +196,29 @@ async fn main() -> Result<()> { alice_peer_id, alice_addr, }) => { - let db_state = if let Swap::Bob(db_state) = db.get_state(swap_id)? { - db_state - } else { - bail!("Swap {} is not buy xmr.", swap_id) - }; - - let (bitcoin_wallet, monero_wallet, _) = setup_wallets( + let (bitcoin_wallet, monero_wallet, starting_balances) = setup_wallets( bitcoind_url, bitcoin_wallet_name.as_str(), monero_wallet_rpc_url, config, ) .await?; - bob_swap( + + let bob_factory = BobSwapFactory::new( + seed, + db_path, swap_id, - db_state.into(), bitcoin_wallet, monero_wallet, - db, - alice_peer_id, + config, + starting_balances, alice_addr, - seed, - ) - .await?; + alice_peer_id, + ); + let (swap, event_loop) = bob_factory.recover_bob_from_db().await?; + + tokio::spawn(async move { event_loop.run().await }); + bob::run(swap).await?; } }; @@ -268,43 +260,3 @@ async fn setup_wallets( Ok((bitcoin_wallet, monero_wallet, starting_balances)) } - -#[allow(clippy::too_many_arguments)] -async fn bob_swap( - swap_id: Uuid, - state: BobState, - bitcoin_wallet: Arc, - monero_wallet: Arc, - db: Database, - alice_peer_id: PeerId, - alice_addr: Multiaddr, - seed: Seed, -) -> Result { - let identity = network::Seed::new(seed).derive_libp2p_identity(); - let peer_id = identity.public().into_peer_id(); - - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(identity)?; - - let (event_loop, handle) = bob::event_loop::EventLoop::new( - bob_transport, - bob_behaviour, - peer_id, - alice_peer_id, - alice_addr, - )?; - - let swap = bob::Swap { - state, - event_loop_handle: handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - }; - - let swap = bob::swap::run(swap); - - tokio::spawn(event_loop.run()); - swap.await -} diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index fb2f09aa..25b35f79 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,15 +1,16 @@ //! Run an XMR/BTC swap in the role of Bob. //! Bob holds BTC and wishes receive XMR. +use anyhow::Result; use libp2p::{core::Multiaddr, NetworkBehaviour, PeerId}; use tracing::{debug, info}; use crate::{ bitcoin, bitcoin::EncryptedSignature, - monero, + database, monero, network, network::peer_tracker::{self, PeerTracker}, protocol::{alice, bob}, - SwapAmounts, + StartingBalances, SwapAmounts, }; pub use self::{ @@ -22,8 +23,10 @@ pub use self::{ state::*, swap::{run, run_until}, }; -use crate::database::Database; -use std::sync::Arc; +use crate::{config::Config, database::Database, network::transport::build, seed::Seed}; +use libp2p::identity::Keypair; +use rand::rngs::OsRng; +use std::{path::PathBuf, sync::Arc}; use uuid::Uuid; mod amounts; @@ -44,6 +47,160 @@ pub struct Swap { pub swap_id: Uuid, } +pub struct BobSwapFactory { + identity: Keypair, + peer_id: PeerId, + + db_path: PathBuf, + swap_id: Uuid, + + pub bitcoin_wallet: Arc, + pub monero_wallet: Arc, + config: Config, + pub starting_balances: StartingBalances, + + alice_connect_address: Multiaddr, + alice_connect_peer_id: PeerId, +} + +impl BobSwapFactory { + #[allow(clippy::too_many_arguments)] + pub fn new( + seed: Seed, + db_path: PathBuf, + swap_id: Uuid, + bitcoin_wallet: Arc, + monero_wallet: Arc, + config: Config, + starting_balances: StartingBalances, + alice_connect_address: Multiaddr, + alice_connect_peer_id: PeerId, + ) -> Self { + let identity = network::Seed::new(seed).derive_libp2p_identity(); + let peer_id = identity.public().into_peer_id(); + + Self { + identity, + peer_id, + db_path, + swap_id, + bitcoin_wallet, + monero_wallet, + config, + starting_balances, + alice_connect_address, + alice_connect_peer_id, + } + } + + pub async fn new_swap_as_bob( + &self, + swap_amounts: SwapAmounts, + ) -> Result<(bob::Swap, bob::EventLoop)> { + let initial_state = init_bob_state( + swap_amounts.btc, + swap_amounts.xmr, + self.bitcoin_wallet.clone(), + self.config, + ) + .await?; + + let (event_loop, event_loop_handle) = init_bob_event_loop( + self.identity.clone(), + self.peer_id.clone(), + self.alice_connect_peer_id.clone(), + self.alice_connect_address.clone(), + )?; + + let db = Database::open(self.db_path.as_path())?; + + Ok(( + Swap { + state: initial_state, + event_loop_handle, + db, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + swap_id: self.swap_id, + }, + event_loop, + )) + } + + pub async fn recover_bob_from_db(&self) -> Result<(bob::Swap, bob::EventLoop)> { + // reopen the existing database + let db = Database::open(self.db_path.clone().as_path())?; + + let resume_state = if let database::Swap::Bob(state) = db.get_state(self.swap_id)? { + state.into() + } else { + unreachable!() + }; + + let (event_loop, event_loop_handle) = init_bob_event_loop( + self.identity.clone(), + self.peer_id.clone(), + self.alice_connect_peer_id.clone(), + self.alice_connect_address.clone(), + )?; + + Ok(( + Swap { + state: resume_state, + event_loop_handle, + db, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + swap_id: self.swap_id, + }, + event_loop, + )) + } +} + +async fn init_bob_state( + btc_to_swap: bitcoin::Amount, + xmr_to_swap: monero::Amount, + bob_btc_wallet: Arc, + config: Config, +) -> Result { + let amounts = SwapAmounts { + btc: btc_to_swap, + xmr: xmr_to_swap, + }; + + let refund_address = bob_btc_wallet.new_address().await?; + let state0 = bob::State0::new( + &mut OsRng, + btc_to_swap, + xmr_to_swap, + config.bitcoin_cancel_timelock, + config.bitcoin_punish_timelock, + refund_address, + config.monero_finality_confirmations, + ); + + Ok(BobState::Started { state0, amounts }) +} + +fn init_bob_event_loop( + identity: Keypair, + peer_id: PeerId, + alice_peer_id: PeerId, + alice_addr: Multiaddr, +) -> Result<(bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle)> { + let bob_behaviour = bob::Behaviour::default(); + let bob_transport = build(identity)?; + + bob::event_loop::EventLoop::new( + bob_transport, + bob_behaviour, + peer_id, + alice_peer_id, + alice_addr, + ) +} + #[derive(Debug, Clone)] pub enum OutEvent { ConnectionEstablished(PeerId), diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 7bb05f8a..b0938705 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -2,21 +2,18 @@ use crate::testutils; use bitcoin_harness::Bitcoind; use futures::Future; use get_port::get_port; -use libp2p::{core::Multiaddr, PeerId}; +use libp2p::core::Multiaddr; use monero_harness::{image, Monero}; -use rand::rngs::OsRng; -use std::{path::PathBuf, sync::Arc}; +use std::sync::Arc; use swap::{ bitcoin, config::Config, - database::Database, - monero, network, - network::transport::build, + monero, protocol::{ alice, alice::{AliceState, AliceSwapFactory}, bob, - bob::BobState, + bob::{BobState, BobSwapFactory}, }, seed::Seed, StartingBalances, SwapAmounts, @@ -51,7 +48,8 @@ impl Test { let (swap, event_loop) = self .bob_swap_factory .new_swap_as_bob(self.swap_amounts) - .await; + .await + .unwrap(); tokio::spawn(async move { event_loop.run().await }); @@ -71,7 +69,7 @@ impl Test { } pub async fn recover_bob_from_db(&self) -> bob::Swap { - let (swap, event_loop) = self.bob_swap_factory.recover_bob_from_db().await; + let (swap, event_loop) = self.bob_swap_factory.recover_bob_from_db().await.unwrap(); tokio::spawn(async move { event_loop.run().await }); @@ -363,17 +361,26 @@ where btc: swap_amounts.btc * 10, }; + let (bob_bitcoin_wallet, bob_monero_wallet) = init_wallets( + "bob", + &containers.bitcoind, + &monero, + bob_starting_balances.clone(), + config, + ) + .await; + let bob_swap_factory = BobSwapFactory::new( Seed::random().unwrap(), - config, + tempdir().unwrap().path().to_path_buf(), Uuid::new_v4(), - &monero, - &containers.bitcoind, + bob_bitcoin_wallet, + bob_monero_wallet, + config, bob_starting_balances, alice_swap_factory.listen_address(), alice_swap_factory.peer_id(), - ) - .await; + ); let test = Test { swap_amounts, @@ -384,112 +391,6 @@ where testfn(test).await } -pub struct BobSwapFactory { - seed: Seed, - - db_path: PathBuf, - swap_id: Uuid, - - bitcoin_wallet: Arc, - monero_wallet: Arc, - config: Config, - starting_balances: StartingBalances, - - alice_connect_address: Multiaddr, - alice_connect_peer_id: PeerId, -} - -impl BobSwapFactory { - #[allow(clippy::too_many_arguments)] - async fn new( - seed: Seed, - config: Config, - swap_id: Uuid, - monero: &Monero, - bitcoind: &Bitcoind<'_>, - starting_balances: StartingBalances, - alice_connect_address: Multiaddr, - alice_connect_peer_id: PeerId, - ) -> Self { - let db_path = tempdir().unwrap().path().to_path_buf(); - - let (bitcoin_wallet, monero_wallet) = - init_wallets("bob", bitcoind, monero, starting_balances.clone(), config).await; - - Self { - seed, - db_path, - swap_id, - bitcoin_wallet, - monero_wallet, - config, - starting_balances, - alice_connect_address, - alice_connect_peer_id, - } - } - - pub async fn new_swap_as_bob(&self, swap_amounts: SwapAmounts) -> (bob::Swap, bob::EventLoop) { - let initial_state = init_bob_state( - swap_amounts.btc, - swap_amounts.xmr, - self.bitcoin_wallet.clone(), - self.config, - ) - .await; - - let (event_loop, event_loop_handle) = init_bob_event_loop( - self.seed, - self.alice_connect_peer_id.clone(), - self.alice_connect_address.clone(), - ); - - let db = Database::open(self.db_path.as_path()).unwrap(); - - ( - bob::Swap { - state: initial_state, - event_loop_handle, - db, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - swap_id: self.swap_id, - }, - event_loop, - ) - } - - pub async fn recover_bob_from_db(&self) -> (bob::Swap, bob::EventLoop) { - // reopen the existing database - let db = Database::open(self.db_path.clone().as_path()).unwrap(); - - let resume_state = - if let swap::database::Swap::Bob(state) = db.get_state(self.swap_id).unwrap() { - state.into() - } else { - unreachable!() - }; - - let (event_loop, event_loop_handle) = init_bob_event_loop( - self.seed, - self.alice_connect_peer_id.clone(), - self.alice_connect_address.clone(), - ); - - ( - bob::Swap { - state: resume_state, - event_loop_handle, - db, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - swap_id: self.swap_id, - }, - event_loop, - ) - } -} - async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) { let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); let _ = bitcoind.init(5).await; @@ -536,51 +437,6 @@ async fn init_wallets( (btc_wallet, xmr_wallet) } -async fn init_bob_state( - btc_to_swap: bitcoin::Amount, - xmr_to_swap: monero::Amount, - bob_btc_wallet: Arc, - config: Config, -) -> BobState { - let amounts = SwapAmounts { - btc: btc_to_swap, - xmr: xmr_to_swap, - }; - - let refund_address = bob_btc_wallet.new_address().await.unwrap(); - let state0 = bob::State0::new( - &mut OsRng, - btc_to_swap, - xmr_to_swap, - config.bitcoin_cancel_timelock, - config.bitcoin_punish_timelock, - refund_address, - config.monero_finality_confirmations, - ); - - BobState::Started { state0, amounts } -} - -fn init_bob_event_loop( - seed: Seed, - alice_peer_id: PeerId, - alice_addr: Multiaddr, -) -> (bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle) { - let identity = network::Seed::new(seed).derive_libp2p_identity(); - let peer_id = identity.public().into_peer_id(); - - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(identity).unwrap(); - bob::event_loop::EventLoop::new( - bob_transport, - bob_behaviour, - peer_id, - alice_peer_id, - alice_addr, - ) - .unwrap() -} - // This is just to keep the containers alive #[allow(dead_code)] struct Containers<'a> { From 82974412b2a3e5bff807b04caaad19e294012dfb Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Tue, 19 Jan 2021 09:43:50 +1100 Subject: [PATCH 08/15] Remove roles from SwapFactory name as implied by module and cleanup --- swap/src/main.rs | 10 +++++----- swap/src/protocol/alice.rs | 18 +++++++++--------- swap/src/protocol/bob.rs | 23 +++++++++++------------ swap/tests/testutils/mod.rs | 15 +++++---------- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/swap/src/main.rs b/swap/src/main.rs index 891c7954..5c66b952 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -23,7 +23,7 @@ use swap::{ config::Config, database::Database, monero, - protocol::{alice, bob, bob::BobSwapFactory}, + protocol::{alice, bob, bob::SwapFactory}, trace::init_tracing, StartingBalances, SwapAmounts, }; @@ -82,7 +82,7 @@ async fn main() -> Result<()> { send_monero, receive_bitcoin, swap_id ); - let alice_factory = alice::AliceSwapFactory::new( + let alice_factory = alice::SwapFactory::new( seed, config, swap_id, @@ -127,7 +127,7 @@ async fn main() -> Result<()> { send_bitcoin, receive_monero, swap_id ); - let bob_factory = BobSwapFactory::new( + let bob_factory = SwapFactory::new( seed, db_path, swap_id, @@ -172,7 +172,7 @@ async fn main() -> Result<()> { ) .await?; - let alice_factory = alice::AliceSwapFactory::new( + let alice_factory = alice::SwapFactory::new( seed, config, swap_id, @@ -204,7 +204,7 @@ async fn main() -> Result<()> { ) .await?; - let bob_factory = BobSwapFactory::new( + let bob_factory = SwapFactory::new( seed, db_path, swap_id, diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 04ff737b..7d801ef1 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -50,21 +50,21 @@ pub struct Swap { pub db: Database, } -pub struct AliceSwapFactory { - listen_address: Multiaddr, +pub struct SwapFactory { + swap_id: Uuid, identity: Keypair, peer_id: PeerId, - db_path: PathBuf, - swap_id: Uuid, + config: Config, + + listen_address: Multiaddr, pub bitcoin_wallet: Arc, pub monero_wallet: Arc, - config: Config, pub starting_balances: StartingBalances, } -impl AliceSwapFactory { +impl SwapFactory { #[allow(clippy::too_many_arguments)] pub async fn new( seed: Seed, @@ -81,14 +81,14 @@ impl AliceSwapFactory { let peer_id = PeerId::from(identity.public()); Self { - listen_address, + swap_id, identity, peer_id, db_path, - swap_id, + config, + listen_address, bitcoin_wallet, monero_wallet, - config, starting_balances, } } diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 25b35f79..906eb80f 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -47,23 +47,22 @@ pub struct Swap { pub swap_id: Uuid, } -pub struct BobSwapFactory { +pub struct SwapFactory { + swap_id: Uuid, identity: Keypair, peer_id: PeerId, - db_path: PathBuf, - swap_id: Uuid, - - pub bitcoin_wallet: Arc, - pub monero_wallet: Arc, config: Config, - pub starting_balances: StartingBalances, alice_connect_address: Multiaddr, alice_connect_peer_id: PeerId, + + pub bitcoin_wallet: Arc, + pub monero_wallet: Arc, + pub starting_balances: StartingBalances, } -impl BobSwapFactory { +impl SwapFactory { #[allow(clippy::too_many_arguments)] pub fn new( seed: Seed, @@ -80,16 +79,16 @@ impl BobSwapFactory { let peer_id = identity.public().into_peer_id(); Self { + swap_id, identity, peer_id, db_path, - swap_id, - bitcoin_wallet, - monero_wallet, config, - starting_balances, alice_connect_address, alice_connect_peer_id, + bitcoin_wallet, + monero_wallet, + starting_balances, } } diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index b0938705..24cb9eb5 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -9,12 +9,7 @@ use swap::{ bitcoin, config::Config, monero, - protocol::{ - alice, - alice::{AliceState, AliceSwapFactory}, - bob, - bob::{BobState, BobSwapFactory}, - }, + protocol::{alice, alice::AliceState, bob, bob::BobState}, seed::Seed, StartingBalances, SwapAmounts, }; @@ -27,8 +22,8 @@ use uuid::Uuid; pub struct Test { swap_amounts: SwapAmounts, - alice_swap_factory: AliceSwapFactory, - bob_swap_factory: BobSwapFactory, + alice_swap_factory: alice::SwapFactory, + bob_swap_factory: bob::SwapFactory, } impl Test { @@ -344,7 +339,7 @@ where ) .await; - let alice_swap_factory = AliceSwapFactory::new( + let alice_swap_factory = alice::SwapFactory::new( Seed::random().unwrap(), config, Uuid::new_v4(), @@ -370,7 +365,7 @@ where ) .await; - let bob_swap_factory = BobSwapFactory::new( + let bob_swap_factory = bob::SwapFactory::new( Seed::random().unwrap(), tempdir().unwrap().path().to_path_buf(), Uuid::new_v4(), From 170e90ffed5743a999c6feb38ef320bf488b6241 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 20 Jan 2021 10:37:16 +1100 Subject: [PATCH 09/15] Rename do_run_until to _run_until_internal --- swap/src/protocol/alice/swap.rs | 26 +++++++++++++------------- swap/src/protocol/bob/swap.rs | 20 ++++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index f6dcb3e0..2ed2eb2f 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -72,7 +72,7 @@ pub async fn run_until( swap: alice::Swap, is_target_state: fn(&AliceState) -> bool, ) -> Result { - do_run_until( + run_until_internal( swap.state, is_target_state, swap.event_loop_handle, @@ -88,7 +88,7 @@ pub async fn run_until( // State machine driver for swap execution #[async_recursion] #[allow(clippy::too_many_arguments)] -async fn do_run_until( +async fn run_until_internal( state: AliceState, is_target_state: fn(&AliceState) -> bool, mut event_loop_handle: EventLoopHandle, @@ -116,7 +116,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -159,7 +159,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -200,7 +200,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -238,7 +238,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -276,7 +276,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - return do_run_until( + return run_until_internal( state, is_target_state, event_loop_handle, @@ -304,7 +304,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -331,7 +331,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -365,7 +365,7 @@ async fn do_run_until( db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -390,7 +390,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -445,7 +445,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -469,7 +469,7 @@ async fn do_run_until( let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index b01a129b..02efb241 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -46,7 +46,7 @@ pub async fn run_until( swap: bob::Swap, is_target_state: fn(&BobState) -> bool, ) -> Result { - do_run_until( + run_until_internal( swap.state, is_target_state, swap.event_loop_handle, @@ -62,7 +62,7 @@ pub async fn run_until( // State machine driver for swap execution #[allow(clippy::too_many_arguments)] #[async_recursion] -async fn do_run_until( +async fn run_until_internal( state: BobState, is_target_state: fn(&BobState) -> bool, mut event_loop_handle: EventLoopHandle, @@ -95,7 +95,7 @@ where let state = BobState::Negotiated(state2); let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -116,7 +116,7 @@ where let state = BobState::BtcLocked(state3); let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -181,7 +181,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -222,7 +222,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -257,7 +257,7 @@ where let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -278,7 +278,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -303,7 +303,7 @@ where db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) .await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -332,7 +332,7 @@ where let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - do_run_until( + run_until_internal( state, is_target_state, event_loop_handle, From e91987e23f2e40faa42c5c7789540fd3bc29dfae Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 20 Jan 2021 10:38:28 +1100 Subject: [PATCH 10/15] Fix rand import --- swap/src/protocol/bob/swap.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 02efb241..1eafc2b7 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Result}; use async_recursion::async_recursion; -use rand::{CryptoRng, RngCore}; +use rand::{rngs::OsRng, CryptoRng, RngCore}; use std::sync::Arc; use tokio::select; use tracing::{debug, info}; @@ -13,7 +13,6 @@ use crate::{ protocol::bob::{self, event_loop::EventLoopHandle, state::*}, ExpiredTimelocks, SwapAmounts, }; -use ecdsa_fun::fun::rand_core::OsRng; pub fn is_complete(state: &BobState) -> bool { matches!( From acfd43ee7981b6cc320169f02fca850ea6a45627 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 20 Jan 2021 10:40:40 +1100 Subject: [PATCH 11/15] Rename Test to TestContext and argument to ctx --- swap/tests/happy_path.rs | 10 +++++----- swap/tests/happy_path_restart_alice.rs | 12 ++++++------ swap/tests/happy_path_restart_bob_after_comm.rs | 12 ++++++------ swap/tests/happy_path_restart_bob_before_comm.rs | 12 ++++++------ swap/tests/punish.rs | 12 ++++++------ swap/tests/refund_restart_alice.rs | 12 ++++++------ swap/tests/testutils/mod.rs | 8 ++++---- 7 files changed, 39 insertions(+), 39 deletions(-) diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index d3385229..700c5d05 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -7,17 +7,17 @@ pub mod testutils; #[tokio::test] async fn happy_path() { - testutils::setup_test(|test| async move { - let alice_swap = test.new_swap_as_alice().await; - let bob_swap = test.new_swap_as_bob().await; + testutils::setup_test(|ctx| async move { + let alice_swap = ctx.new_swap_as_alice().await; + let bob_swap = ctx.new_swap_as_bob().await; let alice = alice::run(alice_swap); let bob = bob::run(bob_swap); let (alice_state, bob_state) = join!(alice, bob); - test.assert_alice_redeemed(alice_state.unwrap()).await; - test.assert_bob_redeemed(bob_state.unwrap()).await; + ctx.assert_alice_redeemed(alice_state.unwrap()).await; + ctx.assert_bob_redeemed(bob_state.unwrap()).await; }) .await; } diff --git a/swap/tests/happy_path_restart_alice.rs b/swap/tests/happy_path_restart_alice.rs index 6cce546b..dcd39f8e 100644 --- a/swap/tests/happy_path_restart_alice.rs +++ b/swap/tests/happy_path_restart_alice.rs @@ -4,9 +4,9 @@ pub mod testutils; #[tokio::test] async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { - testutils::setup_test(|test| async move { - let alice_swap = test.new_swap_as_alice().await; - let bob_swap = test.new_swap_as_bob().await; + testutils::setup_test(|ctx| async move { + let alice_swap = ctx.new_swap_as_alice().await; + let bob_swap = ctx.new_swap_as_bob().await; let bob = bob::run(bob_swap); let bob_handle = tokio::spawn(bob); @@ -16,15 +16,15 @@ async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { .unwrap(); assert!(matches!(alice_state, AliceState::EncSigLearned {..})); - let alice_swap = test.recover_alice_from_db().await; + let alice_swap = ctx.recover_alice_from_db().await; assert!(matches!(alice_swap.state, AliceState::EncSigLearned {..})); let alice_state = alice::run(alice_swap).await.unwrap(); - test.assert_alice_redeemed(alice_state).await; + ctx.assert_alice_redeemed(alice_state).await; let bob_state = bob_handle.await.unwrap(); - test.assert_bob_redeemed(bob_state.unwrap()).await + ctx.assert_bob_redeemed(bob_state.unwrap()).await }) .await; } diff --git a/swap/tests/happy_path_restart_bob_after_comm.rs b/swap/tests/happy_path_restart_bob_after_comm.rs index 194b6349..a1c04034 100644 --- a/swap/tests/happy_path_restart_bob_after_comm.rs +++ b/swap/tests/happy_path_restart_bob_after_comm.rs @@ -4,9 +4,9 @@ pub mod testutils; #[tokio::test] async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { - testutils::setup_test(|test| async move { - let alice_swap = test.new_swap_as_alice().await; - let bob_swap = test.new_swap_as_bob().await; + testutils::setup_test(|ctx| async move { + let alice_swap = ctx.new_swap_as_alice().await; + let bob_swap = ctx.new_swap_as_bob().await; let alice = alice::run(alice_swap); let alice_handle = tokio::spawn(alice); @@ -17,15 +17,15 @@ async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { assert!(matches!(bob_state, BobState::EncSigSent {..})); - let bob_swap = test.recover_bob_from_db().await; + let bob_swap = ctx.recover_bob_from_db().await; assert!(matches!(bob_swap.state, BobState::EncSigSent {..})); let bob_state = bob::run(bob_swap).await.unwrap(); - test.assert_bob_redeemed(bob_state).await; + ctx.assert_bob_redeemed(bob_state).await; let alice_state = alice_handle.await.unwrap(); - test.assert_alice_redeemed(alice_state.unwrap()).await; + ctx.assert_alice_redeemed(alice_state.unwrap()).await; }) .await; } diff --git a/swap/tests/happy_path_restart_bob_before_comm.rs b/swap/tests/happy_path_restart_bob_before_comm.rs index aea07c52..231ed1e5 100644 --- a/swap/tests/happy_path_restart_bob_before_comm.rs +++ b/swap/tests/happy_path_restart_bob_before_comm.rs @@ -7,9 +7,9 @@ pub mod testutils; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { - testutils::setup_test(|test| async move { - let alice_swap = test.new_swap_as_alice().await; - let bob_swap = test.new_swap_as_bob().await; + testutils::setup_test(|ctx| async move { + let alice_swap = ctx.new_swap_as_alice().await; + let bob_swap = ctx.new_swap_as_bob().await; let alice_handle = alice::run(alice_swap); let alice_swap_handle = tokio::spawn(alice_handle); @@ -18,15 +18,15 @@ async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { assert!(matches!(bob_state, BobState::XmrLocked {..})); - let bob_swap = test.recover_bob_from_db().await; + let bob_swap = ctx.recover_bob_from_db().await; assert!(matches!(bob_swap.state, BobState::XmrLocked {..})); let bob_state = bob::run(bob_swap).await.unwrap(); - test.assert_bob_redeemed(bob_state).await; + ctx.assert_bob_redeemed(bob_state).await; let alice_state = alice_swap_handle.await.unwrap(); - test.assert_alice_redeemed(alice_state.unwrap()).await; + ctx.assert_alice_redeemed(alice_state.unwrap()).await; }) .await; } diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs index aa4524d4..9aea25bd 100644 --- a/swap/tests/punish.rs +++ b/swap/tests/punish.rs @@ -9,9 +9,9 @@ pub mod testutils; /// the encsig and fail to refund or redeem. Alice punishes. #[tokio::test] async fn alice_punishes_if_bob_never_acts_after_fund() { - testutils::setup_test(|test| async move { - let alice_swap = test.new_swap_as_alice().await; - let bob_swap = test.new_swap_as_bob().await; + testutils::setup_test(|ctx| async move { + let alice_swap = ctx.new_swap_as_alice().await; + let bob_swap = ctx.new_swap_as_bob().await; let alice = alice::run(alice_swap); let alice_handle = tokio::spawn(alice); @@ -21,16 +21,16 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { assert!(matches!(bob_state, BobState::BtcLocked {..})); let alice_state = alice_handle.await.unwrap(); - test.assert_alice_punished(alice_state.unwrap()).await; + ctx.assert_alice_punished(alice_state.unwrap()).await; // Restart Bob after Alice punished to ensure Bob transitions to // punished and does not run indefinitely - let bob_swap = test.recover_bob_from_db().await; + let bob_swap = ctx.recover_bob_from_db().await; assert!(matches!(bob_swap.state, BobState::BtcLocked {..})); let bob_state = bob::run(bob_swap).await.unwrap(); - test.assert_bob_punished(bob_state).await; + ctx.assert_bob_punished(bob_state).await; }) .await; } diff --git a/swap/tests/refund_restart_alice.rs b/swap/tests/refund_restart_alice.rs index 48bc09e0..111d1cfa 100644 --- a/swap/tests/refund_restart_alice.rs +++ b/swap/tests/refund_restart_alice.rs @@ -6,9 +6,9 @@ pub mod testutils; /// then also refunds. #[tokio::test] async fn given_alice_restarts_after_xmr_is_locked_abort_swap() { - testutils::setup_test(|test| async move { - let alice_swap = test.new_swap_as_alice().await; - let bob_swap = test.new_swap_as_bob().await; + testutils::setup_test(|ctx| async move { + let alice_swap = ctx.new_swap_as_alice().await; + let bob_swap = ctx.new_swap_as_bob().await; let bob = bob::run(bob_swap); let bob_handle = tokio::spawn(bob); @@ -22,7 +22,7 @@ async fn given_alice_restarts_after_xmr_is_locked_abort_swap() { let bob_state = bob_handle.await.unwrap(); // Once bob has finished Alice is restarted and refunds as well - let alice_swap = test.recover_alice_from_db().await; + let alice_swap = ctx.recover_alice_from_db().await; assert!(matches!(alice_swap.state, AliceState::XmrLocked {..})); let alice_state = alice::run(alice_swap).await.unwrap(); @@ -31,8 +31,8 @@ async fn given_alice_restarts_after_xmr_is_locked_abort_swap() { // refunded, not at the end because this can cause side-effects! // We have to properly wait for the refund tx's finality inside the assertion, // which requires storing the refund_tx_id in the the state! - test.assert_bob_refunded(bob_state.unwrap()).await; - test.assert_alice_refunded(alice_state).await; + ctx.assert_bob_refunded(bob_state.unwrap()).await; + ctx.assert_alice_refunded(alice_state).await; }) .await; } diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 24cb9eb5..7466faee 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -19,14 +19,14 @@ use tracing_core::dispatcher::DefaultGuard; use tracing_log::LogTracer; use uuid::Uuid; -pub struct Test { +pub struct TestContext { swap_amounts: SwapAmounts, alice_swap_factory: alice::SwapFactory, bob_swap_factory: bob::SwapFactory, } -impl Test { +impl TestContext { pub async fn new_swap_as_alice(&self) -> alice::Swap { let (swap, mut event_loop) = self .alice_swap_factory @@ -303,7 +303,7 @@ impl Test { pub async fn setup_test(testfn: T) where - T: Fn(Test) -> F, + T: Fn(TestContext) -> F, F: Future, { let cli = Cli::default(); @@ -377,7 +377,7 @@ where alice_swap_factory.peer_id(), ); - let test = Test { + let test = TestContext { swap_amounts, alice_swap_factory, bob_swap_factory, From 2dceab28a4eb30a442a70b457d02dd1a2de76453 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 20 Jan 2021 10:44:24 +1100 Subject: [PATCH 12/15] Derive Default --- swap/src/protocol/alice.rs | 15 +-------------- swap/src/protocol/bob.rs | 15 +-------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index 7d801ef1..e9941172 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -280,7 +280,7 @@ impl From for OutEvent { } /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice. -#[derive(NetworkBehaviour)] +#[derive(NetworkBehaviour, Default)] #[behaviour(out_event = "OutEvent", event_process = false)] #[allow(missing_debug_implementations)] pub struct Behaviour { @@ -318,16 +318,3 @@ impl Behaviour { debug!("Sent Message2"); } } - -impl Default for Behaviour { - fn default() -> Self { - Self { - pt: PeerTracker::default(), - amounts: Amounts::default(), - message0: message0::Behaviour::default(), - message1: message1::Behaviour::default(), - message2: message2::Behaviour::default(), - message3: message3::Behaviour::default(), - } - } -} diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 906eb80f..5890f5b4 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -261,7 +261,7 @@ impl From for OutEvent { } /// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. -#[derive(NetworkBehaviour)] +#[derive(NetworkBehaviour, Default)] #[behaviour(out_event = "OutEvent", event_process = false)] #[allow(missing_debug_implementations)] pub struct Behaviour { @@ -311,16 +311,3 @@ impl Behaviour { self.pt.add_address(peer_id, address) } } - -impl Default for Behaviour { - fn default() -> Behaviour { - Self { - pt: PeerTracker::default(), - amounts: Amounts::default(), - message0: message0::Behaviour::default(), - message1: message1::Behaviour::default(), - message2: message2::Behaviour::default(), - message3: message3::Behaviour::default(), - } - } -} From 41e335fc2de9e10e4801b622fe22bbe025c192c9 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 20 Jan 2021 10:50:25 +1100 Subject: [PATCH 13/15] Error messages instead of unreachable Currently this code is actually not reachable, but that is semantically applied by the program's flow (the resume command includes the swap direction). It is still preferred to have an error message rather than an unreachable statement. --- swap/src/protocol/alice.rs | 7 +++++-- swap/src/protocol/bob.rs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index e9941172..eb0b720a 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -1,6 +1,6 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. -use anyhow::Result; +use anyhow::{bail, Result}; use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId}; use tracing::{debug, info}; @@ -131,7 +131,10 @@ impl SwapFactory { let resume_state = if let database::Swap::Alice(state) = db.get_state(self.swap_id)? { state.into() } else { - unreachable!() + bail!( + "Trying to load swap with id {} for the wrong direction.", + self.swap_id + ) }; let (event_loop, event_loop_handle) = init_alice_event_loop( diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 5890f5b4..4ec70fba 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,6 +1,6 @@ //! Run an XMR/BTC swap in the role of Bob. //! Bob holds BTC and wishes receive XMR. -use anyhow::Result; +use anyhow::{bail, Result}; use libp2p::{core::Multiaddr, NetworkBehaviour, PeerId}; use tracing::{debug, info}; @@ -133,7 +133,10 @@ impl SwapFactory { let resume_state = if let database::Swap::Bob(state) = db.get_state(self.swap_id)? { state.into() } else { - unreachable!() + bail!( + "Trying to load swap with id {} for the wrong direction.", + self.swap_id + ) }; let (event_loop, event_loop_handle) = init_bob_event_loop( From 37f619dbfccb20acde96dee07547700eb413da8c Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 20 Jan 2021 13:29:46 +1100 Subject: [PATCH 14/15] Move StartingBalances into protocol module --- swap/src/lib.rs | 6 ------ swap/src/main.rs | 4 ++-- swap/src/protocol.rs | 6 ++++++ swap/src/protocol/alice.rs | 7 +++++-- swap/src/protocol/bob.rs | 7 +++++-- swap/tests/testutils/mod.rs | 4 ++-- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 7644da0a..f7020277 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -56,12 +56,6 @@ pub struct SwapAmounts { pub xmr: monero::Amount, } -#[derive(Debug, Clone)] -pub struct StartingBalances { - pub xmr: monero::Amount, - pub btc: bitcoin::Amount, -} - // TODO: Display in XMR and BTC (not picos and sats). impl Display for SwapAmounts { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/swap/src/main.rs b/swap/src/main.rs index 5c66b952..18beda09 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -23,9 +23,9 @@ use swap::{ config::Config, database::Database, monero, - protocol::{alice, bob, bob::SwapFactory}, + protocol::{alice, bob, bob::SwapFactory, StartingBalances}, trace::init_tracing, - StartingBalances, SwapAmounts, + SwapAmounts, }; use tracing::{info, log::LevelFilter}; use uuid::Uuid; diff --git a/swap/src/protocol.rs b/swap/src/protocol.rs index 9de27854..a2e13b79 100644 --- a/swap/src/protocol.rs +++ b/swap/src/protocol.rs @@ -1,2 +1,8 @@ pub mod alice; pub mod bob; + +#[derive(Debug, Clone)] +pub struct StartingBalances { + pub xmr: crate::monero::Amount, + pub btc: bitcoin::Amount, +} diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index eb0b720a..3b07005b 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -12,7 +12,7 @@ use crate::{ Seed as NetworkSeed, }, protocol::bob, - StartingBalances, SwapAmounts, + SwapAmounts, }; pub use self::{ @@ -24,7 +24,10 @@ pub use self::{ state::*, swap::{run, run_until}, }; -use crate::{config::Config, database::Database, network::transport::build, seed::Seed}; +use crate::{ + config::Config, database::Database, network::transport::build, protocol::StartingBalances, + seed::Seed, +}; use libp2p::{core::Multiaddr, identity::Keypair}; use rand::rngs::OsRng; use std::{path::PathBuf, sync::Arc}; diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 4ec70fba..f9ac84a2 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -10,7 +10,7 @@ use crate::{ database, monero, network, network::peer_tracker::{self, PeerTracker}, protocol::{alice, bob}, - StartingBalances, SwapAmounts, + SwapAmounts, }; pub use self::{ @@ -23,7 +23,10 @@ pub use self::{ state::*, swap::{run, run_until}, }; -use crate::{config::Config, database::Database, network::transport::build, seed::Seed}; +use crate::{ + config::Config, database::Database, network::transport::build, protocol::StartingBalances, + seed::Seed, +}; use libp2p::identity::Keypair; use rand::rngs::OsRng; use std::{path::PathBuf, sync::Arc}; diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 7466faee..021b8bd9 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -9,9 +9,9 @@ use swap::{ bitcoin, config::Config, monero, - protocol::{alice, alice::AliceState, bob, bob::BobState}, + protocol::{alice, alice::AliceState, bob, bob::BobState, StartingBalances}, seed::Seed, - StartingBalances, SwapAmounts, + SwapAmounts, }; use tempfile::tempdir; use testcontainers::{clients::Cli, Container}; From 28429fa9fe52c069010d3426ef364661de4e863e Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 20 Jan 2021 13:30:35 +1100 Subject: [PATCH 15/15] Revert log statement --- swap/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swap/src/main.rs b/swap/src/main.rs index 18beda09..0c70d67e 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -78,7 +78,7 @@ async fn main() -> Result<()> { let swap_id = Uuid::new_v4(); info!( - "Swap sending {} and receiving {} starting with ID {}", + "Swap sending {} and receiving {} started with ID {}", send_monero, receive_bitcoin, swap_id ); @@ -123,7 +123,7 @@ async fn main() -> Result<()> { let swap_id = Uuid::new_v4(); info!( - "Swap sending {} and receiving {} starting with ID {}", + "Swap sending {} and receiving {} started with ID {}", send_bitcoin, receive_monero, swap_id );