mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-24 15:09:35 -05:00
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:
parent
e4795fa4ee
commit
8bf467b550
@ -300,15 +300,17 @@ async fn alice_swap(
|
||||
let (mut event_loop, handle) =
|
||||
alice::event_loop::EventLoop::new(alice_transport, alice_behaviour, listen_addr)?;
|
||||
|
||||
let swap = alice::swap::swap(
|
||||
let swap = alice::Swap {
|
||||
state,
|
||||
handle,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet.clone(),
|
||||
event_loop_handle: handle,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
swap_id,
|
||||
db,
|
||||
);
|
||||
};
|
||||
|
||||
let swap = alice::swap::run(swap);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
swap.await
|
||||
@ -331,15 +333,16 @@ async fn bob_swap(
|
||||
let (event_loop, handle) =
|
||||
bob::event_loop::EventLoop::new(bob_transport, bob_behaviour, alice_peer_id, alice_addr)?;
|
||||
|
||||
let swap = bob::swap::swap(
|
||||
let swap = bob::Swap {
|
||||
state,
|
||||
handle,
|
||||
event_loop_handle: handle,
|
||||
db,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet.clone(),
|
||||
OsRng,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
swap_id,
|
||||
);
|
||||
};
|
||||
|
||||
let swap = bob::swap::run(swap);
|
||||
|
||||
tokio::spawn(event_loop.run());
|
||||
swap.await
|
||||
|
@ -9,6 +9,7 @@ use libp2p::{
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::{
|
||||
bitcoin, monero,
|
||||
network::{
|
||||
peer_tracker::{self, PeerTracker},
|
||||
request_response::AliceToBob,
|
||||
@ -26,8 +27,11 @@ pub use self::{
|
||||
message1::Message1,
|
||||
message2::Message2,
|
||||
state::*,
|
||||
swap::{run_until, swap},
|
||||
swap::{run, run_until},
|
||||
};
|
||||
use crate::{config::Config, database::Database};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod amounts;
|
||||
pub mod event_loop;
|
||||
@ -39,6 +43,16 @@ pub mod state;
|
||||
mod steps;
|
||||
pub mod swap;
|
||||
|
||||
pub struct Swap {
|
||||
pub state: AliceState,
|
||||
pub event_loop_handle: EventLoopHandle,
|
||||
pub bitcoin_wallet: Arc<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 fn new_swarm(
|
||||
|
@ -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<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 {
|
||||
matches!(
|
||||
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
|
||||
#[async_recursion]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run_until(
|
||||
async fn do_run_until(
|
||||
state: AliceState,
|
||||
is_target_state: fn(&AliceState) -> bool,
|
||||
mut event_loop_handle: EventLoopHandle,
|
||||
@ -110,9 +114,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -153,9 +157,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -194,9 +198,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -232,9 +236,9 @@ pub async fn run_until(
|
||||
};
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -270,9 +274,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::CancelTimelockExpired { state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
return run_until(
|
||||
return do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -298,9 +302,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::BtcRedeemed;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -325,9 +329,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::BtcCancelled { state3, tx_cancel };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -358,10 +362,10 @@ pub async fn run_until(
|
||||
None => {
|
||||
let state = AliceState::BtcPunishable { tx_refund, state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -384,9 +388,9 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::BtcRefunded { spend_key, state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -409,7 +413,7 @@ pub async fn run_until(
|
||||
|
||||
let state = AliceState::XmrRefunded;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
Ok(state)
|
||||
}
|
||||
@ -439,9 +443,9 @@ pub async fn run_until(
|
||||
Either::Left(_) => {
|
||||
let state = AliceState::BtcPunished;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -463,9 +467,9 @@ pub async fn run_until(
|
||||
)?;
|
||||
let state = AliceState::BtcRefunded { spend_key, state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
|
||||
.await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
|
@ -8,7 +8,9 @@ use libp2p::{
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::{
|
||||
bitcoin,
|
||||
bitcoin::EncryptedSignature,
|
||||
monero,
|
||||
network::{
|
||||
peer_tracker::{self, PeerTracker},
|
||||
transport::SwapTransport,
|
||||
@ -26,8 +28,11 @@ pub use self::{
|
||||
message2::Message2,
|
||||
message3::Message3,
|
||||
state::*,
|
||||
swap::{run_until, swap},
|
||||
swap::{run, run_until},
|
||||
};
|
||||
use crate::database::Database;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod amounts;
|
||||
pub mod event_loop;
|
||||
@ -38,6 +43,15 @@ mod message3;
|
||||
pub mod state;
|
||||
pub mod swap;
|
||||
|
||||
pub struct Swap {
|
||||
pub state: BobState,
|
||||
pub event_loop_handle: bob::EventLoopHandle,
|
||||
pub db: Database,
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
pub type Swarm = libp2p::Swarm<Behaviour>;
|
||||
|
||||
pub fn new_swarm(transport: SwapTransport, behaviour: Behaviour) -> Result<Swarm> {
|
||||
|
@ -13,33 +13,7 @@ use crate::{
|
||||
protocol::bob::{self, event_loop::EventLoopHandle, state::*},
|
||||
ExpiredTimelocks, SwapAmounts,
|
||||
};
|
||||
|
||||
// TODO(Franck): Make this a method on a struct
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn swap<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
|
||||
}
|
||||
use ecdsa_fun::fun::rand_core::OsRng;
|
||||
|
||||
pub fn is_complete(state: &BobState) -> bool {
|
||||
matches!(
|
||||
@ -63,10 +37,32 @@ pub fn is_encsig_sent(state: &BobState) -> bool {
|
||||
matches!(state, BobState::EncSigSent(..))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run(swap: bob::Swap) -> Result<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
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[async_recursion]
|
||||
pub async fn run_until<R>(
|
||||
async fn do_run_until<R>(
|
||||
state: BobState,
|
||||
is_target_state: fn(&BobState) -> bool,
|
||||
mut event_loop_handle: EventLoopHandle,
|
||||
@ -99,7 +95,7 @@ where
|
||||
let state = BobState::Negotiated(state2);
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -120,7 +116,7 @@ where
|
||||
let state = BobState::BtcLocked(state3);
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -185,7 +181,7 @@ where
|
||||
};
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -226,7 +222,7 @@ where
|
||||
};
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -261,7 +257,7 @@ where
|
||||
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -282,7 +278,7 @@ where
|
||||
};
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -307,7 +303,7 @@ where
|
||||
db.insert_latest_state(swap_id, Swap::Bob(state.clone().into()))
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
@ -336,7 +332,7 @@ where
|
||||
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
run_until(
|
||||
do_run_until(
|
||||
state,
|
||||
is_target_state,
|
||||
event_loop_handle,
|
||||
|
@ -1,4 +1,3 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, bob};
|
||||
use tokio::join;
|
||||
|
||||
@ -8,33 +7,17 @@ pub mod testutils;
|
||||
|
||||
#[tokio::test]
|
||||
async fn happy_path() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::init(|test| async move {
|
||||
let alice_swap = test.new_swap_as_alice().await;
|
||||
let bob_swap = test.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice = alice::run(alice_swap);
|
||||
|
||||
let bob_swap = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
);
|
||||
let (alice_state, bob_state) = join!(alice_swap, bob_swap);
|
||||
let bob = bob::run(bob_swap);
|
||||
let (alice_state, bob_state) = join!(alice, bob);
|
||||
|
||||
alice_harness.assert_redeemed(alice_state.unwrap()).await;
|
||||
bob_harness.assert_redeemed(bob_state.unwrap()).await;
|
||||
test.assert_alice_redeemed(alice_state.unwrap()).await;
|
||||
test.assert_bob_redeemed(bob_state.unwrap()).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -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;
|
||||
testutils::init(|test| async move {
|
||||
let alice_swap = test.new_swap_as_alice().await;
|
||||
let bob_swap = test.new_swap_as_bob().await;
|
||||
|
||||
let bob_swap = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
);
|
||||
let bob_swap_handle = tokio::spawn(bob_swap);
|
||||
let bob = bob::run(bob_swap);
|
||||
let bob_handle = tokio::spawn(bob);
|
||||
|
||||
let alice_state = alice::run_until(
|
||||
alice.state,
|
||||
alice::swap::is_encsig_learned,
|
||||
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_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_swap = test.recover_alice_from_db().await;
|
||||
assert!(matches!(alice_swap.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();
|
||||
let alice_state = alice::run(alice_swap).await.unwrap();
|
||||
|
||||
alice_harness.assert_redeemed(alice_state).await;
|
||||
test.assert_alice_redeemed(alice_state).await;
|
||||
|
||||
let bob_state = bob_swap_handle.await.unwrap();
|
||||
bob_harness.assert_redeemed(bob_state.unwrap()).await
|
||||
let bob_state = bob_handle.await.unwrap();
|
||||
test.assert_bob_redeemed(bob_state.unwrap()).await
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -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;
|
||||
testutils::init(|test| async move {
|
||||
let alice_swap = test.new_swap_as_alice().await;
|
||||
let bob_swap = test.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice_swap_handle = tokio::spawn(alice_swap);
|
||||
let alice = alice::run(alice_swap);
|
||||
let alice_handle = tokio::spawn(alice);
|
||||
|
||||
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();
|
||||
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_swap = test.recover_bob_from_db().await;
|
||||
assert!(matches!(bob_swap.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();
|
||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
||||
|
||||
bob_harness.assert_redeemed(bob_state).await;
|
||||
test.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
let alice_state = alice_swap_handle.await.unwrap();
|
||||
alice_harness.assert_redeemed(alice_state.unwrap()).await;
|
||||
let alice_state = alice_handle.await.unwrap();
|
||||
test.assert_alice_redeemed(alice_state.unwrap()).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -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;
|
||||
testutils::init(|test| async move {
|
||||
let alice_swap = test.new_swap_as_alice().await;
|
||||
let bob_swap = test.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice_swap_handle = tokio::spawn(alice_swap);
|
||||
let alice_handle = alice::run(alice_swap);
|
||||
let alice_swap_handle = tokio::spawn(alice_handle);
|
||||
|
||||
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();
|
||||
let bob_state = bob::run_until(bob_swap, is_xmr_locked).await.unwrap();
|
||||
|
||||
assert!(matches!(bob_state, BobState::XmrLocked {..}));
|
||||
|
||||
let bob = bob_harness.recover_bob_from_db().await;
|
||||
assert!(matches!(bob.state, BobState::XmrLocked {..}));
|
||||
let bob_swap = test.recover_bob_from_db().await;
|
||||
assert!(matches!(bob_swap.state, BobState::XmrLocked {..}));
|
||||
|
||||
let bob_state = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
||||
|
||||
bob_harness.assert_redeemed(bob_state).await;
|
||||
test.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
let alice_state = alice_swap_handle.await.unwrap();
|
||||
alice_harness.assert_redeemed(alice_state.unwrap()).await;
|
||||
test.assert_alice_redeemed(alice_state.unwrap()).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -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;
|
||||
testutils::init(|test| async move {
|
||||
let alice_swap = test.new_swap_as_alice().await;
|
||||
let bob_swap = test.new_swap_as_bob().await;
|
||||
|
||||
let alice_swap = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
);
|
||||
let alice_swap_handle = tokio::spawn(alice_swap);
|
||||
let alice = alice::run(alice_swap);
|
||||
let alice_handle = tokio::spawn(alice);
|
||||
|
||||
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();
|
||||
let bob_state = bob::run_until(bob_swap, is_btc_locked).await.unwrap();
|
||||
|
||||
assert!(matches!(bob_state, BobState::BtcLocked {..}));
|
||||
|
||||
let alice_state = alice_swap_handle.await.unwrap();
|
||||
alice_harness.assert_punished(alice_state.unwrap()).await;
|
||||
let alice_state = alice_handle.await.unwrap();
|
||||
test.assert_alice_punished(alice_state.unwrap()).await;
|
||||
|
||||
// Restart Bob after Alice punished to ensure Bob transitions to
|
||||
// punished and does not run indefinitely
|
||||
let bob = bob_harness.recover_bob_from_db().await;
|
||||
assert!(matches!(bob.state, BobState::BtcLocked {..}));
|
||||
let bob_swap = test.recover_bob_from_db().await;
|
||||
assert!(matches!(bob_swap.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();
|
||||
let bob_state = bob::run(bob_swap).await.unwrap();
|
||||
|
||||
bob_harness.assert_punished(bob_state).await;
|
||||
test.assert_bob_punished(bob_state).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use rand::rngs::OsRng;
|
||||
use swap::protocol::{alice, alice::AliceState, bob};
|
||||
|
||||
pub mod testutils;
|
||||
@ -7,60 +6,33 @@ pub mod testutils;
|
||||
/// then also refunds.
|
||||
#[tokio::test]
|
||||
async fn given_alice_restarts_after_xmr_is_locked_abort_swap() {
|
||||
testutils::test(|alice_harness, bob_harness| async move {
|
||||
let alice = alice_harness.new_alice().await;
|
||||
let bob = bob_harness.new_bob().await;
|
||||
testutils::init(|test| async move {
|
||||
let alice_swap = test.new_swap_as_alice().await;
|
||||
let bob_swap = test.new_swap_as_bob().await;
|
||||
|
||||
let bob_swap = bob::swap(
|
||||
bob.state,
|
||||
bob.event_loop_handle,
|
||||
bob.db,
|
||||
bob.bitcoin_wallet.clone(),
|
||||
bob.monero_wallet.clone(),
|
||||
OsRng,
|
||||
bob.swap_id,
|
||||
);
|
||||
let bob_swap_handle = tokio::spawn(bob_swap);
|
||||
let bob = bob::run(bob_swap);
|
||||
let bob_handle = tokio::spawn(bob);
|
||||
|
||||
let alice_state = alice::run_until(
|
||||
alice.state,
|
||||
alice::swap::is_xmr_locked,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_state = alice::run_until(alice_swap, alice::swap::is_xmr_locked)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(alice_state, AliceState::XmrLocked {..}));
|
||||
|
||||
// Alice does not act, Bob refunds
|
||||
let bob_state = bob_swap_handle.await.unwrap();
|
||||
let bob_state = bob_handle.await.unwrap();
|
||||
|
||||
// Once bob has finished Alice is restarted and refunds as well
|
||||
let alice = alice_harness.recover_alice_from_db().await;
|
||||
assert!(matches!(alice.state, AliceState::XmrLocked {..}));
|
||||
let alice_swap = test.recover_alice_from_db().await;
|
||||
assert!(matches!(alice_swap.state, AliceState::XmrLocked {..}));
|
||||
|
||||
let alice_state = alice::swap(
|
||||
alice.state,
|
||||
alice.event_loop_handle,
|
||||
alice.bitcoin_wallet.clone(),
|
||||
alice.monero_wallet.clone(),
|
||||
alice.config,
|
||||
alice.swap_id,
|
||||
alice.db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_state = alice::run(alice_swap).await.unwrap();
|
||||
|
||||
// TODO: The test passes like this, but the assertion should be done after Bob
|
||||
// refunded, not at the end because this can cause side-effects!
|
||||
// We have to properly wait for the refund tx's finality inside the assertion,
|
||||
// which requires storing the refund_tx_id in the the state!
|
||||
bob_harness.assert_refunded(bob_state.unwrap()).await;
|
||||
alice_harness.assert_refunded(alice_state).await;
|
||||
test.assert_bob_refunded(bob_state.unwrap()).await;
|
||||
test.assert_alice_refunded(alice_state).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -22,9 +22,285 @@ use tracing_core::dispatcher::DefaultGuard;
|
||||
use tracing_log::LogTracer;
|
||||
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
|
||||
T: Fn(AliceHarness, BobHarness) -> F,
|
||||
T: Fn(Test) -> F,
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
let cli = Cli::default();
|
||||
@ -44,9 +320,8 @@ where
|
||||
xmr: swap_amounts.xmr * 10,
|
||||
btc: bitcoin::Amount::ZERO,
|
||||
};
|
||||
let alice_harness = AliceHarness::new(
|
||||
let alice_swap_factory = AliceSwapFactory::new(
|
||||
config,
|
||||
swap_amounts,
|
||||
Uuid::new_v4(),
|
||||
&monero,
|
||||
&containers.bitcoind,
|
||||
@ -59,32 +334,27 @@ where
|
||||
btc: swap_amounts.btc * 10,
|
||||
};
|
||||
|
||||
let bob_harness = BobHarness::new(
|
||||
let bob_swap_factory = BobSwapFactory::new(
|
||||
config,
|
||||
swap_amounts,
|
||||
Uuid::new_v4(),
|
||||
&monero,
|
||||
&containers.bitcoind,
|
||||
bob_starting_balances,
|
||||
alice_harness.listen_address(),
|
||||
alice_harness.peer_id(),
|
||||
alice_swap_factory.listen_address(),
|
||||
alice_swap_factory.peer_id(),
|
||||
)
|
||||
.await;
|
||||
|
||||
testfn(alice_harness, bob_harness).await
|
||||
let test = Test {
|
||||
swap_amounts,
|
||||
alice_swap_factory,
|
||||
bob_swap_factory,
|
||||
};
|
||||
|
||||
testfn(test).await
|
||||
}
|
||||
|
||||
pub struct Alice {
|
||||
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 {
|
||||
pub struct AliceSwapFactory {
|
||||
listen_address: Multiaddr,
|
||||
peer_id: PeerId,
|
||||
|
||||
@ -92,17 +362,15 @@ pub struct AliceHarness {
|
||||
db_path: PathBuf,
|
||||
swap_id: Uuid,
|
||||
|
||||
swap_amounts: SwapAmounts,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
config: Config,
|
||||
starting_balances: StartingBalances,
|
||||
}
|
||||
|
||||
impl AliceHarness {
|
||||
impl AliceSwapFactory {
|
||||
async fn new(
|
||||
config: Config,
|
||||
swap_amounts: SwapAmounts,
|
||||
swap_id: Uuid,
|
||||
monero: &Monero,
|
||||
bitcoind: &Bitcoind<'_>,
|
||||
@ -132,7 +400,6 @@ impl AliceHarness {
|
||||
listen_address,
|
||||
peer_id,
|
||||
swap_id,
|
||||
swap_amounts,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
@ -140,39 +407,37 @@ impl AliceHarness {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_alice(&self) -> Alice {
|
||||
pub async fn new_swap_as_alice(
|
||||
&self,
|
||||
swap_amounts: SwapAmounts,
|
||||
) -> (alice::Swap, alice::EventLoop) {
|
||||
let initial_state = init_alice_state(
|
||||
self.swap_amounts.btc,
|
||||
self.swap_amounts.xmr,
|
||||
swap_amounts.btc,
|
||||
swap_amounts.xmr,
|
||||
self.bitcoin_wallet.clone(),
|
||||
self.config,
|
||||
)
|
||||
.await;
|
||||
|
||||
let (mut event_loop, event_loop_handle) =
|
||||
let (event_loop, event_loop_handle) =
|
||||
init_alice_event_loop(self.listen_address.clone(), self.seed);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
let db = Database::open(self.db_path.as_path()).unwrap();
|
||||
|
||||
Alice {
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
db,
|
||||
state: initial_state,
|
||||
swap_id: self.swap_id,
|
||||
}
|
||||
(
|
||||
alice::Swap {
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
db,
|
||||
state: initial_state,
|
||||
swap_id: self.swap_id,
|
||||
},
|
||||
event_loop,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn recover_alice_from_db(&self) -> Alice {
|
||||
// TODO: "simulated restart" issues:
|
||||
// - create new wallets instead of reusing (hard because of container
|
||||
// lifetimes)
|
||||
// - consider aborting the old event loop (currently just keeps running)
|
||||
|
||||
pub async fn recover_alice_from_db(&self) -> (alice::Swap, alice::EventLoop) {
|
||||
// reopen the existing database
|
||||
let db = Database::open(self.db_path.clone().as_path()).unwrap();
|
||||
|
||||
@ -183,60 +448,21 @@ impl AliceHarness {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (mut event_loop, event_loop_handle) =
|
||||
let (event_loop, event_loop_handle) =
|
||||
init_alice_event_loop(self.listen_address.clone(), self.seed);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
Alice {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
swap_id: self.swap_id,
|
||||
db,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_redeemed(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcRedeemed));
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc + self.swap_amounts.btc
|
||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||
);
|
||||
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert!(xmr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr);
|
||||
}
|
||||
|
||||
pub async fn assert_refunded(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::XmrRefunded));
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(btc_balance_after_swap, self.starting_balances.btc);
|
||||
|
||||
// Ensure that Alice's balance is refreshed as we use a newly created wallet
|
||||
self.monero_wallet.as_ref().inner.refresh().await.unwrap();
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(xmr_balance_after_swap, self.swap_amounts.xmr);
|
||||
}
|
||||
|
||||
pub async fn assert_punished(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcPunished));
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc + self.swap_amounts.btc
|
||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
||||
);
|
||||
|
||||
let xnr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert!(xnr_balance_after_swap <= self.starting_balances.xmr - self.swap_amounts.xmr);
|
||||
(
|
||||
alice::Swap {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
config: self.config,
|
||||
swap_id: self.swap_id,
|
||||
db,
|
||||
},
|
||||
event_loop,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerId {
|
||||
@ -248,20 +474,10 @@ impl AliceHarness {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bob {
|
||||
pub state: BobState,
|
||||
pub event_loop_handle: bob::EventLoopHandle,
|
||||
pub db: Database,
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct BobHarness {
|
||||
pub struct BobSwapFactory {
|
||||
db_path: PathBuf,
|
||||
swap_id: Uuid,
|
||||
|
||||
swap_amounts: SwapAmounts,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
config: Config,
|
||||
@ -271,11 +487,10 @@ pub struct BobHarness {
|
||||
alice_connect_peer_id: PeerId,
|
||||
}
|
||||
|
||||
impl BobHarness {
|
||||
impl BobSwapFactory {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn new(
|
||||
config: Config,
|
||||
swap_amounts: SwapAmounts,
|
||||
swap_id: Uuid,
|
||||
monero: &Monero,
|
||||
bitcoind: &Bitcoind<'_>,
|
||||
@ -291,7 +506,6 @@ impl BobHarness {
|
||||
Self {
|
||||
db_path,
|
||||
swap_id,
|
||||
swap_amounts,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
@ -301,10 +515,10 @@ impl BobHarness {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_bob(&self) -> Bob {
|
||||
pub async fn new_swap_as_bob(&self, swap_amounts: SwapAmounts) -> (bob::Swap, bob::EventLoop) {
|
||||
let initial_state = init_bob_state(
|
||||
self.swap_amounts.btc,
|
||||
self.swap_amounts.xmr,
|
||||
swap_amounts.btc,
|
||||
swap_amounts.xmr,
|
||||
self.bitcoin_wallet.clone(),
|
||||
self.config,
|
||||
)
|
||||
@ -315,26 +529,22 @@ impl BobHarness {
|
||||
self.alice_connect_address.clone(),
|
||||
);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
let db = Database::open(self.db_path.as_path()).unwrap();
|
||||
|
||||
Bob {
|
||||
state: initial_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
}
|
||||
(
|
||||
bob::Swap {
|
||||
state: initial_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
},
|
||||
event_loop,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn recover_bob_from_db(&self) -> Bob {
|
||||
// TODO: "simulated restart" issues:
|
||||
// - create new wallets instead of reusing (hard because of container
|
||||
// lifetimes)
|
||||
// - consider aborting the old event loop (currently just keeps running)
|
||||
|
||||
pub async fn recover_bob_from_db(&self) -> (bob::Swap, bob::EventLoop) {
|
||||
// reopen the existing database
|
||||
let db = Database::open(self.db_path.clone().as_path()).unwrap();
|
||||
|
||||
@ -350,99 +560,17 @@ impl BobHarness {
|
||||
self.alice_connect_address.clone(),
|
||||
);
|
||||
|
||||
tokio::spawn(async move { event_loop.run().await });
|
||||
|
||||
Bob {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_redeemed(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
|
||||
);
|
||||
|
||||
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
||||
self.monero_wallet.as_ref().inner.refresh().await.unwrap();
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(
|
||||
xmr_balance_after_swap,
|
||||
self.starting_balances.xmr + self.swap_amounts.xmr
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn assert_refunded(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
|
||||
state4.tx_lock_id()
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
|
||||
let alice_submitted_cancel = btc_balance_after_swap
|
||||
== self.starting_balances.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
|
||||
|
||||
let bob_submitted_cancel = btc_balance_after_swap
|
||||
== self.starting_balances.btc
|
||||
- lock_tx_bitcoin_fee
|
||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
|
||||
|
||||
// The cancel tx can be submitted by both Alice and Bob.
|
||||
// Since we cannot be sure who submitted it we have to assert accordingly
|
||||
assert!(alice_submitted_cancel || bob_submitted_cancel);
|
||||
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr);
|
||||
}
|
||||
|
||||
pub async fn assert_punished(&self, state: BobState) {
|
||||
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
panic!("Bob in unexpected state");
|
||||
};
|
||||
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bitcoin_wallet
|
||||
.transaction_fee(lock_tx_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let btc_balance_after_swap = self.bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
self.starting_balances.btc - self.swap_amounts.btc - lock_tx_bitcoin_fee
|
||||
);
|
||||
|
||||
let xmr_balance_after_swap = self.monero_wallet.as_ref().get_balance().await.unwrap();
|
||||
assert_eq!(xmr_balance_after_swap, self.starting_balances.xmr);
|
||||
(
|
||||
bob::Swap {
|
||||
state: resume_state,
|
||||
event_loop_handle,
|
||||
db,
|
||||
bitcoin_wallet: self.bitcoin_wallet.clone(),
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
swap_id: self.swap_id,
|
||||
},
|
||||
event_loop,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user