diff --git a/swap/src/alice/swap.rs b/swap/src/alice/swap.rs index 85251d27..6389b6bd 100644 --- a/swap/src/alice/swap.rs +++ b/swap/src/alice/swap.rs @@ -259,6 +259,13 @@ pub fn is_xmr_locked(state: &AliceState) -> bool { ) } +pub fn is_encsig_learned(state: &AliceState) -> bool { + matches!( + state, + AliceState::EncSignLearned{..} + ) +} + // State machine driver for swap execution #[async_recursion] #[allow(clippy::too_many_arguments)] diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 6ab3e4e7..9cbca0cf 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -118,7 +118,10 @@ async fn main() -> Result<()> { alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?; let swap_id = Uuid::new_v4(); - info!("Swap id: {}", swap_id); + info!( + "Swap sending {} and receiving {} started with ID {}", + send_monero, receive_bitcoin, swap_id + ); let swap = alice::swap::swap( alice_state, @@ -135,7 +138,6 @@ async fn main() -> Result<()> { } Options::BuyXmr { alice_addr, - alice_peer_id: _, bitcoind_url, bitcoin_wallet_name, monero_wallet_rpc_url, @@ -193,6 +195,12 @@ async fn main() -> Result<()> { let (event_loop, handle) = bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap(); + let swap_id = Uuid::new_v4(); + info!( + "Swap sending {} and receiving {} started with ID {}", + send_bitcoin, receive_monero, swap_id + ); + let swap = bob::swap::swap( bob_state, handle, @@ -200,7 +208,7 @@ async fn main() -> Result<()> { bitcoin_wallet.clone(), monero_wallet.clone(), OsRng, - Uuid::new_v4(), + swap_id, ); let _event_loop = tokio::spawn(async move { event_loop.run().await }); diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 5005bd50..a3ec61c7 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -1,4 +1,4 @@ -use libp2p::{core::Multiaddr, PeerId}; +use libp2p::core::Multiaddr; use url::Url; use uuid::Uuid; @@ -40,9 +40,6 @@ pub enum Options { #[structopt(short = "a", long = "connect-addr")] alice_addr: Multiaddr, - #[structopt(short = "p", long = "connect-peer-id")] - alice_peer_id: PeerId, - #[structopt( short = "b", long = "bitcoind", diff --git a/swap/tests/alice_safe_restart.rs b/swap/tests/alice_safe_restart.rs new file mode 100644 index 00000000..7a84b285 --- /dev/null +++ b/swap/tests/alice_safe_restart.rs @@ -0,0 +1,324 @@ +use bitcoin_harness::Bitcoind; +use libp2p::Multiaddr; +use monero_harness::Monero; +use rand::rngs::OsRng; +use swap::{alice, alice::swap::AliceState, bitcoin, bob, storage::Database}; +use tempfile::tempdir; +use testcontainers::clients::Cli; +use uuid::Uuid; +use xmr_btc::config::Config; + +pub mod testutils; + +use crate::testutils::{init_alice, init_bob}; +use std::convert::TryFrom; +use testutils::init_tracing; + +#[tokio::test] +async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { + let _guard = init_tracing(); + + let cli = Cli::default(); + let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); + let _ = bitcoind.init(5).await; + let (monero, _container) = + Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) + .await + .unwrap(); + + let btc_to_swap = bitcoin::Amount::from_sat(1_000_000); + let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000); + + let bob_btc_starting_balance = btc_to_swap * 10; + let bob_xmr_starting_balance = xmr_btc::monero::Amount::ZERO; + + let alice_btc_starting_balance = bitcoin::Amount::ZERO; + let alice_xmr_starting_balance = xmr_to_swap * 10; + + let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877" + .parse() + .expect("failed to parse Alice's address"); + + let config = Config::regtest(); + + let ( + alice_state, + mut alice_event_loop, + alice_event_loop_handle, + alice_btc_wallet, + alice_xmr_wallet, + ) = init_alice( + &bitcoind, + &monero, + btc_to_swap, + alice_btc_starting_balance, + xmr_to_swap, + alice_xmr_starting_balance, + alice_multiaddr.clone(), + config, + ) + .await; + + let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) = + init_bob( + alice_multiaddr, + &bitcoind, + &monero, + btc_to_swap, + bob_btc_starting_balance, + xmr_to_swap, + bob_xmr_starting_balance, + config, + ) + .await; + + let _ = tokio::spawn(async move { + bob::swap::swap( + bob_state, + bob_event_loop_handle, + bob_db, + bob_btc_wallet.clone(), + bob_xmr_wallet.clone(), + OsRng, + Uuid::new_v4(), + ) + .await + }); + + let _bob_swarm_fut = tokio::spawn(async move { bob_event_loop.run().await }); + + let alice_db_datadir = tempdir().unwrap(); + let alice_db = Database::open(alice_db_datadir.path()).unwrap(); + + let _alice_swarm_fut = tokio::spawn(async move { alice_event_loop.run().await }); + + let alice_swap_id = Uuid::new_v4(); + + let (alice_state, alice_event_loop_handle) = alice::swap::run_until( + alice_state, + alice::swap::is_encsig_learned, + alice_event_loop_handle, + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + Config::regtest(), + alice_swap_id, + alice_db, + ) + .await + .unwrap(); + + assert!(matches!(alice_state, AliceState::EncSignLearned {..})); + + // todo: add db code here + let alice_db = Database::open(alice_db_datadir.path()).unwrap(); + let alice_state = alice_db.get_state(alice_swap_id).unwrap(); + + let (alice_state, _) = alice::swap::swap( + AliceState::try_from(alice_state).unwrap(), + alice_event_loop_handle, + alice_btc_wallet.clone(), + alice_xmr_wallet.clone(), + Config::regtest(), + Uuid::new_v4(), + alice_db, + ) + .await + .unwrap(); + + assert!(matches!(alice_state, AliceState::BtcRedeemed {..})); +} +// #[tokio::test] +// async fn given_alice_restarts_after_xmr_is_locked_refund_swap() { +// setup_tracing(); +// +// let config = Config::regtest(); +// +// let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" +// .parse() +// .expect("failed to parse Alice's address"); +// +// let btc_to_swap = bitcoin::Amount::from_sat(1_000_000); +// let init_btc_alice = bitcoin::Amount::ZERO; +// let init_btc_bob = btc_to_swap * 10; +// +// let xmr_to_swap = monero::Amount::from_piconero(1_000_000_000_000); +// let init_xmr_alice = xmr_to_swap * 10; +// let init_xmr_bob = monero::Amount::ZERO; +// +// let cli = Cli::default(); +// let (alice_btc_wallet, alice_xmr_wallet, bob_btc_wallet, bob_xmr_wallet, +// _containers) = setup_wallets( +// &cli, +// init_btc_alice, +// init_xmr_alice, +// init_btc_bob, +// init_xmr_bob, +// config +// ) +// .await; +// +// let alice_btc_wallet = Arc::new(alice_btc_wallet); +// let alice_xmr_wallet = Arc::new(alice_xmr_wallet); +// let bob_btc_wallet = Arc::new(bob_btc_wallet); +// let bob_xmr_wallet = Arc::new(bob_xmr_wallet); +// +// let amounts = SwapAmounts { +// btc: btc_to_swap, +// xmr: xmr_to_swap, +// }; +// +// let alice_db_dir = TempDir::new().unwrap(); +// let alice_swap_fut = async { +// let rng = &mut OsRng; +// let (alice_start_state, state0) = { +// let a = bitcoin::SecretKey::new_random(rng); +// let s_a = cross_curve_dleq::Scalar::random(rng); +// let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng); +// let redeem_address = +// alice_btc_wallet.as_ref().new_address().await.unwrap(); let +// punish_address = redeem_address.clone(); let state0 = +// xmr_btc::alice::State0::new( a, +// s_a, +// v_a, +// amounts.btc, +// amounts.xmr, +// config.bitcoin_refund_timelock, +// config.bitcoin_punish_timelock, +// redeem_address, +// punish_address, +// ); +// +// ( +// AliceState::Started { +// amounts, +// state0: state0.clone(), +// }, +// state0, +// ) +// }; +// let alice_behaviour = alice::Behaviour::new(state0.clone()); +// let alice_transport = build(alice_behaviour.identity()).unwrap(); +// let (mut alice_event_loop_1, alice_event_loop_handle) = +// alice::event_loop::EventLoop::new( alice_transport, +// alice_behaviour, +// alice_multiaddr.clone(), +// ) +// .unwrap(); +// +// let _alice_event_loop_1 = tokio::spawn(async move { +// alice_event_loop_1.run().await }); +// +// let config = xmr_btc::config::Config::regtest(); +// let swap_id = Uuid::new_v4(); +// +// let db = Database::open(alice_db_dir.path()).unwrap(); +// +// // Alice reaches encsig_learned +// alice::swap::run_until( +// alice_start_state, +// |state| matches!(state, AliceState::XmrLocked { .. }), +// alice_event_loop_handle, +// alice_btc_wallet.clone(), +// alice_xmr_wallet.clone(), +// config, +// swap_id, +// db, +// ) +// .await +// .unwrap(); +// +// let db = Database::open(alice_db_dir.path()).unwrap(); +// +// let alice_behaviour = alice::Behaviour::new(state0); +// let alice_transport = build(alice_behaviour.identity()).unwrap(); +// let (mut alice_event_loop_2, alice_event_loop_handle) = +// alice::event_loop::EventLoop::new( alice_transport, +// alice_behaviour, +// alice_multiaddr.clone(), +// ) +// .unwrap(); +// +// let _alice_event_loop_2 = tokio::spawn(async move { +// alice_event_loop_2.run().await }); +// +// // Load the latest state from the db +// let latest_state = db.get_state(swap_id).unwrap(); +// let latest_state = latest_state.try_into().unwrap(); +// +// // Finish the swap +// alice::swap::swap( +// latest_state, +// alice_event_loop_handle, +// alice_btc_wallet.clone(), +// alice_xmr_wallet.clone(), +// config, +// swap_id, +// db, +// ) +// .await +// }; +// +// let (bob_swap, bob_event_loop) = { +// let rng = &mut OsRng; +// let bob_db_dir = tempdir().unwrap(); +// let bob_db = Database::open(bob_db_dir.path()).unwrap(); +// let bob_behaviour = bob::Behaviour::default(); +// let bob_transport = build(bob_behaviour.identity()).unwrap(); +// +// let refund_address = bob_btc_wallet.new_address().await.unwrap(); +// let state0 = xmr_btc::bob::State0::new( +// rng, +// btc_to_swap, +// xmr_to_swap, +// config.bitcoin_refund_timelock, +// config.bitcoin_punish_timelock, +// refund_address, +// ); +// let bob_state = BobState::Started { +// state0, +// amounts, +// addr: alice_multiaddr.clone(), +// }; +// let (bob_event_loop, bob_event_loop_handle) = +// bob::event_loop::EventLoop::new(bob_transport, +// bob_behaviour).unwrap(); +// +// ( +// bob::swap::swap( +// bob_state, +// bob_event_loop_handle, +// bob_db, +// bob_btc_wallet.clone(), +// bob_xmr_wallet.clone(), +// OsRng, +// Uuid::new_v4(), +// ), +// bob_event_loop, +// ) +// }; +// +// let _bob_event_loop = tokio::spawn(async move { +// bob_event_loop.run().await }); +// +// try_join(alice_swap_fut, bob_swap).await.unwrap(); +// +// let btc_alice_final = alice_btc_wallet.balance().await.unwrap(); +// let xmr_alice_final = alice_xmr_wallet.get_balance().await.unwrap(); +// +// let btc_bob_final = bob_btc_wallet.balance().await.unwrap(); +// bob_xmr_wallet.0.refresh().await.unwrap(); +// let xmr_bob_final = bob_xmr_wallet.get_balance().await.unwrap(); +// +// // Alice's BTC balance did not change +// assert_eq!(btc_alice_final, init_btc_alice); +// // Bob wasted some BTC fees +// assert_eq!( +// btc_bob_final, +// init_btc_bob - bitcoin::Amount::from_sat(bitcoin::TX_FEE) +// ); +// +// // Alice wasted some XMR fees +// assert_eq!(init_xmr_alice - xmr_alice_final, monero::Amount::ZERO); +// // Bob's ZMR balance did not change +// assert_eq!(xmr_bob_final, init_xmr_bob); +// } diff --git a/swap/tests/bob_safe_restart.rs b/swap/tests/bob_safe_restart.rs deleted file mode 100644 index 3f1122da..00000000 --- a/swap/tests/bob_safe_restart.rs +++ /dev/null @@ -1,264 +0,0 @@ -use bitcoin_harness::Bitcoind; -use futures::future::try_join; -use libp2p::Multiaddr; -use monero_harness::{image, Monero}; -use rand::rngs::OsRng; -use std::sync::Arc; -use swap::{ - alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, monero, - network::transport::build, storage::Database, SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK, -}; -use tempfile::tempdir; -use testcontainers::{clients::Cli, Container}; -use uuid::Uuid; -use xmr_btc::cross_curve_dleq; - -fn setup_tracing() { - use tracing_subscriber::util::SubscriberInitExt as _; - let _guard = tracing_subscriber::fmt() - .with_env_filter("swap=trace,xmr_btc=trace") - .with_ansi(false) - .set_default(); -} - -// This is just to keep the containers alive -#[allow(dead_code)] -struct Containers<'a> { - bitcoind: Bitcoind<'a>, - monerods: Vec>, -} - -/// Returns Alice's and Bob's wallets, in this order -async fn setup_wallets( - cli: &Cli, - _init_btc_alice: bitcoin::Amount, - init_xmr_alice: monero::Amount, - init_btc_bob: bitcoin::Amount, - init_xmr_bob: monero::Amount, -) -> ( - bitcoin::Wallet, - monero::Wallet, - bitcoin::Wallet, - monero::Wallet, - Containers<'_>, -) { - let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); - let _ = bitcoind.init(5).await; - - let alice_btc_wallet = swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone()) - .await - .unwrap(); - let bob_btc_wallet = swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone()) - .await - .unwrap(); - bitcoind - .mint(bob_btc_wallet.0.new_address().await.unwrap(), init_btc_bob) - .await - .unwrap(); - - let (monero, monerods) = Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) - .await - .unwrap(); - monero - .init(vec![ - ("alice", init_xmr_alice.as_piconero()), - ("bob", init_xmr_bob.as_piconero()), - ]) - .await - .unwrap(); - - let alice_xmr_wallet = swap::monero::Wallet(monero.wallet("alice").unwrap().client()); - let bob_xmr_wallet = swap::monero::Wallet(monero.wallet("bob").unwrap().client()); - - ( - alice_btc_wallet, - alice_xmr_wallet, - bob_btc_wallet, - bob_xmr_wallet, - Containers { bitcoind, monerods }, - ) -} - -#[tokio::test] -async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { - setup_tracing(); - - let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876" - .parse() - .expect("failed to parse Alice's address"); - - let btc_to_swap = bitcoin::Amount::from_sat(1_000_000); - let init_btc_alice = bitcoin::Amount::ZERO; - let init_btc_bob = btc_to_swap * 10; - - let xmr_to_swap = monero::Amount::from_piconero(1_000_000_000_000); - let init_xmr_alice = xmr_to_swap * 10; - let init_xmr_bob = monero::Amount::ZERO; - - let cli = Cli::default(); - let (alice_btc_wallet, alice_xmr_wallet, bob_btc_wallet, bob_xmr_wallet, _containers) = - setup_wallets( - &cli, - init_btc_alice, - init_xmr_alice, - init_btc_bob, - init_xmr_bob, - ) - .await; - - let alice_btc_wallet = Arc::new(alice_btc_wallet); - let alice_xmr_wallet = Arc::new(alice_xmr_wallet); - let bob_btc_wallet = Arc::new(bob_btc_wallet); - let bob_xmr_wallet = Arc::new(bob_xmr_wallet); - - let amounts = SwapAmounts { - btc: btc_to_swap, - xmr: xmr_to_swap, - }; - - let (alice_swap, mut alice_event_loop) = { - let rng = &mut OsRng; - let (alice_start_state, state0) = { - let a = bitcoin::SecretKey::new_random(rng); - let s_a = cross_curve_dleq::Scalar::random(rng); - let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng); - let redeem_address = alice_btc_wallet.as_ref().new_address().await.unwrap(); - let punish_address = redeem_address.clone(); - let state0 = xmr_btc::alice::State0::new( - a, - s_a, - v_a, - amounts.btc, - amounts.xmr, - REFUND_TIMELOCK, - PUNISH_TIMELOCK, - redeem_address, - punish_address, - ); - - ( - AliceState::Started { - amounts, - state0: state0.clone(), - }, - state0, - ) - }; - let alice_behaviour = alice::Behaviour::new(state0); - let alice_transport = build(alice_behaviour.identity()).unwrap(); - let (alice_event_loop, alice_event_loop_handle) = alice::event_loop::EventLoop::new( - alice_transport, - alice_behaviour, - alice_multiaddr.clone(), - ) - .unwrap(); - - let alice_db_dir = tempdir().unwrap(); - let alice_db = Database::open(alice_db_dir.path()).unwrap(); - let config = xmr_btc::config::Config::regtest(); - let swap_id = Uuid::new_v4(); - - ( - alice::swap::swap( - alice_start_state, - alice_event_loop_handle, - alice_btc_wallet.clone(), - alice_xmr_wallet.clone(), - config, - swap_id, - alice_db, - ), - alice_event_loop, - ) - }; - - let bob_swap_fut = async { - let rng = &mut OsRng; - let bob_db_dir = tempdir().unwrap(); - let bob_db = Database::open(bob_db_dir.path()).unwrap(); - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(bob_behaviour.identity()).unwrap(); - - let refund_address = bob_btc_wallet.new_address().await.unwrap(); - let state0 = xmr_btc::bob::State0::new( - rng, - btc_to_swap, - xmr_to_swap, - REFUND_TIMELOCK, - PUNISH_TIMELOCK, - refund_address, - ); - let start_state = BobState::Started { - state0, - amounts, - addr: alice_multiaddr.clone(), - }; - - let (bob_event_loop_1, bob_event_loop_handle_1) = - bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap(); - - let _bob_event_loop_1 = tokio::spawn(async move { bob_event_loop_1.run().await }); - - let swap_id = Uuid::new_v4(); - // Bob reaches encsig_learned - bob::swap::run_until( - start_state, - |state| matches!(state, BobState::EncSigSent { .. }), - bob_event_loop_handle_1, - bob_db, - bob_btc_wallet.clone(), - bob_xmr_wallet.clone(), - OsRng, - swap_id, - ) - .await - .unwrap(); - - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(bob_behaviour.identity()).unwrap(); - let (bob_event_loop_2, bob_event_loop_handle_2) = - bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap(); - let bob_db = Database::open(bob_db_dir.path()).unwrap(); - - let _bob_event_loop_2 = tokio::spawn(async move { bob_event_loop_2.run().await }); - - // Load the latest state from the db - let latest_state = bob_db.get_state(swap_id).unwrap(); - let state = if let swap::state::Swap::Bob(state) = latest_state { - state - } else { - panic!("Bob state expected") - }; - - bob::swap::swap( - state.into(), - bob_event_loop_handle_2, - bob_db, - bob_btc_wallet.clone(), - bob_xmr_wallet.clone(), - OsRng, - swap_id, - ) - .await - }; - - let _alice_event_loop = tokio::spawn(async move { alice_event_loop.run().await }); - - try_join(alice_swap, bob_swap_fut).await.unwrap(); - - let btc_alice_final = alice_btc_wallet.balance().await.unwrap(); - let xmr_alice_final = alice_xmr_wallet.get_balance().await.unwrap(); - - let btc_bob_final = bob_btc_wallet.balance().await.unwrap(); - bob_xmr_wallet.0.refresh().await.unwrap(); - let xmr_bob_final = bob_xmr_wallet.get_balance().await.unwrap(); - - assert_eq!( - btc_alice_final, - init_btc_alice + btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE) - ); - assert!(btc_bob_final <= init_btc_bob - btc_to_swap); - - assert!(xmr_alice_final <= init_xmr_alice - xmr_to_swap); - assert_eq!(xmr_bob_final, init_xmr_bob + xmr_to_swap); -} diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index b42203bc..7e538bcc 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -1,19 +1,17 @@ +use crate::testutils::{init_alice, init_bob}; use bitcoin_harness::Bitcoind; use futures::future::try_join; use libp2p::Multiaddr; use monero_harness::Monero; use rand::rngs::OsRng; -use std::sync::Arc; -use swap::{ - alice, alice::swap::AliceState, bob, bob::swap::BobState, network::transport::build, - storage::Database, SwapAmounts, -}; +use swap::{alice, alice::swap::AliceState, bob, bob::swap::BobState, storage::Database}; use tempfile::tempdir; use testcontainers::clients::Cli; -use tracing_core::dispatcher::DefaultGuard; -use tracing_log::LogTracer; +use testutils::init_tracing; use uuid::Uuid; -use xmr_btc::{alice::State0, bitcoin, config::Config, cross_curve_dleq}; +use xmr_btc::{bitcoin, config::Config}; + +pub mod testutils; /// Run the following tests with RUST_MIN_STACK=10000000 @@ -48,8 +46,8 @@ async fn happy_path() { let ( alice_state, - mut alice_swarm_driver, - alice_swarm_handle, + mut alice_event_loop, + alice_event_loop_handle, alice_btc_wallet, alice_xmr_wallet, ) = init_alice( @@ -64,7 +62,7 @@ async fn happy_path() { ) .await; - let (bob_state, bob_swarm_driver, bob_swarm_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) = + let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) = init_bob( alice_multiaddr, &bitcoind, @@ -82,7 +80,7 @@ async fn happy_path() { let alice_swap_fut = alice::swap::swap( alice_state, - alice_swarm_handle, + alice_event_loop_handle, alice_btc_wallet.clone(), alice_xmr_wallet.clone(), config, @@ -90,11 +88,11 @@ async fn happy_path() { alice_db, ); - let _alice_swarm_fut = tokio::spawn(async move { alice_swarm_driver.run().await }); + let _alice_swarm_fut = tokio::spawn(async move { alice_event_loop.run().await }); let bob_swap_fut = bob::swap::swap( bob_state, - bob_swarm_handle, + bob_event_loop_handle, bob_db, bob_btc_wallet.clone(), bob_xmr_wallet.clone(), @@ -102,7 +100,7 @@ async fn happy_path() { Uuid::new_v4(), ); - let _bob_swarm_fut = tokio::spawn(async move { bob_swarm_driver.run().await }); + let _bob_swarm_fut = tokio::spawn(async move { bob_event_loop.run().await }); try_join(alice_swap_fut, bob_swap_fut).await.unwrap(); @@ -154,20 +152,25 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { let config = Config::regtest(); - let (alice_state, mut alice_swarm, alice_swarm_handle, alice_btc_wallet, alice_xmr_wallet) = - init_alice( - &bitcoind, - &monero, - btc_to_swap, - alice_btc_starting_balance, - xmr_to_swap, - alice_xmr_starting_balance, - alice_multiaddr.clone(), - config, - ) - .await; + let ( + alice_state, + mut alice_event_loop, + alice_event_loop_handle, + alice_btc_wallet, + alice_xmr_wallet, + ) = init_alice( + &bitcoind, + &monero, + btc_to_swap, + alice_btc_starting_balance, + xmr_to_swap, + alice_xmr_starting_balance, + alice_multiaddr.clone(), + config, + ) + .await; - let (bob_state, bob_swarm_driver, bob_swarm_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) = + let (bob_state, bob_event_loop, bob_event_loop_handle, bob_btc_wallet, bob_xmr_wallet, bob_db) = init_bob( alice_multiaddr, &bitcoind, @@ -183,7 +186,7 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { let bob_btc_locked_fut = bob::swap::run_until( bob_state, bob::swap::is_btc_locked, - bob_swarm_handle, + bob_event_loop_handle, bob_db, bob_btc_wallet.clone(), bob_xmr_wallet.clone(), @@ -191,14 +194,14 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { Uuid::new_v4(), ); - let _bob_swarm_fut = tokio::spawn(async move { bob_swarm_driver.run().await }); + let _bob_swarm_fut = tokio::spawn(async move { bob_event_loop.run().await }); let alice_db_datadir = tempdir().unwrap(); let alice_db = Database::open(alice_db_datadir.path()).unwrap(); let alice_fut = alice::swap::swap( alice_state, - alice_swarm_handle, + alice_event_loop_handle, alice_btc_wallet.clone(), alice_xmr_wallet.clone(), Config::regtest(), @@ -206,7 +209,7 @@ async fn alice_punishes_if_bob_never_acts_after_fund() { alice_db, ); - let _alice_swarm_fut = tokio::spawn(async move { alice_swarm.run().await }); + let _alice_swarm_fut = tokio::spawn(async move { alice_event_loop.run().await }); // Wait until alice has locked xmr and bob has locked btc let ((alice_state, _), bob_state) = try_join(alice_fut, bob_btc_locked_fut).await.unwrap(); @@ -314,6 +317,10 @@ async fn both_refund() { tokio::spawn(async move { bob_swarm_driver.run().await }); + let alice_swap_id = Uuid::new_v4(); + let alice_db_datadir = tempdir().unwrap(); + let alice_db = Database::open(alice_db_datadir.path()).unwrap(); + let alice_xmr_locked_fut = alice::swap::run_until( alice_state, alice::swap::is_xmr_locked, @@ -321,8 +328,8 @@ async fn both_refund() { alice_btc_wallet.clone(), alice_xmr_wallet.clone(), Config::regtest(), - todo!(), - todo!(), + alice_swap_id, + alice_db, ); tokio::spawn(async move { alice_swarm_driver.run().await }); @@ -337,14 +344,16 @@ async fn both_refund() { panic!("Bob in unexpected state"); }; + let alice_db = Database::open(alice_db_datadir.path()).unwrap(); + let (alice_state, _) = alice::swap::swap( alice_state, alice_swarm_handle, alice_btc_wallet.clone(), alice_xmr_wallet.clone(), Config::regtest(), - todo!(), - todo!(), + alice_swap_id, + alice_db, ) .await .unwrap(); @@ -384,189 +393,3 @@ async fn both_refund() { let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); assert_eq!(xmr_bob_final, bob_xmr_starting_balance); } - -#[allow(clippy::too_many_arguments)] -async fn init_alice( - bitcoind: &Bitcoind<'_>, - monero: &Monero, - btc_to_swap: bitcoin::Amount, - _btc_starting_balance: bitcoin::Amount, - xmr_to_swap: xmr_btc::monero::Amount, - xmr_starting_balance: xmr_btc::monero::Amount, - listen: Multiaddr, - config: Config, -) -> ( - AliceState, - alice::event_loop::EventLoop, - alice::event_loop::EventLoopHandle, - Arc, - Arc, -) { - monero - .init(vec![("alice", xmr_starting_balance.as_piconero())]) - .await - .unwrap(); - - let alice_xmr_wallet = Arc::new(swap::monero::Wallet( - monero.wallet("alice").unwrap().client(), - )); - - let alice_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone(), config.bitcoin_network) - .await - .unwrap(), - ); - - let amounts = SwapAmounts { - btc: btc_to_swap, - xmr: xmr_to_swap, - }; - - let rng = &mut OsRng; - let (alice_state, alice_behaviour) = { - let a = bitcoin::SecretKey::new_random(rng); - let s_a = cross_curve_dleq::Scalar::random(rng); - let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng); - let redeem_address = alice_btc_wallet.as_ref().new_address().await.unwrap(); - let punish_address = redeem_address.clone(); - let state0 = State0::new( - a, - s_a, - v_a, - amounts.btc, - amounts.xmr, - config.bitcoin_refund_timelock, - config.bitcoin_punish_timelock, - redeem_address, - punish_address, - ); - - ( - AliceState::Started { - amounts, - state0: state0.clone(), - }, - alice::Behaviour::new(state0), - ) - }; - - let alice_transport = build(alice_behaviour.identity()).unwrap(); - - let (swarm_driver, handle) = - alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen).unwrap(); - - ( - alice_state, - swarm_driver, - handle, - alice_btc_wallet, - alice_xmr_wallet, - ) -} - -#[allow(clippy::too_many_arguments)] -async fn init_bob( - alice_multiaddr: Multiaddr, - bitcoind: &Bitcoind<'_>, - monero: &Monero, - btc_to_swap: bitcoin::Amount, - btc_starting_balance: bitcoin::Amount, - xmr_to_swap: xmr_btc::monero::Amount, - xmr_stating_balance: xmr_btc::monero::Amount, - config: Config, -) -> ( - BobState, - bob::event_loop::EventLoop, - bob::event_loop::EventLoopHandle, - Arc, - Arc, - Database, -) { - let bob_btc_wallet = Arc::new( - swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone(), config.bitcoin_network) - .await - .unwrap(), - ); - bitcoind - .mint( - bob_btc_wallet.inner.new_address().await.unwrap(), - btc_starting_balance, - ) - .await - .unwrap(); - - monero - .init(vec![("bob", xmr_stating_balance.as_piconero())]) - .await - .unwrap(); - - let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); - - let amounts = SwapAmounts { - btc: btc_to_swap, - xmr: xmr_to_swap, - }; - - let bob_db_dir = tempdir().unwrap(); - let bob_db = Database::open(bob_db_dir.path()).unwrap(); - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(bob_behaviour.identity()).unwrap(); - - let refund_address = bob_btc_wallet.new_address().await.unwrap(); - let state0 = xmr_btc::bob::State0::new( - &mut OsRng, - btc_to_swap, - xmr_to_swap, - config.bitcoin_refund_timelock, - config.bitcoin_punish_timelock, - refund_address, - ); - let bob_state = BobState::Started { - state0, - amounts, - addr: alice_multiaddr, - }; - - let (swarm_driver, swarm_handle) = - bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap(); - - ( - bob_state, - swarm_driver, - swarm_handle, - bob_btc_wallet, - bob_xmr_wallet, - bob_db, - ) -} - -/// Utility function to initialize logging in the test environment. -/// Note that you have to keep the `_guard` in scope after calling in test: -/// -/// ```rust -/// let _guard = init_tracing(); -/// ``` -fn init_tracing() -> DefaultGuard { - // converts all log records into tracing events - // Note: Make sure to initialize without unwrapping, otherwise this causes - // trouble when running multiple tests. - let _ = LogTracer::init(); - - let global_filter = tracing::Level::WARN; - let swap_filter = tracing::Level::DEBUG; - let xmr_btc_filter = tracing::Level::DEBUG; - let monero_harness_filter = tracing::Level::INFO; - let bitcoin_harness_filter = tracing::Level::INFO; - - use tracing_subscriber::util::SubscriberInitExt as _; - tracing_subscriber::fmt() - .with_env_filter(format!( - "{},swap={},xmr-btc={},monero_harness={},bitcoin_harness={}", - global_filter, - swap_filter, - xmr_btc_filter, - monero_harness_filter, - bitcoin_harness_filter, - )) - .set_default() -} diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs new file mode 100644 index 00000000..0fe62024 --- /dev/null +++ b/swap/tests/testutils/mod.rs @@ -0,0 +1,264 @@ +use bitcoin_harness::Bitcoind; +use libp2p::core::Multiaddr; +use monero_harness::{image, Monero}; +use rand::rngs::OsRng; +use std::sync::Arc; +use swap::{ + alice, alice::swap::AliceState, bitcoin, bob, bob::swap::BobState, monero, + network::transport::build, storage::Database, SwapAmounts, +}; +use tempfile::tempdir; +use testcontainers::{clients::Cli, Container}; +use tracing_core::dispatcher::DefaultGuard; +use tracing_log::LogTracer; +use xmr_btc::{alice::State0, config::Config, cross_curve_dleq}; + +#[allow(clippy::too_many_arguments)] +pub async fn init_bob( + alice_multiaddr: Multiaddr, + bitcoind: &Bitcoind<'_>, + monero: &Monero, + btc_to_swap: bitcoin::Amount, + btc_starting_balance: bitcoin::Amount, + xmr_to_swap: xmr_btc::monero::Amount, + xmr_stating_balance: xmr_btc::monero::Amount, + config: Config, +) -> ( + BobState, + bob::event_loop::EventLoop, + bob::event_loop::EventLoopHandle, + Arc, + Arc, + Database, +) { + let bob_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone(), config.bitcoin_network) + .await + .unwrap(), + ); + bitcoind + .mint( + bob_btc_wallet.inner.new_address().await.unwrap(), + btc_starting_balance, + ) + .await + .unwrap(); + + monero + .init(vec![("bob", xmr_stating_balance.as_piconero())]) + .await + .unwrap(); + + let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); + + let amounts = SwapAmounts { + btc: btc_to_swap, + xmr: xmr_to_swap, + }; + + let bob_db_dir = tempdir().unwrap(); + let bob_db = Database::open(bob_db_dir.path()).unwrap(); + let bob_behaviour = bob::Behaviour::default(); + let bob_transport = build(bob_behaviour.identity()).unwrap(); + + let refund_address = bob_btc_wallet.new_address().await.unwrap(); + let state0 = xmr_btc::bob::State0::new( + &mut OsRng, + btc_to_swap, + xmr_to_swap, + config.bitcoin_refund_timelock, + config.bitcoin_punish_timelock, + refund_address, + ); + let bob_state = BobState::Started { + state0, + amounts, + addr: alice_multiaddr, + }; + + let (swarm_driver, swarm_handle) = + bob::event_loop::EventLoop::new(bob_transport, bob_behaviour).unwrap(); + + ( + bob_state, + swarm_driver, + swarm_handle, + bob_btc_wallet, + bob_xmr_wallet, + bob_db, + ) +} + +#[allow(clippy::too_many_arguments)] +pub async fn init_alice( + bitcoind: &Bitcoind<'_>, + monero: &Monero, + btc_to_swap: bitcoin::Amount, + _btc_starting_balance: bitcoin::Amount, + xmr_to_swap: xmr_btc::monero::Amount, + xmr_starting_balance: xmr_btc::monero::Amount, + listen: Multiaddr, + config: Config, +) -> ( + AliceState, + alice::event_loop::EventLoop, + alice::event_loop::EventLoopHandle, + Arc, + Arc, +) { + monero + .init(vec![("alice", xmr_starting_balance.as_piconero())]) + .await + .unwrap(); + + let alice_xmr_wallet = Arc::new(swap::monero::Wallet( + monero.wallet("alice").unwrap().client(), + )); + + let alice_btc_wallet = Arc::new( + swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone(), config.bitcoin_network) + .await + .unwrap(), + ); + + let amounts = SwapAmounts { + btc: btc_to_swap, + xmr: xmr_to_swap, + }; + + let rng = &mut OsRng; + let (alice_state, alice_behaviour) = { + let a = crate::bitcoin::SecretKey::new_random(rng); + let s_a = cross_curve_dleq::Scalar::random(rng); + let v_a = xmr_btc::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 = State0::new( + a, + s_a, + v_a, + amounts.btc, + amounts.xmr, + config.bitcoin_refund_timelock, + config.bitcoin_punish_timelock, + redeem_address, + punish_address, + ); + + ( + AliceState::Started { + amounts, + state0: state0.clone(), + }, + alice::Behaviour::new(state0), + ) + }; + + let alice_transport = build(alice_behaviour.identity()).unwrap(); + + let (swarm_driver, handle) = + alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen).unwrap(); + + ( + alice_state, + swarm_driver, + handle, + alice_btc_wallet, + alice_xmr_wallet, + ) +} + +/// Returns Alice's and Bob's wallets, in this order +pub async fn setup_wallets( + cli: &Cli, + _init_btc_alice: bitcoin::Amount, + init_xmr_alice: xmr_btc::monero::Amount, + init_btc_bob: bitcoin::Amount, + init_xmr_bob: xmr_btc::monero::Amount, + config: Config, +) -> ( + bitcoin::Wallet, + monero::Wallet, + bitcoin::Wallet, + monero::Wallet, + Containers<'_>, +) { + let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); + let _ = bitcoind.init(5).await; + + let alice_btc_wallet = + swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone(), config.bitcoin_network) + .await + .unwrap(); + let bob_btc_wallet = + swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone(), config.bitcoin_network) + .await + .unwrap(); + bitcoind + .mint( + bob_btc_wallet.inner.new_address().await.unwrap(), + init_btc_bob, + ) + .await + .unwrap(); + + let (monero, monerods) = Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()]) + .await + .unwrap(); + monero + .init(vec![ + ("alice", init_xmr_alice.as_piconero()), + ("bob", init_xmr_bob.as_piconero()), + ]) + .await + .unwrap(); + + let alice_xmr_wallet = swap::monero::Wallet(monero.wallet("alice").unwrap().client()); + let bob_xmr_wallet = swap::monero::Wallet(monero.wallet("bob").unwrap().client()); + + ( + alice_btc_wallet, + alice_xmr_wallet, + bob_btc_wallet, + bob_xmr_wallet, + Containers { bitcoind, monerods }, + ) +} + +// This is just to keep the containers alive +#[allow(dead_code)] +pub struct Containers<'a> { + bitcoind: Bitcoind<'a>, + monerods: Vec>, +} + +/// Utility function to initialize logging in the test environment. +/// Note that you have to keep the `_guard` in scope after calling in test: +/// +/// ```rust +/// let _guard = init_tracing(); +/// ``` +pub fn init_tracing() -> DefaultGuard { + // converts all log records into tracing events + // Note: Make sure to initialize without unwrapping, otherwise this causes + // trouble when running multiple tests. + let _ = LogTracer::init(); + + let global_filter = tracing::Level::WARN; + let swap_filter = tracing::Level::DEBUG; + let xmr_btc_filter = tracing::Level::DEBUG; + let monero_harness_filter = tracing::Level::INFO; + let bitcoin_harness_filter = tracing::Level::INFO; + + use tracing_subscriber::util::SubscriberInitExt as _; + tracing_subscriber::fmt() + .with_env_filter(format!( + "{},swap={},xmr-btc={},monero_harness={},bitcoin_harness={}", + global_filter, + swap_filter, + xmr_btc_filter, + monero_harness_filter, + bitcoin_harness_filter, + )) + .set_default() +}