Make the factory code usable in production

- Introduce Test abstraction instead of tow harnesses, move test specific data into Test
- Change the abstraction from actors to swap, because we are creating swaps, not actors
- rename actor::swap  to run, because we are running a swap
This commit is contained in:
Daniel Karzel 2021-01-18 19:56:43 +11:00
parent e4795fa4ee
commit 8bf467b550
12 changed files with 568 additions and 564 deletions

View File

@ -300,15 +300,17 @@ async fn alice_swap(
let (mut event_loop, handle) = let (mut event_loop, handle) =
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?; alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?;
let swap = alice::swap::swap( let swap = alice::Swap {
state, state,
handle, event_loop_handle: handle,
bitcoin_wallet.clone(), bitcoin_wallet,
monero_wallet.clone(), monero_wallet,
config, config,
swap_id, swap_id,
db, db,
); };
let swap = alice::swap::run(swap);
tokio::spawn(async move { event_loop.run().await }); tokio::spawn(async move { event_loop.run().await });
swap.await swap.await
@ -331,15 +333,16 @@ async fn bob_swap(
let (event_loop, handle) = let (event_loop, handle) =
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)?; bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)?;
let swap = bob::swap::swap( let swap = bob::Swap {
state, state,
handle, event_loop_handle: handle,
db, db,
bitcoin_wallet.clone(), bitcoin_wallet,
monero_wallet.clone(), monero_wallet,
OsRng,
swap_id, swap_id,
); };
let swap = bob::swap::run(swap);
tokio::spawn(event_loop.run()); tokio::spawn(event_loop.run());
swap.await swap.await

View File

@ -9,6 +9,7 @@ use libp2p::{
use tracing::{debug, info}; use tracing::{debug, info};
use crate::{ use crate::{
bitcoin, monero,
network::{ network::{
peer_tracker::{self, PeerTracker}, peer_tracker::{self, PeerTracker},
request_response::AliceToBob, request_response::AliceToBob,
@ -26,8 +27,11 @@ pub use self::{
message1::Message1, message1::Message1,
message2::Message2, message2::Message2,
state::*, state::*,
swap::{run_until, swap}, swap::{run, run_until},
}; };
use crate::{config::Config, database::Database};
use std::sync::Arc;
use uuid::Uuid;
mod amounts; mod amounts;
pub mod event_loop; pub mod event_loop;
@ -39,6 +43,16 @@ pub mod state;
mod steps; mod steps;
pub mod swap; pub mod swap;
pub struct Swap {
pub state: AliceState,
pub event_loop_handle: EventLoopHandle,
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
pub monero_wallet: Arc<monero::Wallet>,
pub config: Config,
pub swap_id: Uuid,
pub db: Database,
}
pub type Swarm = libp2p::Swarm<Behaviour>; pub type Swarm = libp2p::Swarm<Behaviour>;
pub fn new_swarm( pub fn new_swarm(

View File

@ -15,18 +15,23 @@ use crate::{
bitcoin, bitcoin,
bitcoin::{TransactionBlockHeight, WatchForRawTransaction}, bitcoin::{TransactionBlockHeight, WatchForRawTransaction},
config::Config, config::Config,
database::{Database, Swap}, database,
database::Database,
monero, monero,
monero::CreateWalletForOutput, monero::CreateWalletForOutput,
protocol::alice::{ protocol::{
event_loop::EventLoopHandle, alice,
steps::{ alice::{
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction, event_loop::EventLoopHandle,
extract_monero_private_key, lock_xmr, negotiate, publish_bitcoin_punish_transaction, steps::{
publish_bitcoin_redeem_transaction, publish_cancel_transaction, build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund, wait_for_locked_bitcoin, 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, ExpiredTimelocks,
}; };
@ -35,28 +40,6 @@ trait Rng: RngCore + CryptoRng + Send {}
impl<T> Rng for T where T: RngCore + CryptoRng + Send {} impl<T> Rng for T where T: RngCore + CryptoRng + Send {}
pub async fn swap(
state: AliceState,
event_loop_handle: EventLoopHandle,
bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>,
config: Config,
swap_id: Uuid,
db: Database,
) -> Result<AliceState> {
run_until(
state,
is_complete,
event_loop_handle,
bitcoin_wallet,
monero_wallet,
config,
swap_id,
db,
)
.await
}
pub fn is_complete(state: &AliceState) -> bool { pub fn is_complete(state: &AliceState) -> bool {
matches!( matches!(
state, state,
@ -81,10 +64,31 @@ pub fn is_encsig_learned(state: &AliceState) -> bool {
) )
} }
pub async fn run(swap: alice::Swap) -> Result<AliceState> {
run_until(swap, is_complete).await
}
pub async fn run_until(
swap: alice::Swap,
is_target_state: fn(&AliceState) -> bool,
) -> Result<AliceState> {
do_run_until(
swap.state,
is_target_state,
swap.event_loop_handle,
swap.bitcoin_wallet,
swap.monero_wallet,
swap.config,
swap.swap_id,
swap.db,
)
.await
}
// State machine driver for swap execution // State machine driver for swap execution
#[async_recursion] #[async_recursion]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn run_until( async fn do_run_until(
state: AliceState, state: AliceState,
is_target_state: fn(&AliceState) -> bool, is_target_state: fn(&AliceState) -> bool,
mut event_loop_handle: EventLoopHandle, mut event_loop_handle: EventLoopHandle,
@ -110,9 +114,9 @@ pub async fn run_until(
}; };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -153,9 +157,9 @@ pub async fn run_until(
}; };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -194,9 +198,9 @@ pub async fn run_until(
}; };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -232,9 +236,9 @@ pub async fn run_until(
}; };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -270,9 +274,9 @@ pub async fn run_until(
let state = AliceState::CancelTimelockExpired { state3 }; let state = AliceState::CancelTimelockExpired { state3 };
let db_state = (&state).into(); 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?; .await?;
return run_until( return do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -298,9 +302,9 @@ pub async fn run_until(
let state = AliceState::BtcRedeemed; let state = AliceState::BtcRedeemed;
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -325,9 +329,9 @@ pub async fn run_until(
let state = AliceState::BtcCancelled { state3, tx_cancel }; let state = AliceState::BtcCancelled { state3, tx_cancel };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -358,10 +362,10 @@ pub async fn run_until(
None => { None => {
let state = AliceState::BtcPunishable { tx_refund, state3 }; let state = AliceState::BtcPunishable { tx_refund, state3 };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -384,9 +388,9 @@ pub async fn run_until(
let state = AliceState::BtcRefunded { spend_key, state3 }; let state = AliceState::BtcRefunded { spend_key, state3 };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -409,7 +413,7 @@ pub async fn run_until(
let state = AliceState::XmrRefunded; let state = AliceState::XmrRefunded;
let db_state = (&state).into(); 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?; .await?;
Ok(state) Ok(state)
} }
@ -439,9 +443,9 @@ pub async fn run_until(
Either::Left(_) => { Either::Left(_) => {
let state = AliceState::BtcPunished; let state = AliceState::BtcPunished;
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -463,9 +467,9 @@ pub async fn run_until(
)?; )?;
let state = AliceState::BtcRefunded { spend_key, state3 }; let state = AliceState::BtcRefunded { spend_key, state3 };
let db_state = (&state).into(); 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?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,

View File

@ -8,7 +8,9 @@ use libp2p::{
use tracing::{debug, info}; use tracing::{debug, info};
use crate::{ use crate::{
bitcoin,
bitcoin::EncryptedSignature, bitcoin::EncryptedSignature,
monero,
network::{ network::{
peer_tracker::{self, PeerTracker}, peer_tracker::{self, PeerTracker},
transport::SwapTransport, transport::SwapTransport,
@ -26,8 +28,11 @@ pub use self::{
message2::Message2, message2::Message2,
message3::Message3, message3::Message3,
state::*, state::*,
swap::{run_until, swap}, swap::{run, run_until},
}; };
use crate::database::Database;
use std::sync::Arc;
use uuid::Uuid;
mod amounts; mod amounts;
pub mod event_loop; pub mod event_loop;
@ -38,6 +43,15 @@ mod message3;
pub mod state; pub mod state;
pub mod swap; pub mod swap;
pub struct Swap {
pub state: BobState,
pub event_loop_handle: bob::EventLoopHandle,
pub db: Database,
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
pub monero_wallet: Arc<monero::Wallet>,
pub swap_id: Uuid,
}
pub type Swarm = libp2p::Swarm<Behaviour>; pub type Swarm = libp2p::Swarm<Behaviour>;
pub fn new_swarm(transport: SwapTransport, behaviour: Behaviour) -> Result<Swarm> { pub fn new_swarm(transport: SwapTransport, behaviour: Behaviour) -> Result<Swarm> {

View File

@ -13,33 +13,7 @@ use crate::{
protocol::bob::{self, event_loop::EventLoopHandle, state::*}, protocol::bob::{self, event_loop::EventLoopHandle, state::*},
ExpiredTimelocks, SwapAmounts, ExpiredTimelocks, SwapAmounts,
}; };
use ecdsa_fun::fun::rand_core::OsRng;
// TODO(Franck): Make this a method on a struct
#[allow(clippy::too_many_arguments)]
pub async fn swap<R>(
state: BobState,
event_loop_handle: EventLoopHandle,
db: Database,
bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>,
rng: R,
swap_id: Uuid,
) -> Result<BobState>
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 { pub fn is_complete(state: &BobState) -> bool {
matches!( matches!(
@ -63,10 +37,32 @@ pub fn is_encsig_sent(state: &BobState) -> bool {
matches!(state, BobState::EncSigSent(..)) matches!(state, BobState::EncSigSent(..))
} }
#[allow(clippy::too_many_arguments)]
pub async fn run(swap: bob::Swap) -> Result<BobState> {
run_until(swap, is_complete).await
}
pub async fn run_until(
swap: bob::Swap,
is_target_state: fn(&BobState) -> bool,
) -> Result<BobState> {
do_run_until(
swap.state,
is_target_state,
swap.event_loop_handle,
swap.db,
swap.bitcoin_wallet,
swap.monero_wallet,
OsRng,
swap.swap_id,
)
.await
}
// State machine driver for swap execution // State machine driver for swap execution
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[async_recursion] #[async_recursion]
pub async fn run_until<R>( async fn do_run_until<R>(
state: BobState, state: BobState,
is_target_state: fn(&BobState) -> bool, is_target_state: fn(&BobState) -> bool,
mut event_loop_handle: EventLoopHandle, mut event_loop_handle: EventLoopHandle,
@ -99,7 +95,7 @@ where
let state = BobState::Negotiated(state2); let state = BobState::Negotiated(state2);
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -120,7 +116,7 @@ where
let state = BobState::BtcLocked(state3); let state = BobState::BtcLocked(state3);
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -185,7 +181,7 @@ where
}; };
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -226,7 +222,7 @@ where
}; };
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -261,7 +257,7 @@ where
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -282,7 +278,7 @@ where
}; };
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -307,7 +303,7 @@ where
db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) db.insert_latest_state(swap_id, Swap::Bob(state.clone().into()))
.await?; .await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,
@ -336,7 +332,7 @@ where
let db_state = state.clone().into(); let db_state = state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until( do_run_until(
state, state,
is_target_state, is_target_state,
event_loop_handle, event_loop_handle,

View File

@ -1,4 +1,3 @@
use rand::rngs::OsRng;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
use tokio::join; use tokio::join;
@ -8,33 +7,17 @@ pub mod testutils;
#[tokio::test] #[tokio::test]
async fn happy_path() { async fn happy_path() {
testutils::test(|alice_harness, bob_harness| async move { testutils::init(|test| async move {
let alice = alice_harness.new_alice().await; let alice_swap = test.new_swap_as_alice().await;
let bob = bob_harness.new_bob().await; let bob_swap = test.new_swap_as_bob().await;
let alice_swap = alice::swap( let alice = alice::run(alice_swap);
alice.state,
alice.event_loop_handle,
alice.bitcoin_wallet.clone(),
alice.monero_wallet.clone(),
alice.config,
alice.swap_id,
alice.db,
);
let bob_swap = bob::swap( let bob = bob::run(bob_swap);
bob.state, let (alice_state, bob_state) = join!(alice, bob);
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);
alice_harness.assert_redeemed(alice_state.unwrap()).await; test.assert_alice_redeemed(alice_state.unwrap()).await;
bob_harness.assert_redeemed(bob_state.unwrap()).await; test.assert_bob_redeemed(bob_state.unwrap()).await;
}) })
.await; .await;
} }

View File

@ -1,58 +1,30 @@
use rand::rngs::OsRng;
use swap::protocol::{alice, alice::AliceState, bob}; use swap::protocol::{alice, alice::AliceState, bob};
pub mod testutils; pub mod testutils;
#[tokio::test] #[tokio::test]
async fn given_alice_restarts_after_encsig_is_learned_resume_swap() { async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
testutils::test(|alice_harness, bob_harness| async move { testutils::init(|test| async move {
let alice = alice_harness.new_alice().await; let alice_swap = test.new_swap_as_alice().await;
let bob = bob_harness.new_bob().await; let bob_swap = test.new_swap_as_bob().await;
let bob_swap = bob::swap( let bob = bob::run(bob_swap);
bob.state, let bob_handle = tokio::spawn(bob);
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( let alice_state = alice::run_until(alice_swap, alice::swap::is_encsig_learned)
alice.state, .await
alice::swap::is_encsig_learned, .unwrap();
alice.event_loop_handle,
alice.bitcoin_wallet.clone(),
alice.monero_wallet.clone(),
alice.config,
alice.swap_id,
alice.db,
)
.await
.unwrap();
assert!(matches!(alice_state, AliceState::EncSigLearned {..})); assert!(matches!(alice_state, AliceState::EncSigLearned {..}));
let alice = alice_harness.recover_alice_from_db().await; let alice_swap = test.recover_alice_from_db().await;
assert!(matches!(alice.state, AliceState::EncSigLearned {..})); assert!(matches!(alice_swap.state, AliceState::EncSigLearned {..}));
let alice_state = alice::swap( let alice_state = alice::run(alice_swap).await.unwrap();
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; test.assert_alice_redeemed(alice_state).await;
let bob_state = bob_swap_handle.await.unwrap(); let bob_state = bob_handle.await.unwrap();
bob_harness.assert_redeemed(bob_state.unwrap()).await test.assert_bob_redeemed(bob_state.unwrap()).await
}) })
.await; .await;
} }

View File

@ -1,59 +1,31 @@
use rand::rngs::OsRng;
use swap::protocol::{alice, bob, bob::BobState}; use swap::protocol::{alice, bob, bob::BobState};
pub mod testutils; pub mod testutils;
#[tokio::test] #[tokio::test]
async fn given_bob_restarts_after_encsig_is_sent_resume_swap() { async fn given_bob_restarts_after_encsig_is_sent_resume_swap() {
testutils::test(|alice_harness, bob_harness| async move { testutils::init(|test| async move {
let alice = alice_harness.new_alice().await; let alice_swap = test.new_swap_as_alice().await;
let bob = bob_harness.new_bob().await; let bob_swap = test.new_swap_as_bob().await;
let alice_swap = alice::swap( let alice = alice::run(alice_swap);
alice.state, let alice_handle = tokio::spawn(alice);
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( let bob_state = bob::run_until(bob_swap, bob::swap::is_encsig_sent)
bob.state, .await
bob::swap::is_encsig_sent, .unwrap();
bob.event_loop_handle,
bob.db,
bob.bitcoin_wallet.clone(),
bob.monero_wallet.clone(),
OsRng,
bob.swap_id,
)
.await
.unwrap();
assert!(matches!(bob_state, BobState::EncSigSent {..})); assert!(matches!(bob_state, BobState::EncSigSent {..}));
let bob = bob_harness.recover_bob_from_db().await; let bob_swap = test.recover_bob_from_db().await;
assert!(matches!(bob.state, BobState::EncSigSent {..})); assert!(matches!(bob_swap.state, BobState::EncSigSent {..}));
let bob_state = bob::swap( let bob_state = bob::run(bob_swap).await.unwrap();
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; test.assert_bob_redeemed(bob_state).await;
let alice_state = alice_swap_handle.await.unwrap(); let alice_state = alice_handle.await.unwrap();
alice_harness.assert_redeemed(alice_state.unwrap()).await; test.assert_alice_redeemed(alice_state.unwrap()).await;
}) })
.await; .await;
} }

View File

@ -1,59 +1,32 @@
use rand::rngs::OsRng; use swap::protocol::{
use swap::protocol::{alice, bob, bob::BobState}; alice, bob,
bob::{swap::is_xmr_locked, BobState},
};
pub mod testutils; pub mod testutils;
#[tokio::test] #[tokio::test]
async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
testutils::test(|alice_harness, bob_harness| async move { testutils::init(|test| async move {
let alice = alice_harness.new_alice().await; let alice_swap = test.new_swap_as_alice().await;
let bob = bob_harness.new_bob().await; let bob_swap = test.new_swap_as_bob().await;
let alice_swap = alice::swap( let alice_handle = alice::run(alice_swap);
alice.state, let alice_swap_handle = tokio::spawn(alice_handle);
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( let bob_state = bob::run_until(bob_swap, is_xmr_locked).await.unwrap();
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();
assert!(matches!(bob_state, BobState::XmrLocked {..})); assert!(matches!(bob_state, BobState::XmrLocked {..}));
let bob = bob_harness.recover_bob_from_db().await; let bob_swap = test.recover_bob_from_db().await;
assert!(matches!(bob.state, BobState::XmrLocked {..})); assert!(matches!(bob_swap.state, BobState::XmrLocked {..}));
let bob_state = bob::swap( let bob_state = bob::run(bob_swap).await.unwrap();
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; test.assert_bob_redeemed(bob_state).await;
let alice_state = alice_swap_handle.await.unwrap(); let alice_state = alice_swap_handle.await.unwrap();
alice_harness.assert_redeemed(alice_state.unwrap()).await; test.assert_alice_redeemed(alice_state.unwrap()).await;
}) })
.await; .await;
} }

View File

@ -1,5 +1,7 @@
use rand::rngs::OsRng; use swap::protocol::{
use swap::protocol::{alice, bob, bob::BobState}; alice, bob,
bob::{swap::is_btc_locked, BobState},
};
pub mod testutils; pub mod testutils;
@ -7,57 +9,28 @@ pub mod testutils;
/// the encsig and fail to refund or redeem. Alice punishes. /// the encsig and fail to refund or redeem. Alice punishes.
#[tokio::test] #[tokio::test]
async fn alice_punishes_if_bob_never_acts_after_fund() { async fn alice_punishes_if_bob_never_acts_after_fund() {
testutils::test(|alice_harness, bob_harness| async move { testutils::init(|test| async move {
let alice = alice_harness.new_alice().await; let alice_swap = test.new_swap_as_alice().await;
let bob = bob_harness.new_bob().await; let bob_swap = test.new_swap_as_bob().await;
let alice_swap = alice::swap( let alice = alice::run(alice_swap);
alice.state, let alice_handle = tokio::spawn(alice);
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( let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
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();
assert!(matches!(bob_state, BobState::BtcLocked {..})); assert!(matches!(bob_state, BobState::BtcLocked {..}));
let alice_state = alice_swap_handle.await.unwrap(); let alice_state = alice_handle.await.unwrap();
alice_harness.assert_punished(alice_state.unwrap()).await; test.assert_alice_punished(alice_state.unwrap()).await;
// Restart Bob after Alice punished to ensure Bob transitions to // Restart Bob after Alice punished to ensure Bob transitions to
// punished and does not run indefinitely // punished and does not run indefinitely
let bob = bob_harness.recover_bob_from_db().await; let bob_swap = test.recover_bob_from_db().await;
assert!(matches!(bob.state, BobState::BtcLocked {..})); assert!(matches!(bob_swap.state, BobState::BtcLocked {..}));
let bob_state = bob::swap( let bob_state = bob::run(bob_swap).await.unwrap();
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; test.assert_bob_punished(bob_state).await;
}) })
.await; .await;
} }

View File

@ -1,4 +1,3 @@
use rand::rngs::OsRng;
use swap::protocol::{alice, alice::AliceState, bob}; use swap::protocol::{alice, alice::AliceState, bob};
pub mod testutils; pub mod testutils;
@ -7,60 +6,33 @@ pub mod testutils;
/// then also refunds. /// then also refunds.
#[tokio::test] #[tokio::test]
async fn given_alice_restarts_after_xmr_is_locked_abort_swap() { async fn given_alice_restarts_after_xmr_is_locked_abort_swap() {
testutils::test(|alice_harness, bob_harness| async move { testutils::init(|test| async move {
let alice = alice_harness.new_alice().await; let alice_swap = test.new_swap_as_alice().await;
let bob = bob_harness.new_bob().await; let bob_swap = test.new_swap_as_bob().await;
let bob_swap = bob::swap( let bob = bob::run(bob_swap);
bob.state, let bob_handle = tokio::spawn(bob);
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( let alice_state = alice::run_until(alice_swap, alice::swap::is_xmr_locked)
alice.state, .await
alice::swap::is_xmr_locked, .unwrap();
alice.event_loop_handle,
alice.bitcoin_wallet.clone(),
alice.monero_wallet.clone(),
alice.config,
alice.swap_id,
alice.db,
)
.await
.unwrap();
assert!(matches!(alice_state, AliceState::XmrLocked {..})); assert!(matches!(alice_state, AliceState::XmrLocked {..}));
// Alice does not act, Bob refunds // 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 // Once bob has finished Alice is restarted and refunds as well
let alice = alice_harness.recover_alice_from_db().await; let alice_swap = test.recover_alice_from_db().await;
assert!(matches!(alice.state, AliceState::XmrLocked {..})); assert!(matches!(alice_swap.state, AliceState::XmrLocked {..}));
let alice_state = alice::swap( let alice_state = alice::run(alice_swap).await.unwrap();
alice.state,
alice.event_loop_handle,
alice.bitcoin_wallet.clone(),
alice.monero_wallet.clone(),
alice.config,
alice.swap_id,
alice.db,
)
.await
.unwrap();
// TODO: The test passes like this, but the assertion should be done after Bob // 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! // 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, // 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! // which requires storing the refund_tx_id in the the state!
bob_harness.assert_refunded(bob_state.unwrap()).await; test.assert_bob_refunded(bob_state.unwrap()).await;
alice_harness.assert_refunded(alice_state).await; test.assert_alice_refunded(alice_state).await;
}) })
.await; .await;
} }

View File

@ -22,9 +22,285 @@ use tracing_core::dispatcher::DefaultGuard;
use tracing_log::LogTracer; use tracing_log::LogTracer;
use uuid::Uuid; use uuid::Uuid;
pub async fn test<T, F>(testfn: T) pub struct Test {
swap_amounts: SwapAmounts,
alice_swap_factory: AliceSwapFactory,
bob_swap_factory: BobSwapFactory,
}
impl Test {
pub async fn new_swap_as_alice(&self) -> alice::Swap {
let (swap, mut event_loop) = self
.alice_swap_factory
.new_swap_as_alice(self.swap_amounts)
.await;
tokio::spawn(async move { event_loop.run().await });
swap
}
pub async fn new_swap_as_bob(&self) -> bob::Swap {
let (swap, event_loop) = self
.bob_swap_factory
.new_swap_as_bob(self.swap_amounts)
.await;
tokio::spawn(async move { event_loop.run().await });
swap
}
pub async fn recover_alice_from_db(&self) -> alice::Swap {
let (swap, mut event_loop) = self.alice_swap_factory.recover_alice_from_db().await;
tokio::spawn(async move { event_loop.run().await });
swap
}
pub async fn recover_bob_from_db(&self) -> bob::Swap {
let (swap, event_loop) = self.bob_swap_factory.recover_bob_from_db().await;
tokio::spawn(async move { event_loop.run().await });
swap
}
pub async fn assert_alice_redeemed(&self, state: AliceState) {
assert!(matches!(state, AliceState::BtcRedeemed));
let btc_balance_after_swap = self
.alice_swap_factory
.bitcoin_wallet
.as_ref()
.balance()
.await
.unwrap();
assert_eq!(
btc_balance_after_swap,
self.alice_swap_factory.starting_balances.btc + self.swap_amounts.btc
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
);
let xmr_balance_after_swap = self
.alice_swap_factory
.monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert!(
xmr_balance_after_swap
<= self.alice_swap_factory.starting_balances.xmr - self.swap_amounts.xmr
);
}
pub async fn assert_alice_refunded(&self, state: AliceState) {
assert!(matches!(state, AliceState::XmrRefunded));
let btc_balance_after_swap = self
.alice_swap_factory
.bitcoin_wallet
.as_ref()
.balance()
.await
.unwrap();
assert_eq!(
btc_balance_after_swap,
self.alice_swap_factory.starting_balances.btc
);
// Ensure that Alice's balance is refreshed as we use a newly created wallet
self.alice_swap_factory
.monero_wallet
.as_ref()
.inner
.refresh()
.await
.unwrap();
let xmr_balance_after_swap = self
.alice_swap_factory
.monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr);
}
pub async fn assert_alice_punished(&self, state: AliceState) {
assert!(matches!(state, AliceState::BtcPunished));
let btc_balance_after_swap = self
.alice_swap_factory
.bitcoin_wallet
.as_ref()
.balance()
.await
.unwrap();
assert_eq!(
btc_balance_after_swap,
self.alice_swap_factory.starting_balances.btc + self.swap_amounts.btc
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
);
let xmr_balance_after_swap = self
.alice_swap_factory
.monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert!(
xmr_balance_after_swap
<= self.alice_swap_factory.starting_balances.xmr - self.swap_amounts.xmr
);
}
pub async fn assert_bob_redeemed(&self, state: BobState) {
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
tx_lock_id
} else {
panic!("Bob in unexpected state");
};
let lock_tx_bitcoin_fee = self
.bob_swap_factory
.bitcoin_wallet
.transaction_fee(lock_tx_id)
.await
.unwrap();
let btc_balance_after_swap = self
.bob_swap_factory
.bitcoin_wallet
.as_ref()
.balance()
.await
.unwrap();
assert_eq!(
btc_balance_after_swap,
self.bob_swap_factory.starting_balances.btc
- self.swap_amounts.btc
- lock_tx_bitcoin_fee
);
// Ensure that Bob's balance is refreshed as we use a newly created wallet
self.bob_swap_factory
.monero_wallet
.as_ref()
.inner
.refresh()
.await
.unwrap();
let xmr_balance_after_swap = self
.bob_swap_factory
.monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert_eq!(
xmr_balance_after_swap,
self.bob_swap_factory.starting_balances.xmr + self.swap_amounts.xmr
);
}
pub async fn assert_bob_refunded(&self, state: BobState) {
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
state4.tx_lock_id()
} else {
panic!("Bob in unexpected state");
};
let lock_tx_bitcoin_fee = self
.bob_swap_factory
.bitcoin_wallet
.transaction_fee(lock_tx_id)
.await
.unwrap();
let btc_balance_after_swap = self
.bob_swap_factory
.bitcoin_wallet
.as_ref()
.balance()
.await
.unwrap();
let alice_submitted_cancel = btc_balance_after_swap
== self.bob_swap_factory.starting_balances.btc
- lock_tx_bitcoin_fee
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
let bob_submitted_cancel = btc_balance_after_swap
== self.bob_swap_factory.starting_balances.btc
- lock_tx_bitcoin_fee
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
// The cancel tx can be submitted by both Alice and Bob.
// Since we cannot be sure who submitted it we have to assert accordingly
assert!(alice_submitted_cancel || bob_submitted_cancel);
let xmr_balance_after_swap = self
.bob_swap_factory
.monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert_eq!(
xmr_balance_after_swap,
self.bob_swap_factory.starting_balances.xmr
);
}
pub async fn assert_bob_punished(&self, state: BobState) {
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
tx_lock_id
} else {
panic!("Bob in unexpected state");
};
let lock_tx_bitcoin_fee = self
.bob_swap_factory
.bitcoin_wallet
.transaction_fee(lock_tx_id)
.await
.unwrap();
let btc_balance_after_swap = self
.bob_swap_factory
.bitcoin_wallet
.as_ref()
.balance()
.await
.unwrap();
assert_eq!(
btc_balance_after_swap,
self.bob_swap_factory.starting_balances.btc
- self.swap_amounts.btc
- lock_tx_bitcoin_fee
);
let xmr_balance_after_swap = self
.bob_swap_factory
.monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert_eq!(
xmr_balance_after_swap,
self.bob_swap_factory.starting_balances.xmr
);
}
}
pub async fn init<T, F>(testfn: T)
where where
T: Fn(AliceHarness, BobHarness) -> F, T: Fn(Test) -> F,
F: Future<Output = ()>, F: Future<Output = ()>,
{ {
let cli = Cli::default(); let cli = Cli::default();
@ -44,9 +320,8 @@ where
xmr: swap_amounts.xmr * 10, xmr: swap_amounts.xmr * 10,
btc: bitcoin::Amount::ZERO, btc: bitcoin::Amount::ZERO,
}; };
let alice_harness = AliceHarness::new( let alice_swap_factory = AliceSwapFactory::new(
config, config,
swap_amounts,
Uuid::new_v4(), Uuid::new_v4(),
&monero, &monero,
&containers.bitcoind, &containers.bitcoind,
@ -59,32 +334,27 @@ where
btc: swap_amounts.btc * 10, btc: swap_amounts.btc * 10,
}; };
let bob_harness = BobHarness::new( let bob_swap_factory = BobSwapFactory::new(
config, config,
swap_amounts,
Uuid::new_v4(), Uuid::new_v4(),
&monero, &monero,
&containers.bitcoind, &containers.bitcoind,
bob_starting_balances, bob_starting_balances,
alice_harness.listen_address(), alice_swap_factory.listen_address(),
alice_harness.peer_id(), alice_swap_factory.peer_id(),
) )
.await; .await;
testfn(alice_harness, bob_harness).await let test = Test {
swap_amounts,
alice_swap_factory,
bob_swap_factory,
};
testfn(test).await
} }
pub struct Alice { pub struct AliceSwapFactory {
pub state: AliceState,
pub event_loop_handle: alice::EventLoopHandle,
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
pub monero_wallet: Arc<monero::Wallet>,
pub config: Config,
pub swap_id: Uuid,
pub db: Database,
}
pub struct AliceHarness {
listen_address: Multiaddr, listen_address: Multiaddr,
peer_id: PeerId, peer_id: PeerId,
@ -92,17 +362,15 @@ pub struct AliceHarness {
db_path: PathBuf, db_path: PathBuf,
swap_id: Uuid, swap_id: Uuid,
swap_amounts: SwapAmounts,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
config: Config, config: Config,
starting_balances: StartingBalances, starting_balances: StartingBalances,
} }
impl AliceHarness { impl AliceSwapFactory {
async fn new( async fn new(
config: Config, config: Config,
swap_amounts: SwapAmounts,
swap_id: Uuid, swap_id: Uuid,
monero: &Monero, monero: &Monero,
bitcoind: &Bitcoind<'_>, bitcoind: &Bitcoind<'_>,
@ -132,7 +400,6 @@ impl AliceHarness {
listen_address, listen_address,
peer_id, peer_id,
swap_id, swap_id,
swap_amounts,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
@ -140,39 +407,37 @@ impl AliceHarness {
} }
} }
pub async fn new_alice(&self) -> Alice { pub async fn new_swap_as_alice(
&self,
swap_amounts: SwapAmounts,
) -> (alice::Swap, alice::EventLoop) {
let initial_state = init_alice_state( let initial_state = init_alice_state(
self.swap_amounts.btc, swap_amounts.btc,
self.swap_amounts.xmr, swap_amounts.xmr,
self.bitcoin_wallet.clone(), self.bitcoin_wallet.clone(),
self.config, self.config,
) )
.await; .await;
let (mut event_loop, event_loop_handle) = let (event_loop, event_loop_handle) =
init_alice_event_loop(self.listen_address.clone(), self.seed); init_alice_event_loop(self.listen_address.clone(), self.seed);
tokio::spawn(async move { event_loop.run().await });
let db = Database::open(self.db_path.as_path()).unwrap(); let db = Database::open(self.db_path.as_path()).unwrap();
(
Alice { alice::Swap {
event_loop_handle, event_loop_handle,
bitcoin_wallet: self.bitcoin_wallet.clone(), bitcoin_wallet: self.bitcoin_wallet.clone(),
monero_wallet: self.monero_wallet.clone(), monero_wallet: self.monero_wallet.clone(),
config: self.config, config: self.config,
db, db,
state: initial_state, state: initial_state,
swap_id: self.swap_id, swap_id: self.swap_id,
} },
event_loop,
)
} }
pub async fn recover_alice_from_db(&self) -> Alice { pub async fn recover_alice_from_db(&self) -> (alice::Swap, alice::EventLoop) {
// 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 // reopen the existing database
let db = Database::open(self.db_path.clone().as_path()).unwrap(); let db = Database::open(self.db_path.clone().as_path()).unwrap();
@ -183,60 +448,21 @@ impl AliceHarness {
unreachable!() unreachable!()
}; };
let (mut event_loop, event_loop_handle) = let (event_loop, event_loop_handle) =
init_alice_event_loop(self.listen_address.clone(), self.seed); init_alice_event_loop(self.listen_address.clone(), self.seed);
tokio::spawn(async move { event_loop.run().await }); (
alice::Swap {
Alice { state: resume_state,
state: resume_state, event_loop_handle,
event_loop_handle, bitcoin_wallet: self.bitcoin_wallet.clone(),
bitcoin_wallet: self.bitcoin_wallet.clone(), monero_wallet: self.monero_wallet.clone(),
monero_wallet: self.monero_wallet.clone(), config: self.config,
config: self.config, swap_id: self.swap_id,
swap_id: self.swap_id, db,
db, },
} event_loop,
} )
pub async fn assert_redeemed(&self, state: AliceState) {
assert!(matches!(state, AliceState::BtcRedeemed));
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.starting_balances.btc + self.swap_amounts.btc
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
);
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
assert!(xmr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr);
}
pub async fn assert_refunded(&self, state: AliceState) {
assert!(matches!(state, AliceState::XmrRefunded));
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(btc_balance_after_swap, self.starting_balances.btc);
// Ensure that Alice's balance is refreshed as we use a newly created wallet
self.monero_wallet.as_ref().inner.refresh().await.unwrap();
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr);
}
pub async fn assert_punished(&self, state: AliceState) {
assert!(matches!(state, AliceState::BtcPunished));
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.starting_balances.btc + self.swap_amounts.btc
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
);
let xnr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
assert!(xnr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr);
} }
pub fn peer_id(&self) -> PeerId { pub fn peer_id(&self) -> PeerId {
@ -248,20 +474,10 @@ impl AliceHarness {
} }
} }
pub struct Bob { pub struct BobSwapFactory {
pub state: BobState,
pub event_loop_handle: bob::EventLoopHandle,
pub db: Database,
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
pub monero_wallet: Arc<monero::Wallet>,
pub swap_id: Uuid,
}
pub struct BobHarness {
db_path: PathBuf, db_path: PathBuf,
swap_id: Uuid, swap_id: Uuid,
swap_amounts: SwapAmounts,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
config: Config, config: Config,
@ -271,11 +487,10 @@ pub struct BobHarness {
alice_connect_peer_id: PeerId, alice_connect_peer_id: PeerId,
} }
impl BobHarness { impl BobSwapFactory {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn new( async fn new(
config: Config, config: Config,
swap_amounts: SwapAmounts,
swap_id: Uuid, swap_id: Uuid,
monero: &Monero, monero: &Monero,
bitcoind: &Bitcoind<'_>, bitcoind: &Bitcoind<'_>,
@ -291,7 +506,6 @@ impl BobHarness {
Self { Self {
db_path, db_path,
swap_id, swap_id,
swap_amounts,
bitcoin_wallet, bitcoin_wallet,
monero_wallet, monero_wallet,
config, config,
@ -301,10 +515,10 @@ impl BobHarness {
} }
} }
pub async fn new_bob(&self) -> Bob { pub async fn new_swap_as_bob(&self, swap_amounts: SwapAmounts) -> (bob::Swap, bob::EventLoop) {
let initial_state = init_bob_state( let initial_state = init_bob_state(
self.swap_amounts.btc, swap_amounts.btc,
self.swap_amounts.xmr, swap_amounts.xmr,
self.bitcoin_wallet.clone(), self.bitcoin_wallet.clone(),
self.config, self.config,
) )
@ -315,26 +529,22 @@ impl BobHarness {
self.alice_connect_address.clone(), self.alice_connect_address.clone(),
); );
tokio::spawn(async move { event_loop.run().await });
let db = Database::open(self.db_path.as_path()).unwrap(); let db = Database::open(self.db_path.as_path()).unwrap();
Bob { (
state: initial_state, bob::Swap {
event_loop_handle, state: initial_state,
db, event_loop_handle,
bitcoin_wallet: self.bitcoin_wallet.clone(), db,
monero_wallet: self.monero_wallet.clone(), bitcoin_wallet: self.bitcoin_wallet.clone(),
swap_id: self.swap_id, monero_wallet: self.monero_wallet.clone(),
} swap_id: self.swap_id,
},
event_loop,
)
} }
pub async fn recover_bob_from_db(&self) -> Bob { pub async fn recover_bob_from_db(&self) -> (bob::Swap, bob::EventLoop) {
// 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 // reopen the existing database
let db = Database::open(self.db_path.clone().as_path()).unwrap(); let db = Database::open(self.db_path.clone().as_path()).unwrap();
@ -350,99 +560,17 @@ impl BobHarness {
self.alice_connect_address.clone(), self.alice_connect_address.clone(),
); );
tokio::spawn(async move { event_loop.run().await }); (
bob::Swap {
Bob { state: resume_state,
state: resume_state, event_loop_handle,
event_loop_handle, db,
db, bitcoin_wallet: self.bitcoin_wallet.clone(),
bitcoin_wallet: self.bitcoin_wallet.clone(), monero_wallet: self.monero_wallet.clone(),
monero_wallet: self.monero_wallet.clone(), swap_id: self.swap_id,
swap_id: self.swap_id, },
} event_loop,
} )
pub async fn assert_redeemed(&self, state: BobState) {
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
tx_lock_id
} else {
panic!("Bob in unexpected state");
};
let lock_tx_bitcoin_fee = self
.bitcoin_wallet
.transaction_fee(lock_tx_id)
.await
.unwrap();
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
);
// Ensure that Bob's balance is refreshed as we use a newly created wallet
self.monero_wallet.as_ref().inner.refresh().await.unwrap();
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
assert_eq!(
xmr_balance_after_swap,
self.starting_balances.xmr + self.swap_amounts.xmr
);
}
pub async fn assert_refunded(&self, state: BobState) {
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
state4.tx_lock_id()
} else {
panic!("Bob in unexpected state");
};
let lock_tx_bitcoin_fee = self
.bitcoin_wallet
.transaction_fee(lock_tx_id)
.await
.unwrap();
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
let alice_submitted_cancel = btc_balance_after_swap
== self.starting_balances.btc
- lock_tx_bitcoin_fee
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
let bob_submitted_cancel = btc_balance_after_swap
== self.starting_balances.btc
- lock_tx_bitcoin_fee
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
// The cancel tx can be submitted by both Alice and Bob.
// Since we cannot be sure who submitted it we have to assert accordingly
assert!(alice_submitted_cancel || bob_submitted_cancel);
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr);
}
pub async fn assert_punished(&self, state: BobState) {
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
tx_lock_id
} else {
panic!("Bob in unexpected state");
};
let lock_tx_bitcoin_fee = self
.bitcoin_wallet
.transaction_fee(lock_tx_id)
.await
.unwrap();
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
);
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr);
} }
} }