diff --git a/swap/src/main.rs b/swap/src/main.rs index c924f54a..0c70d67e 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, alice::AliceState, bob, bob::BobState}, - seed::Seed, + database::Database, + monero, + protocol::{alice, bob, bob::SwapFactory, StartingBalances}, trace::init_tracing, SwapAmounts, }; @@ -51,8 +47,7 @@ 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 seed = swap::config::seed::Seed::from_file_or_generate(&data_dir) .expect("Could not retrieve/initialize seed") @@ -67,7 +62,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 +75,28 @@ 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::SwapFactory::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 +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, @@ -137,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 {}", send_bitcoin, receive_monero, swap_id ); - bob_swap( + let bob_factory = SwapFactory::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]); } @@ -192,30 +164,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::SwapFactory::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, @@ -225,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 = SwapFactory::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?; } }; @@ -260,7 +230,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,68 +253,10 @@ 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 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 (mut event_loop, handle) = - alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?; - - let swap = alice::swap::swap( - state, - handle, - bitcoin_wallet.clone(), - monero_wallet.clone(), - config, - swap_id, - db, - ); - - tokio::spawn(async move { event_loop.run().await }); - swap.await -} - -#[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 bob_behaviour = bob::Behaviour::new(network::Seed::new(seed)); - let bob_transport = build(bob_behaviour.identity())?; - - let (event_loop, handle) = - bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)?; - - let swap = bob::swap::swap( - state, - handle, - db, - bitcoin_wallet.clone(), - monero_wallet.clone(), - OsRng, - swap_id, - ); + let starting_balances = StartingBalances { + btc: bitcoin_balance, + xmr: monero_balance, + }; - tokio::spawn(event_loop.run()); - swap.await + Ok((bitcoin_wallet, monero_wallet, starting_balances)) } 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 6ce5fd95..3b07005b 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -1,19 +1,15 @@ //! 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 anyhow::{bail, Result}; +use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId}; use tracing::{debug, info}; use crate::{ + bitcoin, database, monero, network::{ peer_tracker::{self, PeerTracker}, request_response::AliceToBob, - transport::SwapTransport, - Seed, TokioExecutor, + Seed as NetworkSeed, }, protocol::bob, SwapAmounts, @@ -26,8 +22,16 @@ pub use self::{ message1::Message1, message2::Message2, state::*, - swap::{run_until, swap}, + swap::{run, run_until}, +}; +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}; +use uuid::Uuid; mod amounts; pub mod event_loop; @@ -39,29 +43,173 @@ pub mod state; mod steps; pub mod swap; -pub type Swarm = libp2p::Swarm; +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 fn new_swarm( - listen: Multiaddr, - transport: SwapTransport, - behaviour: Behaviour, -) -> Result { - use anyhow::Context as _; +pub struct SwapFactory { + swap_id: Uuid, + identity: Keypair, + peer_id: PeerId, + db_path: PathBuf, + config: Config, + + listen_address: Multiaddr, + + pub bitcoin_wallet: Arc, + pub monero_wallet: Arc, + pub starting_balances: StartingBalances, +} + +impl SwapFactory { + #[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 { + swap_id, + identity, + peer_id, + db_path, + config, + listen_address, + bitcoin_wallet, + monero_wallet, + 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 { + bail!( + "Trying to load swap with id {} for the wrong direction.", + self.swap_id + ) + }; + + let (event_loop, event_loop_handle) = init_alice_event_loop( + self.listen_address.clone(), + self.identity.clone(), + self.peer_id.clone(), + )?; - let local_peer_id = behaviour.peer_id(); + 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 mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) - .executor(Box::new(TokioExecutor { - handle: tokio::runtime::Handle::current(), - })) - .build(); + let amounts = SwapAmounts { + btc: btc_to_swap, + xmr: xmr_to_swap, + }; - Swarm::listen_on(&mut swarm, listen.clone()) - .with_context(|| format!("Address is not supported: {:#}", listen))?; + 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, + ); - tracing::info!("Initialized swarm: {}", local_peer_id); + Ok(AliceState::Started { amounts, state0 }) +} - Ok(swarm) +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) } #[derive(Debug)] @@ -138,7 +286,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 { @@ -148,33 +296,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); 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/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index a322c995..2ed2eb2f 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 { + run_until_internal( + 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 run_until_internal( 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( + run_until_internal( 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( + run_until_internal( 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( + run_until_internal( 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( + run_until_internal( 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 run_until_internal( 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( + run_until_internal( 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( + run_until_internal( state, is_target_state, event_loop_handle, @@ -358,10 +362,12 @@ 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?; - swap( + + run_until_internal( state, + is_target_state, event_loop_handle, bitcoin_wallet.clone(), monero_wallet, @@ -382,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( + run_until_internal( state, is_target_state, event_loop_handle, @@ -407,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) } @@ -437,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( + run_until_internal( state, is_target_state, event_loop_handle, @@ -461,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( + run_until_internal( state, is_target_state, event_loop_handle, diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index ab9bbfc6..f9ac84a2 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,19 +1,14 @@ //! 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 anyhow::{bail, Result}; +use libp2p::{core::Multiaddr, NetworkBehaviour, PeerId}; use tracing::{debug, info}; use crate::{ + bitcoin, bitcoin::EncryptedSignature, - network::{ - peer_tracker::{self, PeerTracker}, - transport::SwapTransport, - Seed, TokioExecutor, - }, + database, monero, network, + network::peer_tracker::{self, PeerTracker}, protocol::{alice, bob}, SwapAmounts, }; @@ -26,8 +21,16 @@ pub use self::{ message2::Message2, message3::Message3, state::*, - swap::{run_until, swap}, + swap::{run, run_until}, +}; +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}; +use uuid::Uuid; mod amounts; pub mod event_loop; @@ -38,20 +41,169 @@ mod message3; pub mod state; pub mod swap; -pub type Swarm = libp2p::Swarm; +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 struct SwapFactory { + swap_id: Uuid, + identity: Keypair, + peer_id: PeerId, + db_path: PathBuf, + config: Config, + + alice_connect_address: Multiaddr, + alice_connect_peer_id: PeerId, + + pub bitcoin_wallet: Arc, + pub monero_wallet: Arc, + pub starting_balances: StartingBalances, +} + +impl SwapFactory { + #[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 { + swap_id, + identity, + peer_id, + db_path, + config, + alice_connect_address, + alice_connect_peer_id, + bitcoin_wallet, + monero_wallet, + starting_balances, + } + } + + 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())?; -pub fn new_swarm(transport: SwapTransport, behaviour: Behaviour) -> Result { - let local_peer_id = behaviour.peer_id(); + 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 swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) - .executor(Box::new(TokioExecutor { - handle: tokio::runtime::Handle::current(), - })) - .build(); + let resume_state = if let database::Swap::Bob(state) = db.get_state(self.swap_id)? { + state.into() + } else { + bail!( + "Trying to load swap with id {} for the wrong direction.", + self.swap_id + ) + }; - info!("Initialized swarm with identity {}", local_peer_id); + 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(swarm) + 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)] @@ -115,7 +267,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 { @@ -125,33 +277,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); @@ -189,19 +317,3 @@ impl Behaviour { self.pt.add_address(peer_id, address) } } - -impl Default for Behaviour { - fn default() -> Behaviour { - let identity = Keypair::generate_ed25519(); - - Self { - pt: PeerTracker::default(), - amounts: Amounts::default(), - message0: message0::Behaviour::default(), - 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/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 9046e5a6..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}; @@ -14,33 +14,6 @@ use crate::{ 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 -} - pub fn is_complete(state: &BobState) -> bool { matches!( state, @@ -63,10 +36,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 { + run_until_internal( + 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 run_until_internal( state: BobState, is_target_state: fn(&BobState) -> bool, mut event_loop_handle: EventLoopHandle, @@ -99,7 +94,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( + run_until_internal( state, is_target_state, event_loop_handle, @@ -120,7 +115,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( + run_until_internal( state, is_target_state, event_loop_handle, @@ -185,7 +180,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -226,7 +221,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -261,7 +256,7 @@ where let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -282,7 +277,7 @@ where }; let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -307,7 +302,7 @@ where db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) .await?; - run_until( + run_until_internal( state, is_target_state, event_loop_handle, @@ -336,7 +331,7 @@ where let db_state = state.clone().into(); db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until( + run_until_internal( state, is_target_state, event_loop_handle, diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 18d31198..700c5d05 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::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_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; + 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 1e49fdf3..dcd39f8e 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::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); + + 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 = ctx.recover_alice_from_db().await; + assert!(matches!(alice_swap.state, AliceState::EncSigLearned {..})); + + let alice_state = alice::run(alice_swap).await.unwrap(); + + ctx.assert_alice_redeemed(alice_state).await; + + let bob_state = bob_handle.await.unwrap(); + 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 7c1618b0..a1c04034 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::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); + + 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 = ctx.recover_bob_from_db().await; + assert!(matches!(bob_swap.state, BobState::EncSigSent {..})); + + let bob_state = bob::run(bob_swap).await.unwrap(); + + ctx.assert_bob_redeemed(bob_state).await; + + let alice_state = alice_handle.await.unwrap(); + 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 c206e2fd..231ed1e5 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::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); + + 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 = ctx.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; + ctx.assert_bob_redeemed(bob_state).await; let alice_state = alice_swap_handle.await.unwrap(); - alice_harness.assert_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 e17de0ff..9aea25bd 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::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); + + 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(); + 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 = 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 = ctx.recover_bob_from_db().await; + assert!(matches!(bob_swap.state, BobState::BtcLocked {..})); + + let bob_state = bob::run(bob_swap).await.unwrap(); + + 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 2a1d338c..111d1cfa 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::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_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 = ctx.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; + 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 30f69b7b..021b8bd9 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -2,17 +2,14 @@ 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, - protocol::{alice, alice::AliceState, bob, bob::BobState}, + monero, + protocol::{alice, alice::AliceState, bob, bob::BobState, StartingBalances}, seed::Seed, SwapAmounts, }; @@ -22,347 +19,150 @@ use tracing_core::dispatcher::DefaultGuard; use tracing_log::LogTracer; use uuid::Uuid; -pub async fn test(testfn: T) -where - T: Fn(AliceHarness, BobHarness) -> F, - F: Future, -{ - let cli = Cli::default(); - - let _guard = init_tracing(); - - let (monero, containers) = testutils::init_containers(&cli).await; - - let swap_amounts = SwapAmounts { - btc: bitcoin::Amount::from_sat(1_000_000), - xmr: monero::Amount::from_piconero(1_000_000_000_000), - }; - - let config = Config::regtest(); - - let alice_starting_balances = StartingBalances { - xmr: swap_amounts.xmr * 10, - btc: bitcoin::Amount::ZERO, - }; - let alice_harness = AliceHarness::new( - config, - swap_amounts, - Uuid::new_v4(), - &monero, - &containers.bitcoind, - alice_starting_balances, - ) - .await; - - let bob_starting_balances = StartingBalances { - xmr: monero::Amount::ZERO, - btc: swap_amounts.btc * 10, - }; - - let bob_harness = BobHarness::new( - config, - swap_amounts, - Uuid::new_v4(), - &monero, - &containers.bitcoind, - bob_starting_balances, - alice_harness.listen_address(), - alice_harness.peer_id(), - ) - .await; - - testfn(alice_harness, bob_harness).await -} +pub struct TestContext { + swap_amounts: SwapAmounts, -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, + alice_swap_factory: alice::SwapFactory, + bob_swap_factory: bob::SwapFactory, } -pub struct AliceHarness { - listen_address: Multiaddr, - peer_id: PeerId, - - seed: Seed, - db_path: PathBuf, - swap_id: Uuid, +impl TestContext { + 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 + .unwrap(); - swap_amounts: SwapAmounts, - bitcoin_wallet: Arc, - monero_wallet: Arc, - config: Config, - starting_balances: StartingBalances, -} + tokio::spawn(async move { event_loop.run().await }); -impl AliceHarness { - async fn new( - config: Config, - swap_amounts: SwapAmounts, - 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, - swap_amounts, - bitcoin_wallet, - monero_wallet, - config, - starting_balances, - } + swap } - pub async fn new_alice(&self) -> Alice { - let initial_state = init_alice_state( - self.swap_amounts.btc, - self.swap_amounts.xmr, - self.bitcoin_wallet.clone(), - self.config, - ) - .await; - - let (mut event_loop, event_loop_handle) = - init_alice_event_loop(self.listen_address.clone(), self.seed); + 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 + .unwrap(); 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, - } + swap } - 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 { + let (swap, mut event_loop) = self + .alice_swap_factory + .recover_alice_from_db() + .await + .unwrap(); - // reopen the existing database - let db = Database::open(self.db_path.clone().as_path()).unwrap(); + tokio::spawn(async move { event_loop.run().await }); - let resume_state = - if let swap::database::Swap::Alice(state) = db.get_state(self.swap_id).unwrap() { - state.into() - } else { - unreachable!() - }; + swap + } - let (mut event_loop, event_loop_handle) = - init_alice_event_loop(self.listen_address.clone(), self.seed); + pub async fn recover_bob_from_db(&self) -> bob::Swap { + let (swap, event_loop) = self.bob_swap_factory.recover_bob_from_db().await.unwrap(); 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, - } + swap } - pub async fn assert_redeemed(&self, state: AliceState) { + pub async fn assert_alice_redeemed(&self, state: AliceState) { assert!(matches!(state, AliceState::BtcRedeemed)); - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); + let btc_balance_after_swap = self + .alice_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); assert_eq!( btc_balance_after_swap, - self.starting_balances.btc + self.swap_amounts.btc + self.alice_swap_factory.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); + 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_refunded(&self, state: AliceState) { + pub async fn assert_alice_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); + 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.monero_wallet.as_ref().inner.refresh().await.unwrap(); - let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); + 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_punished(&self, state: AliceState) { + pub async fn assert_alice_punished(&self, state: AliceState) { assert!(matches!(state, AliceState::BtcPunished)); - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap(); + let btc_balance_after_swap = self + .alice_swap_factory + .bitcoin_wallet + .as_ref() + .balance() + .await + .unwrap(); assert_eq!( btc_balance_after_swap, - self.starting_balances.btc + self.swap_amounts.btc + self.alice_swap_factory.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); - } - - pub fn peer_id(&self) -> PeerId { - self.peer_id.clone() - } - - pub fn listen_address(&self) -> Multiaddr { - self.listen_address.clone() - } -} - -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 { - db_path: PathBuf, - swap_id: Uuid, - - swap_amounts: SwapAmounts, - bitcoin_wallet: Arc, - monero_wallet: Arc, - config: Config, - starting_balances: StartingBalances, - - alice_connect_address: Multiaddr, - alice_connect_peer_id: PeerId, -} - -impl BobHarness { - #[allow(clippy::too_many_arguments)] - async fn new( - config: Config, - swap_amounts: SwapAmounts, - 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 { - db_path, - swap_id, - swap_amounts, - bitcoin_wallet, - monero_wallet, - config, - starting_balances, - alice_connect_address, - alice_connect_peer_id, - } - } - - pub async fn new_bob(&self) -> Bob { - let initial_state = init_bob_state( - self.swap_amounts.btc, - self.swap_amounts.xmr, - self.bitcoin_wallet.clone(), - self.config, - ) - .await; - - let (event_loop, event_loop_handle) = init_bob_event_loop( - self.alice_connect_peer_id.clone(), - 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, - } - } - - 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) - - // 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.alice_connect_peer_id.clone(), - self.alice_connect_address.clone(), + 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 ); - - 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) { + 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 { @@ -370,47 +170,75 @@ impl BobHarness { }; let lock_tx_bitcoin_fee = self + .bob_swap_factory .bitcoin_wallet .transaction_fee(lock_tx_id) .await .unwrap(); - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().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.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee + 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.monero_wallet.as_ref().inner.refresh().await.unwrap(); - let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap(); + 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.starting_balances.xmr + self.swap_amounts.xmr + self.bob_swap_factory.starting_balances.xmr + self.swap_amounts.xmr ); } - pub async fn assert_refunded(&self, state: BobState) { + 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.bitcoin_wallet.as_ref().balance().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.starting_balances.btc + == 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.starting_balances.btc + == self.bob_swap_factory.starting_balances.btc - lock_tx_bitcoin_fee - bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE); @@ -418,11 +246,20 @@ impl BobHarness { // 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); + 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_punished(&self, state: BobState) { + 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 { @@ -430,26 +267,123 @@ impl BobHarness { }; let lock_tx_bitcoin_fee = self + .bob_swap_factory .bitcoin_wallet .transaction_fee(lock_tx_id) .await .unwrap(); - let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().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.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee + self.bob_swap_factory.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); + 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 + ); } } -#[derive(Debug, Clone)] -struct StartingBalances { - pub xmr: monero::Amount, - pub btc: bitcoin::Amount, +pub async fn setup_test(testfn: T) +where + T: Fn(TestContext) -> F, + F: Future, +{ + let cli = Cli::default(); + + let _guard = init_tracing(); + + let (monero, containers) = testutils::init_containers(&cli).await; + + let swap_amounts = SwapAmounts { + btc: bitcoin::Amount::from_sat(1_000_000), + xmr: monero::Amount::from_piconero(1_000_000_000_000), + }; + + let config = Config::regtest(); + + let alice_starting_balances = StartingBalances { + 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 = alice::SwapFactory::new( + Seed::random().unwrap(), + config, + Uuid::new_v4(), + alice_bitcoin_wallet, + alice_monero_wallet, + alice_starting_balances, + tempdir().unwrap().path().to_path_buf(), + listen_address, + ) + .await; + + let bob_starting_balances = StartingBalances { + xmr: monero::Amount::ZERO, + 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 = bob::SwapFactory::new( + Seed::random().unwrap(), + tempdir().unwrap().path().to_path_buf(), + Uuid::new_v4(), + bob_bitcoin_wallet, + bob_monero_wallet, + config, + bob_starting_balances, + alice_swap_factory.listen_address(), + alice_swap_factory.peer_id(), + ); + + let test = TestContext { + swap_amounts, + alice_swap_factory, + bob_swap_factory, + }; + + testfn(test).await } async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) { @@ -498,87 +432,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 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() -} - -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( - 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() -} - // This is just to keep the containers alive #[allow(dead_code)] struct Containers<'a> {