From 152c8d7eba597b1c1e2e6378ea343864d7ec5e2f Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Fri, 15 Jan 2021 12:44:15 +1100 Subject: [PATCH] Refactor Alice restart test by introducing factory for creating Alices The factory keeps all static state of Alice to be able to simulate a restart. --- swap/tests/happy_path.rs | 11 +- swap/tests/happy_path_restart_alice.rs | 200 +++++++---------------- swap/tests/testutils/mod.rs | 211 +++++++++++++++++++------ 3 files changed, 222 insertions(+), 200 deletions(-) diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 200c1fbe..10ca6ec0 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -11,12 +11,13 @@ pub mod testutils; #[tokio::test] async fn happy_path() { - testutils::test(|alice, bob, swap_amounts| async move { + testutils::test(|alice_factory, bob, swap_amounts| async move { + let alice = alice_factory.new_alice().await; let alice_swap_fut = alice::swap( alice.state, alice.event_loop_handle, - alice.bitcoin_wallet.clone(), - alice.monero_wallet.clone(), + alice.btc_wallet.clone(), + alice.xmr_wallet.clone(), alice.config, alice.swap_id, alice.db, @@ -32,10 +33,10 @@ async fn happy_path() { ); let (alice_state, bob_state) = join!(alice_swap_fut, bob_swap_fut); - let btc_alice_final = alice.bitcoin_wallet.as_ref().balance().await.unwrap(); + let btc_alice_final = alice.btc_wallet.as_ref().balance().await.unwrap(); let btc_bob_final = bob.bitcoin_wallet.as_ref().balance().await.unwrap(); - let xmr_alice_final = alice.monero_wallet.as_ref().get_balance().await.unwrap(); + let xmr_alice_final = alice.xmr_wallet.as_ref().get_balance().await.unwrap(); bob.monero_wallet.as_ref().inner.refresh().await.unwrap(); let xmr_bob_final = bob.monero_wallet.as_ref().get_balance().await.unwrap(); diff --git a/swap/tests/happy_path_restart_alice.rs b/swap/tests/happy_path_restart_alice.rs index 5c615227..fd0cb7fe 100644 --- a/swap/tests/happy_path_restart_alice.rs +++ b/swap/tests/happy_path_restart_alice.rs @@ -1,166 +1,78 @@ -use crate::testutils::{init_alice, init_bob}; -use get_port::get_port; -use libp2p::Multiaddr; use rand::rngs::OsRng; use swap::{ bitcoin, - config::Config, - database::Database, - monero, - protocol::{alice, alice::AliceState, bob}, - seed::Seed, + protocol::{alice, alice::AliceState, bob, bob::BobState}, }; -use tempfile::tempdir; -use testcontainers::clients::Cli; -use testutils::init_tracing; -use uuid::Uuid; pub mod testutils; #[tokio::test] async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { - let _guard = init_tracing(); + testutils::test(|alice_factory, bob, swap_amounts| async move { + let alice = alice_factory.new_alice().await; - let cli = Cli::default(); - let ( - monero, - testutils::Containers { - bitcoind, - monerods: _monerods, - }, - ) = testutils::init_containers(&cli).await; + let bob_swap_fut = 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_fut); - let btc_to_swap = bitcoin::Amount::from_sat(1_000_000); - let xmr_to_swap = monero::Amount::from_piconero(1_000_000_000_000); - - let bob_btc_starting_balance = btc_to_swap * 10; - let alice_xmr_starting_balance = xmr_to_swap * 10; - - let port = get_port().expect("Failed to find a free port"); - let alice_multiaddr: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port) - .parse() - .expect("failed to parse Alice's address"); - - let config = Config::regtest(); - - let alice_seed = Seed::random().unwrap(); - let ( - start_state, - mut alice_event_loop, - alice_event_loop_handle, - alice_btc_wallet, - alice_xmr_wallet, - _, - ) = init_alice( - &bitcoind, - &monero, - btc_to_swap, - xmr_to_swap, - alice_xmr_starting_balance, - alice_multiaddr.clone(), - config, - alice_seed, - ) - .await; - - let alice_peer_id = alice_event_loop.peer_id(); - - let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) = - init_bob( - alice_multiaddr.clone(), - alice_peer_id.clone(), - &bitcoind, - &monero, - btc_to_swap, - bob_btc_starting_balance, - xmr_to_swap, - config, + let alice_state = alice::run_until( + alice.state, + alice::swap::is_encsig_learned, + alice.event_loop_handle, + alice.btc_wallet.clone(), + alice.xmr_wallet.clone(), + alice.config, + alice.swap_id, + alice.db, ) - .await; + .await + .unwrap(); + assert!(matches!(alice_state, AliceState::EncSigLearned {..})); - // TODO: we are making a clone of Bob's wallets here to keep them in scope after - // Bob's wallets are moved into an async task. - let bob_btc_wallet_clone = bob_btc_wallet.clone(); - let bob_xmr_wallet_clone = bob_xmr_wallet.clone(); + let alice = alice_factory.recover_alice_from_db().await; + assert!(matches!(alice.state, AliceState::EncSigLearned {..})); - let bob_fut = bob::swap::swap( - bob_state, - bob_event_loop_handle, - bob_db, - bob_btc_wallet.clone(), - bob_xmr_wallet.clone(), - OsRng, - Uuid::new_v4(), - ); + let alice_state = alice::swap( + alice.state, + alice.event_loop_handle, + alice.btc_wallet.clone(), + alice.xmr_wallet.clone(), + alice.config, + alice.swap_id, + alice.db, + ) + .await + .unwrap(); - let alice_db_datadir = tempdir().unwrap(); - let alice_db = Database::open(alice_db_datadir.path()).unwrap(); + let bob_state = bob_swap_handle.await.unwrap(); - tokio::spawn(async move { alice_event_loop.run().await }); - let bob_swap_handle = tokio::spawn(bob_fut); - tokio::spawn(bob_event_loop.run()); + let btc_alice_final = alice.btc_wallet.as_ref().balance().await.unwrap(); + let btc_bob_final = bob.bitcoin_wallet.as_ref().balance().await.unwrap(); - let alice_swap_id = Uuid::new_v4(); + let xmr_alice_final = alice.xmr_wallet.as_ref().get_balance().await.unwrap(); - let alice_state = alice::swap::run_until( - start_state, - alice::swap::is_encsig_learned, - alice_event_loop_handle, - alice_btc_wallet.clone(), - alice_xmr_wallet.clone(), - config, - alice_swap_id, - alice_db, - ) - .await - .unwrap(); + bob.monero_wallet.as_ref().inner.refresh().await.unwrap(); + let xmr_bob_final = bob.monero_wallet.as_ref().get_balance().await.unwrap(); - assert!(matches!(alice_state, AliceState::EncSigLearned {..})); + assert!(matches!(alice_state, AliceState::BtcRedeemed)); + assert!(matches!(bob_state.unwrap(), BobState::XmrRedeemed)); - let alice_db = Database::open(alice_db_datadir.path()).unwrap(); + assert_eq!( + btc_alice_final, + alice.btc_starting_balance + swap_amounts.btc + - bitcoin::Amount::from_sat(bitcoin::TX_FEE) + ); + assert!(btc_bob_final <= bob.btc_starting_balance - swap_amounts.btc); - let resume_state = - if let swap::database::Swap::Alice(state) = alice_db.get_state(alice_swap_id).unwrap() { - assert!(matches!(state, swap::database::Alice::EncSigLearned {..})); - state.into() - } else { - unreachable!() - }; - - let (mut event_loop_after_restart, event_loop_handle_after_restart) = - testutils::init_alice_event_loop(alice_multiaddr, alice_seed); - tokio::spawn(async move { event_loop_after_restart.run().await }); - - let alice_state = alice::swap::swap( - resume_state, - event_loop_handle_after_restart, - alice_btc_wallet.clone(), - alice_xmr_wallet.clone(), - config, - alice_swap_id, - alice_db, - ) - .await - .unwrap(); - - // Wait for Bob to finish - bob_swap_handle.await.unwrap().unwrap(); - - assert!(matches!(alice_state, AliceState::BtcRedeemed {..})); - - let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap(); - let btc_bob_final = bob_btc_wallet_clone.as_ref().balance().await.unwrap(); - - assert_eq!( - btc_alice_final, - btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE) - ); - assert!(btc_bob_final <= bob_btc_starting_balance - btc_to_swap); - - let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); - bob_xmr_wallet_clone.as_ref().inner.refresh().await.unwrap(); - let xmr_bob_final = bob_xmr_wallet_clone.as_ref().get_balance().await.unwrap(); - - assert!(xmr_alice_final <= alice_xmr_starting_balance - xmr_to_swap); - assert_eq!(xmr_bob_final, xmr_to_swap); + assert!(xmr_alice_final <= alice.xmr_starting_balance - swap_amounts.xmr); + assert_eq!(xmr_bob_final, bob.xmr_starting_balance + swap_amounts.xmr); + }) + .await; } diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 9fb9b20b..3a5186ad 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -5,7 +5,7 @@ use get_port::get_port; use libp2p::{core::Multiaddr, PeerId}; use monero_harness::{image, Monero}; use rand::rngs::OsRng; -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use swap::{ bitcoin, config::Config, @@ -23,15 +23,19 @@ use tracing_log::LogTracer; use uuid::Uuid; pub struct Alice { - pub state: AliceState, pub event_loop_handle: alice::EventLoopHandle, - pub bitcoin_wallet: Arc, - pub monero_wallet: Arc, + pub btc_wallet: Arc, + pub xmr_wallet: Arc, pub config: Config, - pub swap_id: Uuid, pub db: Database, + + pub state: AliceState, + pub xmr_starting_balance: monero::Amount, pub btc_starting_balance: bitcoin::Amount, + + // test context (state we have to keep to simulate restart) + pub swap_id: Uuid, } pub struct Bob { @@ -45,9 +49,149 @@ pub struct Bob { pub xmr_starting_balance: monero::Amount, } +pub struct AliceFactory { + listen_address: Multiaddr, + peer_id: PeerId, + + seed: Seed, + db_path: PathBuf, + swap_id: Uuid, + + // Stuff that should probably not be in here... + swap_amounts: SwapAmounts, + btc_wallet: Arc, + xmr_wallet: Arc, + config: Config, + xmr_starting_balance: monero::Amount, + btc_starting_balance: bitcoin::Amount, +} + +impl AliceFactory { + pub fn peer_id(&self) -> PeerId { + self.peer_id.clone() + } + + pub fn listen_address(&self) -> Multiaddr { + self.listen_address.clone() + } + + pub async fn new( + config: Config, + swap_amounts: SwapAmounts, + swap_id: Uuid, + monero: &Monero, + bitcoind: &Bitcoind<'_>, + xmr_starting_balance: monero::Amount, + btc_starting_balance: bitcoin::Amount, + ) -> 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 alice_xmr_starting_balance = swap_amounts.xmr * 10; + let (btc_wallet, xmr_wallet) = init_wallets( + "alice", + bitcoind, + monero, + None, + Some(alice_xmr_starting_balance), + 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, + swap_amounts, + btc_wallet, + xmr_wallet, + config, + xmr_starting_balance, + btc_starting_balance, + } + } + + pub async fn new_alice(&self) -> Alice { + let initial_state = init_alice_state( + self.swap_amounts.btc, + self.swap_amounts.xmr, + self.btc_wallet.clone(), + self.config, + ) + .await; + + let (mut 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, + btc_wallet: self.btc_wallet.clone(), + xmr_wallet: self.xmr_wallet.clone(), + config: self.config, + db, + state: initial_state, + xmr_starting_balance: self.xmr_starting_balance, + btc_starting_balance: self.btc_starting_balance, + swap_id: self.swap_id, + } + } + + 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) + + // 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 (mut 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, + btc_wallet: self.btc_wallet.clone(), + xmr_wallet: self.xmr_wallet.clone(), + config: self.config, + swap_id: self.swap_id, + db, + xmr_starting_balance: self.xmr_starting_balance, + btc_starting_balance: self.btc_starting_balance, + } + } +} + pub async fn test(testfn: T) where - T: Fn(Alice, Bob, SwapAmounts) -> F, + T: Fn(AliceFactory, Bob, SwapAmounts) -> F, F: Future, { let cli = Cli::default(); @@ -61,42 +205,20 @@ where xmr: monero::Amount::from_piconero(1_000_000_000_000), }; - let bob_btc_starting_balance = swap_amounts.btc * 10; - let alice_xmr_starting_balance = swap_amounts.xmr * 10; - - let port = get_port().expect("Failed to find a free port"); - - let alice_multiaddr: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port) - .parse() - .expect("failed to parse Alice's address"); - let config = Config::regtest(); - let alice_seed = Seed::random().unwrap(); - - let (alice_btc_wallet, alice_xmr_wallet) = init_wallets( - "alice", - &containers.bitcoind, + let alice_factory = AliceFactory::new( + config, + swap_amounts, + Uuid::new_v4(), &monero, - None, - Some(alice_xmr_starting_balance), - config, + &containers.bitcoind, + swap_amounts.xmr * 10, + bitcoin::Amount::ZERO, ) .await; - let alice_state = init_alice_state( - swap_amounts.btc, - swap_amounts.xmr, - alice_btc_wallet.clone(), - config, - ) - .await; - - let (mut alice_event_loop, alice_event_loop_handle) = - init_alice_event_loop(alice_multiaddr.clone(), alice_seed); - - let alice_db_datadir = tempdir().unwrap(); - let alice_db = Database::open(alice_db_datadir.path()).unwrap(); + let bob_btc_starting_balance = swap_amounts.btc * 10; let (bob_btc_wallet, bob_xmr_wallet) = init_wallets( "bob", @@ -117,26 +239,13 @@ where .await; let (bob_event_loop, bob_event_loop_handle) = - init_bob_event_loop(alice_event_loop.peer_id(), alice_multiaddr); + init_bob_event_loop(alice_factory.peer_id(), alice_factory.listen_address()); let bob_db_dir = tempdir().unwrap(); let bob_db = Database::open(bob_db_dir.path()).unwrap(); - tokio::spawn(async move { alice_event_loop.run().await }); tokio::spawn(async move { bob_event_loop.run().await }); - let alice = Alice { - state: alice_state, - event_loop_handle: alice_event_loop_handle, - bitcoin_wallet: alice_btc_wallet, - monero_wallet: alice_xmr_wallet, - config, - swap_id: Uuid::new_v4(), - db: alice_db, - xmr_starting_balance: alice_xmr_starting_balance, - btc_starting_balance: bitcoin::Amount::ZERO, - }; - let bob = Bob { state: bob_state, event_loop_handle: bob_event_loop_handle, @@ -148,7 +257,7 @@ where btc_starting_balance: bob_btc_starting_balance, }; - testfn(alice, bob, swap_amounts).await + testfn(alice_factory, bob, swap_amounts).await } pub async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {