mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-01 11:06:13 -04:00
feat: Add xmr-btc-swap repo in subdirectory
This commit is contained in:
parent
462f3b2e6f
commit
757183e857
526 changed files with 41757 additions and 1 deletions
|
@ -0,0 +1,83 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::FastCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
use swap::{asb, cli};
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() {
|
||||
harness::setup_test(FastCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
assert!(matches!(
|
||||
alice_state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
let (bob_swap, bob_join_handle) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
|
||||
// Ensure cancel timelock is expired
|
||||
if let BobState::BtcLocked { state3, .. } = bob_swap.state.clone() {
|
||||
bob_swap
|
||||
.bitcoin_wallet
|
||||
.subscribe_to(state3.tx_lock)
|
||||
.await
|
||||
.wait_until_confirmed_with(state3.cancel_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Bob in unexpected state {}", bob_swap.state);
|
||||
}
|
||||
|
||||
// Bob manually cancels
|
||||
bob_join_handle.abort();
|
||||
let (_, state) = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db).await?;
|
||||
assert!(matches!(state, BobState::BtcCancelled { .. }));
|
||||
|
||||
let (bob_swap, bob_join_handle) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcCancelled { .. }));
|
||||
|
||||
// Bob manually refunds
|
||||
bob_join_handle.abort();
|
||||
let bob_state = cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db).await?;
|
||||
|
||||
ctx.assert_bob_refunded(bob_state).await;
|
||||
|
||||
// manually refund Alice's swap
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_state = asb::refund(
|
||||
alice_swap.swap_id,
|
||||
alice_swap.bitcoin_wallet,
|
||||
alice_swap.monero_wallet,
|
||||
alice_swap.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
ctx.assert_alice_refunded(alice_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::bitcoin::{parse_rpc_error_code, RpcErrorCode};
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
use swap::{asb, cli};
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||
|
||||
let (bob_swap, bob_join_handle) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
assert!(matches!(
|
||||
alice_state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
// Bob tries but fails to manually cancel
|
||||
let error = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("Cannot cancel swap because the cancel timelock has not expired yet"));
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
assert!(matches!(
|
||||
alice_swap.state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
// Alice tries but fails manual cancel
|
||||
let error = asb::cancel(alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
parse_rpc_error_code(&error).unwrap(),
|
||||
i64::from(RpcErrorCode::RpcVerifyRejected)
|
||||
);
|
||||
|
||||
let (bob_swap, bob_join_handle) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
// Bob tries but fails to manually refund
|
||||
let error = cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("Cannot refund swap because the cancel timelock has not expired yet"));
|
||||
|
||||
let (bob_swap, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
assert!(matches!(
|
||||
alice_swap.state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
// Alice tries but fails manual cancel
|
||||
let result = asb::refund(
|
||||
alice_swap.swap_id,
|
||||
alice_swap.bitcoin_wallet,
|
||||
alice_swap.monero_wallet,
|
||||
alice_swap.db,
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
assert!(matches!(
|
||||
alice_swap.state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::FastCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
use swap::{asb, cli};
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_and_bob_manually_cancel_and_refund_after_funds_locked_both_refund() {
|
||||
harness::setup_test(FastCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
assert!(matches!(
|
||||
alice_state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
let (bob_swap, bob_join_handle) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
|
||||
// Ensure cancel timelock is expired
|
||||
if let BobState::BtcLocked { state3, .. } = bob_swap.state.clone() {
|
||||
bob_swap
|
||||
.bitcoin_wallet
|
||||
.subscribe_to(state3.tx_lock)
|
||||
.await
|
||||
.wait_until_confirmed_with(state3.cancel_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Bob in unexpected state {}", bob_swap.state);
|
||||
}
|
||||
|
||||
// Bob manually cancels and refunds
|
||||
bob_join_handle.abort();
|
||||
let bob_state =
|
||||
cli::cancel_and_refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db).await?;
|
||||
|
||||
ctx.assert_bob_refunded(bob_state).await;
|
||||
|
||||
// manually refund Alice's swap
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_state = asb::refund(
|
||||
alice_swap.swap_id,
|
||||
alice_swap.bitcoin_wallet,
|
||||
alice_swap.monero_wallet,
|
||||
alice_swap.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
ctx.assert_alice_refunded(alice_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::FastPunishConfig;
|
||||
use swap::asb;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||
/// the encsig and fail to refund or redeem. Alice punishes using the cancel and
|
||||
/// punish command. Bob then cooperates with Alice and redeems XMR with her key.
|
||||
#[tokio::test]
|
||||
async fn alice_manually_punishes_after_bob_dead() {
|
||||
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_bitcoin_wallet = alice_swap.bitcoin_wallet.clone();
|
||||
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
|
||||
// Ensure cancel timelock is expired
|
||||
if let AliceState::XmrLockTransactionSent { state3, .. } = alice_state {
|
||||
alice_bitcoin_wallet
|
||||
.subscribe_to(state3.tx_lock)
|
||||
.await
|
||||
.wait_until_confirmed_with(state3.cancel_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Alice in unexpected state {}", alice_state);
|
||||
}
|
||||
|
||||
// manual cancel (required to be able to punish)
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let (_, alice_state) =
|
||||
asb::cancel(alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db).await?;
|
||||
|
||||
// Ensure punish timelock is expired
|
||||
if let AliceState::BtcCancelled { state3, .. } = alice_state {
|
||||
alice_bitcoin_wallet
|
||||
.subscribe_to(state3.tx_cancel())
|
||||
.await
|
||||
.wait_until_confirmed_with(state3.punish_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Alice in unexpected state {}", alice_state);
|
||||
}
|
||||
|
||||
// manual punish
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let (_, alice_state) =
|
||||
asb::punish(alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db).await?;
|
||||
ctx.assert_alice_punished(alice_state).await;
|
||||
|
||||
// Restart Bob after Alice punished to ensure Bob transitions to
|
||||
// punished and does not run indefinitely
|
||||
let (bob_swap, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::FastPunishConfig;
|
||||
use swap::asb;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::cli;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||
/// the encsig and fail to refund or redeem. Alice punishes using the cancel and
|
||||
/// punish command. Then Bob tries to refund.
|
||||
#[tokio::test]
|
||||
async fn alice_manually_punishes_after_bob_dead_and_bob_cancels() {
|
||||
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_bitcoin_wallet = alice_swap.bitcoin_wallet.clone();
|
||||
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
|
||||
// Ensure cancel timelock is expired
|
||||
if let AliceState::XmrLockTransactionSent { state3, .. } = alice_state {
|
||||
alice_bitcoin_wallet
|
||||
.subscribe_to(state3.tx_lock)
|
||||
.await
|
||||
.wait_until_confirmed_with(state3.cancel_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Alice in unexpected state {}", alice_state);
|
||||
}
|
||||
|
||||
// manual cancel (required to be able to punish)
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let (_, alice_state) =
|
||||
asb::cancel(alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db).await?;
|
||||
|
||||
// Ensure punish timelock is expired
|
||||
if let AliceState::BtcCancelled { state3, .. } = alice_state {
|
||||
alice_bitcoin_wallet
|
||||
.subscribe_to(state3.tx_cancel())
|
||||
.await
|
||||
.wait_until_confirmed_with(state3.punish_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Alice in unexpected state {}", alice_state);
|
||||
}
|
||||
|
||||
// manual punish
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let (_, alice_state) =
|
||||
asb::punish(alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db).await?;
|
||||
ctx.assert_alice_punished(alice_state).await;
|
||||
// Bob is in wrong state.
|
||||
let (bob_swap, bob_join_handle) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
bob_join_handle.abort();
|
||||
|
||||
let (_, state) = cli::cancel(bob_swap_id, bob_swap.bitcoin_wallet, bob_swap.db).await?;
|
||||
// Bob should be in BtcCancelled state now.
|
||||
assert!(matches!(state, BobState::BtcCancelled { .. }));
|
||||
|
||||
let (bob_swap, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcCancelled { .. }));
|
||||
// Alice punished Bob, so he should be in the BtcPunished state.
|
||||
let error = cli::refund(bob_swap_id, bob_swap.bitcoin_wallet, bob_swap.db)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"Cannot refund swap because we have already been punished"
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_encsig_learned;
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb;
|
||||
use swap::asb::{Finality, FixedRate};
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Alice redeems using manual redeem command
|
||||
/// after learning encsig from Bob
|
||||
#[tokio::test]
|
||||
async fn alice_manually_redeems_after_enc_sig_learned() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, _) = ctx.bob_swap().await;
|
||||
let bob_swap = tokio::spawn(bob::run(bob_swap));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_encsig_learned,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
assert!(matches!(alice_state, AliceState::EncSigLearned { .. }));
|
||||
|
||||
// manual redeem
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let (_, alice_state) = asb::redeem(
|
||||
alice_swap.swap_id,
|
||||
alice_swap.bitcoin_wallet,
|
||||
alice_swap.db,
|
||||
Finality::Await,
|
||||
)
|
||||
.await?;
|
||||
ctx.assert_alice_redeemed(alice_state).await;
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::FastPunishConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||
/// the encsig and fail to refund or redeem. Alice cancels and punishes. Bob then cooperates with Alice and redeems XMR with her key.
|
||||
#[tokio::test]
|
||||
async fn alice_punishes_after_restart_if_bob_dead() {
|
||||
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_bitcoin_wallet = alice_swap.bitcoin_wallet.clone();
|
||||
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
|
||||
// Ensure cancel timelock is expired (we can only ensure that, because the
|
||||
// cancel transaction is not published at this point)
|
||||
if let AliceState::XmrLockTransactionSent { state3, .. } = alice_state {
|
||||
alice_bitcoin_wallet
|
||||
.subscribe_to(state3.tx_lock)
|
||||
.await
|
||||
.wait_until_confirmed_with(state3.cancel_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Alice in unexpected state {}", alice_state);
|
||||
}
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
ctx.assert_alice_punished(alice_state).await;
|
||||
|
||||
// Restart Bob after Alice punished to ensure Bob transitions to
|
||||
// punished and does not run indefinitely
|
||||
let (bob_swap, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::FastCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob refunds.
|
||||
/// Eventually Alice comes back online and refunds as well.
|
||||
#[tokio::test]
|
||||
async fn alice_refunds_after_restart_if_bob_already_refunded() {
|
||||
harness::setup_test(FastCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, _) = ctx.bob_swap().await;
|
||||
let bob_swap = tokio::spawn(bob::run(bob_swap));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
ctx.assert_bob_refunded(bob_state).await;
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
assert!(matches!(
|
||||
alice_state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
ctx.assert_alice_refunded(alice_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
27
src-xmr-btc-swap/swap/tests/bdk.sh
Executable file
27
src-xmr-btc-swap/swap/tests/bdk.sh
Executable file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
VERSION=0.11.1
|
||||
|
||||
mkdir bdk
|
||||
stat ./target/debug/swap || exit 1
|
||||
cp ./target/debug/swap bdk/swap-current
|
||||
pushd bdk
|
||||
|
||||
echo "download swap $VERSION"
|
||||
curl -L "https://github.com/comit-network/xmr-btc-swap/releases/download/${VERSION}/swap_${VERSION}_Linux_x86_64.tar" | tar xv
|
||||
|
||||
echo "create testnet wallet with $VERSION"
|
||||
./swap --testnet --data-base-dir . --debug balance || exit 1
|
||||
echo "check testnet wallet with this version"
|
||||
./swap-current --testnet --data-base-dir . --debug balance || exit 1
|
||||
|
||||
echo "create mainnet wallet with $VERSION"
|
||||
./swap --version || exit 1
|
||||
./swap --data-base-dir . --debug balance || exit 1
|
||||
echo "check mainnet wallet with this version"
|
||||
./swap-current --version || exit 1
|
||||
./swap-current --data-base-dir . --debug balance || exit 1
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,61 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::bob_run_until::is_xmr_locked;
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
#[tokio::test]
|
||||
async fn concurrent_bobs_after_xmr_lock_proof_sent() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap_1, bob_join_handle_1) = ctx.bob_swap().await;
|
||||
|
||||
let swap_id = bob_swap_1.id;
|
||||
|
||||
let bob_swap_1 = tokio::spawn(bob::run_until(bob_swap_1, is_xmr_locked));
|
||||
|
||||
let alice_swap_1 = ctx.alice_next_swap().await;
|
||||
let alice_swap_1 = tokio::spawn(alice::run(alice_swap_1, FixedRate::default()));
|
||||
|
||||
let bob_state_1 = bob_swap_1.await??;
|
||||
assert!(matches!(bob_state_1, BobState::XmrLocked { .. }));
|
||||
|
||||
// make sure bob_swap_1's event loop is gone
|
||||
bob_join_handle_1.abort();
|
||||
|
||||
let (bob_swap_2, bob_join_handle_2) = ctx.bob_swap().await;
|
||||
let bob_swap_2 = tokio::spawn(bob::run(bob_swap_2));
|
||||
|
||||
let alice_swap_2 = ctx.alice_next_swap().await;
|
||||
let alice_swap_2 = tokio::spawn(alice::run(alice_swap_2, FixedRate::default()));
|
||||
|
||||
// The 2nd swap should ALWAYS finish successfully in this
|
||||
// scenario
|
||||
|
||||
let bob_state_2 = bob_swap_2.await??;
|
||||
assert!(matches!(bob_state_2, BobState::XmrRedeemed { .. }));
|
||||
|
||||
let alice_state_2 = alice_swap_2.await??;
|
||||
assert!(matches!(alice_state_2, AliceState::BtcRedeemed { .. }));
|
||||
|
||||
let (bob_swap_1, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle_2, swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap_1.state, BobState::XmrLocked { .. }));
|
||||
|
||||
// The 1st (paused) swap ALWAYS finishes successfully in this
|
||||
// scenario, because it is ensured that Bob already received the
|
||||
// transfer proof.
|
||||
|
||||
let bob_state_1 = bob::run(bob_swap_1).await?;
|
||||
assert!(matches!(bob_state_1, BobState::XmrRedeemed { .. }));
|
||||
|
||||
let alice_state_1 = alice_swap_1.await??;
|
||||
assert!(matches!(alice_state_1, AliceState::BtcRedeemed { .. }));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
#[tokio::test]
|
||||
async fn concurrent_bobs_before_xmr_lock_proof_sent() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap_1, bob_join_handle_1) = ctx.bob_swap().await;
|
||||
|
||||
let swap_id = bob_swap_1.id;
|
||||
|
||||
let bob_swap_1 = tokio::spawn(bob::run_until(bob_swap_1, is_btc_locked));
|
||||
|
||||
let alice_swap_1 = ctx.alice_next_swap().await;
|
||||
let alice_swap_1 = tokio::spawn(alice::run(alice_swap_1, FixedRate::default()));
|
||||
|
||||
let bob_state_1 = bob_swap_1.await??;
|
||||
assert!(matches!(bob_state_1, BobState::BtcLocked { .. }));
|
||||
|
||||
// make sure bob_swap_1's event loop is gone
|
||||
bob_join_handle_1.abort();
|
||||
|
||||
let (bob_swap_2, bob_join_handle_2) = ctx.bob_swap().await;
|
||||
let bob_swap_2 = tokio::spawn(bob::run(bob_swap_2));
|
||||
|
||||
let alice_swap_2 = ctx.alice_next_swap().await;
|
||||
let alice_swap_2 = tokio::spawn(alice::run(alice_swap_2, FixedRate::default()));
|
||||
|
||||
// The 2nd swap ALWAYS finish successfully in this
|
||||
// scenario, but will receive an "unwanted" transfer proof that is buffered until the 1st swap is resumed
|
||||
|
||||
let bob_state_2 = bob_swap_2.await??;
|
||||
assert!(matches!(bob_state_2, BobState::XmrRedeemed { .. }));
|
||||
|
||||
let alice_state_2 = alice_swap_2.await??;
|
||||
assert!(matches!(alice_state_2, AliceState::BtcRedeemed { .. }));
|
||||
|
||||
let (bob_swap_1, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle_2, swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_state_1, BobState::BtcLocked { .. }));
|
||||
|
||||
// The 1st (paused) swap is expected to finish successfully because the transfer proof is buffered when it is receives while another swap is running.
|
||||
|
||||
let bob_state_1 = bob::run(bob_swap_1).await?;
|
||||
assert!(matches!(bob_state_1, BobState::XmrRedeemed { .. }));
|
||||
|
||||
let alice_state_1 = alice_swap_1.await??;
|
||||
assert!(matches!(alice_state_1, AliceState::BtcRedeemed { .. }));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
21
src-xmr-btc-swap/swap/tests/ensure_same_swap_id.rs
Normal file
21
src-xmr-btc-swap/swap/tests/ensure_same_swap_id.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::protocol::bob;
|
||||
|
||||
#[tokio::test]
|
||||
async fn ensure_same_swap_id_for_alice_and_bob() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, _) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
tokio::spawn(bob::run(bob_swap));
|
||||
|
||||
// once Bob's swap is spawned we can retrieve Alice's swap and assert on the
|
||||
// swap ID
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
assert_eq!(alice_swap.swap_id, bob_swap_id);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
25
src-xmr-btc-swap/swap/tests/happy_path.rs
Normal file
25
src-xmr-btc-swap/swap/tests/happy_path.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::{alice, bob};
|
||||
use tokio::join;
|
||||
|
||||
#[tokio::test]
|
||||
async fn happy_path() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, _) = ctx.bob_swap().await;
|
||||
let bob_swap = tokio::spawn(bob::run(bob_swap));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
|
||||
|
||||
let (bob_state, alice_state) = join!(bob_swap, alice_swap);
|
||||
|
||||
ctx.assert_alice_redeemed(alice_state??).await;
|
||||
ctx.assert_bob_redeemed(bob_state??).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
pub mod harness;
|
||||
|
||||
use crate::harness::bob_run_until::is_encsig_sent;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
use tokio::join;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_restarts_while_alice_redeems_btc() {
|
||||
harness::setup_test(harness::SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_handle) = ctx.bob_swap().await;
|
||||
let swap_id = bob_swap.id;
|
||||
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_encsig_sent));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
|
||||
|
||||
let (bob_state, alice_state) = join!(bob_swap, alice_swap);
|
||||
ctx.assert_alice_redeemed(alice_state??).await;
|
||||
assert!(matches!(bob_state??, BobState::EncSigSent { .. }));
|
||||
|
||||
let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_handle, swap_id).await;
|
||||
|
||||
if let BobState::EncSigSent(state4) = bob_swap.state.clone() {
|
||||
bob_swap
|
||||
.bitcoin_wallet
|
||||
.subscribe_to(state4.tx_lock)
|
||||
.await
|
||||
.wait_until_confirmed_with(state4.cancel_timelock)
|
||||
.await?;
|
||||
} else {
|
||||
panic!("Bob in unexpected state {}", bob_swap.state);
|
||||
}
|
||||
|
||||
// Restart Bob
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::alice::AliceState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_restarts_after_xmr_is_locked_resume_swap() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, _) = ctx.bob_swap().await;
|
||||
let bob_swap = tokio::spawn(bob::run(bob_swap));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
|
||||
assert!(matches!(
|
||||
alice_state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
ctx.restart_alice().await;
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
assert!(matches!(
|
||||
alice_swap.state,
|
||||
AliceState::XmrLockTransactionSent { .. }
|
||||
));
|
||||
|
||||
let alice_state = alice::run(alice_swap, FixedRate::default()).await?;
|
||||
ctx.assert_alice_redeemed(alice_state).await;
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::bob_run_until::is_xmr_locked;
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
|
||||
assert!(matches!(bob_state, BobState::XmrLocked { .. }));
|
||||
|
||||
let (bob_swap, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::XmrLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
ctx.assert_alice_redeemed(alice_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::bob_run_until::is_xmr_locked;
|
||||
use harness::SlowCancelConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
|
||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
|
||||
assert!(matches!(bob_state, BobState::XmrLocked { .. }));
|
||||
|
||||
let (bob_swap, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::XmrLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
ctx.assert_alice_redeemed(alice_state).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
95
src-xmr-btc-swap/swap/tests/harness/bitcoind.rs
Normal file
95
src-xmr-btc-swap/swap/tests/harness/bitcoind.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use std::collections::BTreeMap;
|
||||
use testcontainers::{core::WaitFor, Image, ImageArgs};
|
||||
|
||||
pub const RPC_USER: &str = "admin";
|
||||
pub const RPC_PASSWORD: &str = "123";
|
||||
pub const RPC_PORT: u16 = 18443;
|
||||
pub const PORT: u16 = 18886;
|
||||
pub const DATADIR: &str = "/home/bdk";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Bitcoind {
|
||||
entrypoint: Option<String>,
|
||||
volumes: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl Image for Bitcoind {
|
||||
type Args = BitcoindArgs;
|
||||
|
||||
fn name(&self) -> String {
|
||||
"coblox/bitcoin-core".into()
|
||||
}
|
||||
|
||||
fn tag(&self) -> String {
|
||||
"0.21.0".into()
|
||||
}
|
||||
|
||||
fn ready_conditions(&self) -> Vec<WaitFor> {
|
||||
vec![WaitFor::message_on_stdout("init message: Done loading")]
|
||||
}
|
||||
|
||||
fn volumes(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
|
||||
Box::new(self.volumes.iter())
|
||||
}
|
||||
|
||||
fn entrypoint(&self) -> Option<String> {
|
||||
self.entrypoint.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bitcoind {
|
||||
fn default() -> Self {
|
||||
Bitcoind {
|
||||
entrypoint: Some("/usr/bin/bitcoind".into()),
|
||||
volumes: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bitcoind {
|
||||
pub fn with_volume(mut self, volume: String) -> Self {
|
||||
self.volumes.insert(volume, DATADIR.to_string());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BitcoindArgs;
|
||||
|
||||
impl Default for BitcoindArgs {
|
||||
fn default() -> Self {
|
||||
BitcoindArgs
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for BitcoindArgs {
|
||||
type Item = String;
|
||||
type IntoIter = ::std::vec::IntoIter<String>;
|
||||
|
||||
fn into_iter(self) -> <Self as IntoIterator>::IntoIter {
|
||||
let args = vec![
|
||||
"-server".to_string(),
|
||||
"-regtest".to_string(),
|
||||
"-listen=1".to_string(),
|
||||
"-prune=0".to_string(),
|
||||
"-rpcallowip=0.0.0.0/0".to_string(),
|
||||
"-rpcbind=0.0.0.0".to_string(),
|
||||
format!("-rpcuser={}", RPC_USER),
|
||||
format!("-rpcpassword={}", RPC_PASSWORD),
|
||||
"-printtoconsole".to_string(),
|
||||
"-fallbackfee=0.0002".to_string(),
|
||||
format!("-datadir={}", DATADIR),
|
||||
format!("-rpcport={}", RPC_PORT),
|
||||
format!("-port={}", PORT),
|
||||
"-rest".to_string(),
|
||||
];
|
||||
|
||||
args.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageArgs for BitcoindArgs {
|
||||
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
|
||||
Box::new(self.into_iter())
|
||||
}
|
||||
}
|
135
src-xmr-btc-swap/swap/tests/harness/electrs.rs
Normal file
135
src-xmr-btc-swap/swap/tests/harness/electrs.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::harness::bitcoind;
|
||||
use bitcoin::Network;
|
||||
use testcontainers::{core::WaitFor, Image, ImageArgs};
|
||||
|
||||
pub const HTTP_PORT: u16 = 60401;
|
||||
pub const RPC_PORT: u16 = 3002;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Electrs {
|
||||
tag: String,
|
||||
args: ElectrsArgs,
|
||||
entrypoint: Option<String>,
|
||||
wait_for_message: String,
|
||||
volumes: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl Image for Electrs {
|
||||
type Args = ElectrsArgs;
|
||||
fn name(&self) -> String {
|
||||
"vulpemventures/electrs".into()
|
||||
}
|
||||
|
||||
fn tag(&self) -> String {
|
||||
self.tag.clone()
|
||||
}
|
||||
|
||||
fn ready_conditions(&self) -> Vec<WaitFor> {
|
||||
vec![WaitFor::message_on_stderr(self.wait_for_message.clone())]
|
||||
}
|
||||
|
||||
fn volumes(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
|
||||
Box::new(self.volumes.iter())
|
||||
}
|
||||
|
||||
fn entrypoint(&self) -> Option<String> {
|
||||
self.entrypoint.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Electrs {
|
||||
fn default() -> Self {
|
||||
Electrs {
|
||||
tag: "v0.16.0.3".into(),
|
||||
args: ElectrsArgs::default(),
|
||||
entrypoint: Some("/build/electrs".into()),
|
||||
wait_for_message: "Running accept thread".to_string(),
|
||||
volumes: BTreeMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Electrs {
|
||||
pub fn with_tag(self, tag_str: &str) -> Self {
|
||||
Electrs {
|
||||
tag: tag_str.to_string(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_volume(mut self, volume: String) -> Self {
|
||||
self.volumes.insert(volume, bitcoind::DATADIR.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_daemon_rpc_addr(mut self, name: String) -> Self {
|
||||
self.args.daemon_rpc_addr = name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn self_and_args(self) -> (Self, ElectrsArgs) {
|
||||
let args = self.args.clone();
|
||||
(self, args)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ElectrsArgs {
|
||||
pub network: Network,
|
||||
pub daemon_dir: String,
|
||||
pub daemon_rpc_addr: String,
|
||||
pub cookie: String,
|
||||
pub http_addr: String,
|
||||
pub electrum_rpc_addr: String,
|
||||
pub cors: String,
|
||||
}
|
||||
|
||||
impl Default for ElectrsArgs {
|
||||
fn default() -> Self {
|
||||
// todo: these "defaults" are only suitable for our tests and need to be looked
|
||||
// at
|
||||
ElectrsArgs {
|
||||
network: Network::Regtest,
|
||||
daemon_dir: bitcoind::DATADIR.to_string(),
|
||||
daemon_rpc_addr: format!("0.0.0.0:{}", bitcoind::RPC_PORT),
|
||||
cookie: format!("{}:{}", bitcoind::RPC_USER, bitcoind::RPC_PASSWORD),
|
||||
http_addr: format!("0.0.0.0:{}", HTTP_PORT),
|
||||
electrum_rpc_addr: format!("0.0.0.0:{}", RPC_PORT),
|
||||
cors: "*".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for ElectrsArgs {
|
||||
type Item = String;
|
||||
type IntoIter = ::std::vec::IntoIter<String>;
|
||||
|
||||
fn into_iter(self) -> <Self as IntoIterator>::IntoIter {
|
||||
let mut args = Vec::new();
|
||||
|
||||
match self.network {
|
||||
Network::Testnet => args.push("--network=testnet".to_string()),
|
||||
Network::Regtest => args.push("--network=regtest".to_string()),
|
||||
Network::Bitcoin => {}
|
||||
Network::Signet => panic!("signet not yet supported"),
|
||||
}
|
||||
|
||||
args.push("-vvvvv".to_string());
|
||||
args.push(format!("--daemon-dir={}", self.daemon_dir.as_str()));
|
||||
args.push(format!("--daemon-rpc-addr={}", self.daemon_rpc_addr));
|
||||
args.push(format!("--cookie={}", self.cookie));
|
||||
args.push(format!("--http-addr={}", self.http_addr));
|
||||
args.push(format!("--electrum-rpc-addr={}", self.electrum_rpc_addr));
|
||||
args.push(format!("--cors={}", self.cors));
|
||||
|
||||
args.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageArgs for ElectrsArgs {
|
||||
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
|
||||
Box::new(self.into_iter())
|
||||
}
|
||||
}
|
1058
src-xmr-btc-swap/swap/tests/harness/mod.rs
Normal file
1058
src-xmr-btc-swap/swap/tests/harness/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
39
src-xmr-btc-swap/swap/tests/punish.rs
Normal file
39
src-xmr-btc-swap/swap/tests/punish.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
pub mod harness;
|
||||
|
||||
use harness::bob_run_until::is_btc_locked;
|
||||
use harness::FastPunishConfig;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||
/// the encsig and fail to refund or redeem. Alice punishes. Bob then cooperates with Alice and redeems XMR with her key.
|
||||
#[tokio::test]
|
||||
async fn alice_punishes_if_bob_never_acts_after_fund() {
|
||||
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
|
||||
let alice_swap = ctx.alice_next_swap().await;
|
||||
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
|
||||
|
||||
let bob_state = bob_swap.await??;
|
||||
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
|
||||
|
||||
let alice_state = alice_swap.await??;
|
||||
ctx.assert_alice_punished(alice_state).await;
|
||||
|
||||
// Restart Bob after Alice punished to ensure Bob transitions to
|
||||
// punished and does not run indefinitely
|
||||
let (bob_swap, _) = ctx
|
||||
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
|
||||
.await;
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
436
src-xmr-btc-swap/swap/tests/rpc.rs
Normal file
436
src-xmr-btc-swap/swap/tests/rpc.rs
Normal file
|
@ -0,0 +1,436 @@
|
|||
pub mod harness;
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use jsonrpsee::ws_client::WsClientBuilder;
|
||||
use jsonrpsee_core::client::{Client, ClientT};
|
||||
use jsonrpsee_core::params::ObjectParams;
|
||||
|
||||
use serial_test::serial;
|
||||
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use swap::api::request::{Method, Request};
|
||||
use swap::api::Context;
|
||||
|
||||
use crate::harness::alice_run_until::is_xmr_lock_transaction_sent;
|
||||
use crate::harness::bob_run_until::is_btc_locked;
|
||||
use crate::harness::{setup_test, SlowCancelConfig, TestContext};
|
||||
use swap::asb::FixedRate;
|
||||
use swap::protocol::{alice, bob};
|
||||
use swap::tracing_ext::{capture_logs, MakeCapturingWriter};
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
use uuid::Uuid;
|
||||
|
||||
const SERVER_ADDRESS: &str = "127.0.0.1:1234";
|
||||
const SERVER_START_TIMEOUT_SECS: u64 = 50;
|
||||
const BITCOIN_ADDR: &str = "bcrt1qahvhjfc7vx5857zf8knxs8yp5lkm26jgyt0k76";
|
||||
const MONERO_ADDR: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a";
|
||||
const SELLER: &str =
|
||||
"/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi";
|
||||
const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b";
|
||||
|
||||
pub async fn setup_daemon(
|
||||
harness_ctx: TestContext,
|
||||
) -> (Client, MakeCapturingWriter, Arc<Context>) {
|
||||
let writer = capture_logs(LevelFilter::DEBUG);
|
||||
let server_address: SocketAddr = SERVER_ADDRESS.parse().unwrap();
|
||||
|
||||
let request = Request::new(Method::StartDaemon {
|
||||
server_address: Some(server_address),
|
||||
});
|
||||
|
||||
let context = Arc::new(harness_ctx.get_bob_context().await);
|
||||
|
||||
let context_clone = context.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = request.call(context_clone).await {
|
||||
println!("Failed to initialize daemon for testing: {}", err);
|
||||
}
|
||||
});
|
||||
|
||||
for _ in 0..SERVER_START_TIMEOUT_SECS {
|
||||
if writer.captured().contains("Started RPC server") {
|
||||
let url = format!("ws://{}", SERVER_ADDRESS);
|
||||
let client = WsClientBuilder::default().build(&url).await.unwrap();
|
||||
|
||||
return (client, writer, context);
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
panic!(
|
||||
"Failed to start RPC server after {} seconds",
|
||||
SERVER_START_TIMEOUT_SECS
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_has_keys_serde(map: &serde_json::Map<String, Value>, keys: &[&str]) {
|
||||
for &key in keys {
|
||||
assert!(map.contains_key(key), "Key {} is missing", key);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for HashMap
|
||||
fn assert_has_keys_hashmap<T>(map: &HashMap<String, T>, keys: &[&str]) {
|
||||
for &key in keys {
|
||||
assert!(map.contains_key(key), "Key {} is missing", key);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
pub async fn get_swap_info() {
|
||||
setup_test(SlowCancelConfig, |mut harness_ctx| async move {
|
||||
// Start a swap and wait for xmr lock transaction to be published (XmrLockTransactionSent)
|
||||
let (bob_swap, _) = harness_ctx.bob_swap().await;
|
||||
let bob_swap_id = bob_swap.id;
|
||||
tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
|
||||
let alice_swap = harness_ctx.alice_next_swap().await;
|
||||
alice::run_until(
|
||||
alice_swap,
|
||||
is_xmr_lock_transaction_sent,
|
||||
FixedRate::default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (client, _, _) = setup_daemon(harness_ctx).await;
|
||||
|
||||
let response: HashMap<String, Vec<(Uuid, String)>> = client
|
||||
.request("get_history", ObjectParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let swaps: Vec<(Uuid, String)> = vec![(bob_swap_id, "btc is locked".to_string())];
|
||||
|
||||
assert_eq!(response, HashMap::from([("swaps".to_string(), swaps)]));
|
||||
|
||||
let response: HashMap<String, HashMap<Uuid, Vec<Value>>> = client
|
||||
.request("get_raw_states", ObjectParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response_raw_states = response.get("raw_states").unwrap();
|
||||
|
||||
assert!(response_raw_states.contains_key(&bob_swap_id));
|
||||
assert_eq!(response_raw_states.get(&bob_swap_id).unwrap().len(), 2);
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params.insert("swap_id", bob_swap_id).unwrap();
|
||||
let response: HashMap<String, Value> =
|
||||
client.request("get_swap_info", params).await.unwrap();
|
||||
|
||||
// Check primary keys in response
|
||||
assert_has_keys_hashmap(
|
||||
&response,
|
||||
&[
|
||||
"txRefundFee",
|
||||
"swapId",
|
||||
"cancelTimelock",
|
||||
"timelock",
|
||||
"punishTimelock",
|
||||
"stateName",
|
||||
"btcAmount",
|
||||
"startDate",
|
||||
"btcRefundAddress",
|
||||
"txCancelFee",
|
||||
"xmrAmount",
|
||||
"completed",
|
||||
"txLockId",
|
||||
"seller",
|
||||
],
|
||||
);
|
||||
|
||||
// Assert specific fields
|
||||
assert_eq!(response.get("swapId").unwrap(), &bob_swap_id.to_string());
|
||||
assert_eq!(
|
||||
response.get("stateName").unwrap(),
|
||||
&"btc is locked".to_string()
|
||||
);
|
||||
assert_eq!(response.get("completed").unwrap(), &Value::Bool(false));
|
||||
|
||||
// Check seller object and its keys
|
||||
let seller = response
|
||||
.get("seller")
|
||||
.expect("Field 'seller' is missing from response")
|
||||
.as_object()
|
||||
.expect("'seller' is not an object");
|
||||
assert_has_keys_serde(seller, &["peerId"]);
|
||||
|
||||
// Check timelock object, nested 'None' object, and blocks_left
|
||||
let timelock = response
|
||||
.get("timelock")
|
||||
.expect("Field 'timelock' is missing from response")
|
||||
.as_object()
|
||||
.expect("'timelock' is not an object");
|
||||
let none_obj = timelock
|
||||
.get("None")
|
||||
.expect("Field 'None' is missing from 'timelock'")
|
||||
.as_object()
|
||||
.expect("'None' is not an object in 'timelock'");
|
||||
let blocks_left = none_obj
|
||||
.get("blocks_left")
|
||||
.expect("Field 'blocks_left' is missing from 'None'")
|
||||
.as_i64()
|
||||
.expect("'blocks_left' is not an integer");
|
||||
|
||||
// Validate blocks_left
|
||||
assert!(
|
||||
blocks_left > 0 && blocks_left <= 180,
|
||||
"Field 'blocks_left' should be > 0 and <= 180 but got {}",
|
||||
blocks_left
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
pub async fn test_rpc_calls() {
|
||||
setup_test(SlowCancelConfig, |harness_ctx| async move {
|
||||
let alice_addr = harness_ctx.bob_params.get_concentenated_alice_address();
|
||||
let (change_address, receive_address) =
|
||||
harness_ctx.bob_params.get_change_receive_addresses().await;
|
||||
|
||||
let (client, writer, _) = setup_daemon(harness_ctx).await;
|
||||
assert!(client.is_connected());
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
|
||||
params.insert("force_refresh", false).unwrap();
|
||||
let response: HashMap<String, i32> = client
|
||||
.request("get_bitcoin_balance", params)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response, HashMap::from([("balance".to_string(), 10000000)]));
|
||||
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params.insert("log_reference_id", "test_ref_id").unwrap();
|
||||
params.insert("force_refresh", false).unwrap();
|
||||
|
||||
let _: HashMap<String, i32> = client.request("get_bitcoin_balance", params).await.unwrap();
|
||||
|
||||
assert!(writer.captured().contains(
|
||||
r#"method{method_name="Balance" log_reference_id="\"test_ref_id\""}: swap::api::request: Current Bitcoin balance as of last sync balance=0.1 BTC"#
|
||||
));
|
||||
|
||||
for method in ["get_swap_info", "resume_swap", "cancel_refund_swap"].iter() {
|
||||
let mut params = ObjectParams::new();
|
||||
params.insert("swap_id", "invalid_swap").unwrap();
|
||||
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request(method, params).await;
|
||||
response.expect_err(&format!(
|
||||
"Expected an error when swap_id is invalid for method {}",
|
||||
method
|
||||
));
|
||||
|
||||
let params = ObjectParams::new();
|
||||
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request(method, params).await;
|
||||
response.expect_err(&format!(
|
||||
"Expected an error when swap_id is missing for method {}",
|
||||
method
|
||||
));
|
||||
}
|
||||
|
||||
let params = ObjectParams::new();
|
||||
let result: Result<HashMap<String, String>, _> =
|
||||
client.request("list_sellers", params).await;
|
||||
|
||||
result.expect_err("Expected an error when rendezvous_point is missing");
|
||||
|
||||
let params = ObjectParams::new();
|
||||
let result: Result<HashMap<String, String>, _> =
|
||||
client.request("list_sellers", params).await;
|
||||
|
||||
result.expect_err("Expected an error when rendezvous_point is missing");
|
||||
|
||||
let params = ObjectParams::new();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("withdraw_btc", params).await;
|
||||
response.expect_err("Expected an error when withdraw_address is missing");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params.insert("address", "invalid_address").unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("withdraw_btc", params).await;
|
||||
response.expect_err("Expected an error when withdraw_address is malformed");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params.insert("address", BITCOIN_ADDR).unwrap();
|
||||
params.insert("amount", "0").unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("withdraw_btc", params).await;
|
||||
response.expect_err("Expected an error when amount is 0");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("address", BITCOIN_ADDR)
|
||||
.unwrap();
|
||||
params.insert("amount", "0.01").unwrap();
|
||||
let response: HashMap<String, Value> = client
|
||||
.request("withdraw_btc", params)
|
||||
.await
|
||||
.expect("Expected a valid response");
|
||||
|
||||
assert_has_keys_hashmap(&response, &["signed_tx", "amount", "txid"]);
|
||||
assert_eq!(
|
||||
response.get("amount").unwrap().as_u64().unwrap(),
|
||||
1_000_000
|
||||
);
|
||||
|
||||
let params = ObjectParams::new();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("buy_xmr", params).await;
|
||||
response.expect_err("Expected an error when no params are given");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("bitcoin_change_address", BITCOIN_ADDR)
|
||||
.unwrap();
|
||||
params
|
||||
.insert("monero_receive_address", MONERO_ADDR)
|
||||
.unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("buy_xmr", params).await;
|
||||
response.expect_err("Expected an error when seller is missing");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("bitcoin_change_address", BITCOIN_ADDR)
|
||||
.unwrap();
|
||||
params.insert("seller", SELLER).unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("buy_xmr", params).await;
|
||||
response.expect_err("Expected an error when monero_receive_address is missing");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("monero_receive_address", MONERO_ADDR)
|
||||
.unwrap();
|
||||
params.insert("seller", SELLER).unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("buy_xmr", params).await;
|
||||
response.expect_err("Expected an error when bitcoin_change_address is missing");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("bitcoin_change_address", "invalid_address")
|
||||
.unwrap();
|
||||
params
|
||||
.insert("monero_receive_address", MONERO_ADDR)
|
||||
.unwrap();
|
||||
params.insert("seller", SELLER).unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("buy_xmr", params).await;
|
||||
response.expect_err("Expected an error when bitcoin_change_address is malformed");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("bitcoin_change_address", BITCOIN_ADDR)
|
||||
.unwrap();
|
||||
params
|
||||
.insert("monero_receive_address", "invalid_address")
|
||||
.unwrap();
|
||||
params.insert("seller", SELLER).unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("buy_xmr", params).await;
|
||||
response.expect_err("Expected an error when monero_receive_address is malformed");
|
||||
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("bitcoin_change_address", BITCOIN_ADDR)
|
||||
.unwrap();
|
||||
params
|
||||
.insert("monero_receive_address", MONERO_ADDR)
|
||||
.unwrap();
|
||||
params.insert("seller", "invalid_seller").unwrap();
|
||||
let response: Result<HashMap<String, String>, _> =
|
||||
client.request("buy_xmr", params).await;
|
||||
response.expect_err("Expected an error when seller is malformed");
|
||||
|
||||
let response: Result<HashMap<String, String>, _> = client
|
||||
.request("suspend_current_swap", ObjectParams::new())
|
||||
.await;
|
||||
response.expect_err("Expected an error when no swap is running");
|
||||
|
||||
let mut params = ObjectParams::new();
|
||||
params
|
||||
.insert("bitcoin_change_address", change_address)
|
||||
.unwrap();
|
||||
params
|
||||
.insert("monero_receive_address", receive_address)
|
||||
.unwrap();
|
||||
|
||||
params.insert("seller", alice_addr).unwrap();
|
||||
let response: HashMap<String, Value> = client
|
||||
.request("buy_xmr", params)
|
||||
.await
|
||||
.expect("Expected a HashMap, got an error");
|
||||
|
||||
assert_has_keys_hashmap(&response, &["swapId"]);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
pub async fn suspend_current_swap_swap_running() {
|
||||
setup_test(SlowCancelConfig, |harness_ctx| async move {
|
||||
let (client, _, ctx) = setup_daemon(harness_ctx).await;
|
||||
|
||||
ctx.swap_lock
|
||||
.acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let cloned_ctx = ctx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Immediately release lock when suspend signal is received. Mocks a running swap that is then cancelled.
|
||||
ctx.swap_lock
|
||||
.listen_for_swap_force_suspension()
|
||||
.await
|
||||
.unwrap();
|
||||
ctx.swap_lock.release_swap_lock().await.unwrap();
|
||||
});
|
||||
|
||||
let response: HashMap<String, String> = client
|
||||
.request("suspend_current_swap", ObjectParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response,
|
||||
HashMap::from([("swapId".to_string(), SWAP_ID.to_string())])
|
||||
);
|
||||
|
||||
cloned_ctx
|
||||
.swap_lock
|
||||
.acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response: Result<HashMap<String, String>, _> = client
|
||||
.request("suspend_current_swap", ObjectParams::new())
|
||||
.await;
|
||||
response.expect_err("Expected an error when suspend signal times out");
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue