From daa572e5bf7fd1f42c26adaecff08e0b4989a32a Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Wed, 28 Apr 2021 16:13:04 +1000 Subject: [PATCH] Move recovery commands in dedicated module Less clutter in the folder structure. --- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 8 ++ bors.toml | 6 +- swap/src/asb/command.rs | 28 ++++++ swap/src/bin/asb.rs | 34 ++++++- swap/src/protocol/alice.rs | 11 ++- swap/src/protocol/alice/recovery.rs | 5 + .../protocol/alice/{ => recovery}/cancel.rs | 0 swap/src/protocol/alice/recovery/punish.rs | 98 +++++++++++++++++++ swap/src/protocol/alice/recovery/redeem.rs | 84 ++++++++++++++++ .../protocol/alice/{ => recovery}/refund.rs | 0 .../protocol/alice/recovery/safely_abort.rs | 38 +++++++ swap/src/protocol/alice/state.rs | 12 +++ swap/src/protocol/alice/swap.rs | 11 +-- .../alice_manually_punishes_after_bob_dead.rs | 91 +++++++++++++++++ ..._manually_redeems_after_enc_sig_learned.rs | 42 ++++++++ ... alice_punishes_after_restart_bob_dead.rs} | 9 +- 17 files changed, 460 insertions(+), 23 deletions(-) create mode 100644 swap/src/protocol/alice/recovery.rs rename swap/src/protocol/alice/{ => recovery}/cancel.rs (100%) create mode 100644 swap/src/protocol/alice/recovery/punish.rs create mode 100644 swap/src/protocol/alice/recovery/redeem.rs rename swap/src/protocol/alice/{ => recovery}/refund.rs (100%) create mode 100644 swap/src/protocol/alice/recovery/safely_abort.rs create mode 100644 swap/tests/alice_manually_punishes_after_bob_dead.rs create mode 100644 swap/tests/alice_manually_redeems_after_enc_sig_learned.rs rename swap/tests/{alice_punishes_after_restart_punish_timelock_expired.rs => alice_punishes_after_restart_bob_dead.rs} (85%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73c717ad..98997780 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,11 +104,13 @@ jobs: alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired, alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired_force, punish, - alice_punishes_after_restart_punish_timelock_expired, + alice_punishes_after_restart_bob_dead, + alice_manually_punishes_after_bob_dead, alice_refunds_after_restart_bob_refunded, ensure_same_swap_id, concurrent_bobs_after_xmr_lock_proof_sent, - concurrent_bobs_before_xmr_lock_proof_sent + concurrent_bobs_before_xmr_lock_proof_sent, + alice_manually_redeems_after_enc_sig_learned ] runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index aa66b285..a6c0c6a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Using the refund command to refund the XMR locked by the ASB requires the CLI to first refund the BTC of the swap. If the BTC was not refunded yet the command will print an error accordingly. The command has a `--force` flag that allows executing the command without checking for cancel constraints. +- Punish command for the ASB that allows punishing a specific swap by id. + Includes a `--force` parameter that when set disables the punish timelock check and verifying that the swap is in a cancelled state already. +- Abort command for the ASB that allows safely aborting a specific swap. + Only swaps in a state prior to locking XMR can be safely aborted. +- Redeem command for the ASB that allows redeeming a specific swap. + Only swaps where we learned the encrypted signature are redeemable. + The command checks for expired timelocks to ensure redeeming is safe, but the timelock check can be disable using the `--force` flag. + By default we wait for finality of the redeem transaction; this can be disabled by setting `--do-not-await-finality`. - Resume-only mode for the ASB. When started with `--resume-only` the ASB does not accept new, incoming swap requests but only finishes swaps that are resumed upon startup. diff --git a/bors.toml b/bors.toml index 77501d38..70016fc1 100644 --- a/bors.toml +++ b/bors.toml @@ -14,9 +14,11 @@ status = [ "docker_tests (alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired)", "docker_tests (alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired_force)", "docker_tests (punish)", - "docker_tests (alice_punishes_after_restart_punish_timelock_expired)", + "docker_tests (alice_punishes_after_restart_bob_dead)", + "docker_tests (alice_manually_punishes_after_bob_dead)", "docker_tests (alice_refunds_after_restart_bob_refunded)", "docker_tests (ensure_same_swap_id)", "docker_tests (concurrent_bobs_after_xmr_lock_proof_sent)", - "docker_tests (concurrent_bobs_before_xmr_lock_proof_sent)" + "docker_tests (concurrent_bobs_before_xmr_lock_proof_sent)", + "docker_tests (alice_manually_redeems_after_enc_sig_learned)" ] diff --git a/swap/src/asb/command.rs b/swap/src/asb/command.rs index 19f140a1..4ad8e118 100644 --- a/swap/src/asb/command.rs +++ b/swap/src/asb/command.rs @@ -65,6 +65,19 @@ pub enum Command { #[derive(structopt::StructOpt, Debug)] pub enum ManualRecovery { + #[structopt( + about = "Publishes the Bitcoin redeem transaction. This requires that we learned the encrypted signature from Bob and is only safe if no timelock has expired." + )] + Redeem { + #[structopt(flatten)] + redeem_params: RecoverCommandParams, + + #[structopt( + long = "do_not_await_finality", + help = "If this flag is present we exit directly after publishing the redeem transaction without waiting for the transaction to be included in a block" + )] + do_not_await_finality: bool, + }, #[structopt( about = "Publishes the Bitcoin cancel transaction. By default, the cancel timelock will be enforced. A confirmed cancel transaction enables refund and punish." )] @@ -79,6 +92,21 @@ pub enum ManualRecovery { #[structopt(flatten)] refund_params: RecoverCommandParams, }, + #[structopt( + about = "Publishes the Bitcoin punish transaction. By default, the punish timelock and a swap-state where the cancel transaction was already published will be enforced." + )] + Punish { + #[structopt(flatten)] + punish_params: RecoverCommandParams, + }, + #[structopt(about = "Safely Abort requires the swap to be in a state prior to locking XMR.")] + SafelyAbort { + #[structopt( + long = "swap-id", + help = "The swap id can be retrieved using the history subcommand" + )] + swap_id: Uuid, + }, } #[derive(structopt::StructOpt, Debug)] diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index 9a5d920f..55158343 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -31,7 +31,7 @@ use swap::monero::Amount; use swap::network::swarm; use swap::protocol::alice; use swap::protocol::alice::event_loop::KrakenRate; -use swap::protocol::alice::{run, EventLoop}; +use swap::protocol::alice::{redeem, run, EventLoop}; use swap::seed::Seed; use swap::tor::AuthenticatedClient; use swap::{asb, bitcoin, env, kraken, monero, tor}; @@ -233,6 +233,38 @@ async fn main() -> Result<()> { tracing::info!("Monero successfully refunded"); } + Command::ManualRecovery(ManualRecovery::Punish { + punish_params: RecoverCommandParams { swap_id, force }, + }) => { + let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; + + let (txid, _) = + alice::punish(swap_id, Arc::new(bitcoin_wallet), Arc::new(db), force).await??; + + tracing::info!("Punish transaction successfully published with id {}", txid); + } + Command::ManualRecovery(ManualRecovery::SafelyAbort { swap_id }) => { + alice::safely_abort(swap_id, Arc::new(db)).await?; + + tracing::info!("Swap safely aborted"); + } + Command::ManualRecovery(ManualRecovery::Redeem { + redeem_params: RecoverCommandParams { swap_id, force }, + do_not_await_finality, + }) => { + let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; + + let (txid, _) = alice::redeem( + swap_id, + Arc::new(bitcoin_wallet), + Arc::new(db), + force, + redeem::Finality::from_bool(do_not_await_finality), + ) + .await?; + + tracing::info!("Redeem transaction successfully published with id {}", txid); + } }; Ok(()) diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index d21617e1..9d5394ba 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -7,17 +7,20 @@ use std::sync::Arc; use uuid::Uuid; pub use self::behaviour::{Behaviour, OutEvent}; -pub use self::cancel::cancel; pub use self::event_loop::{EventLoop, EventLoopHandle}; -pub use self::refund::refund; +pub use self::recovery::cancel::cancel; +pub use self::recovery::punish::punish; +pub use self::recovery::redeem::redeem; +pub use self::recovery::refund::refund; +pub use self::recovery::safely_abort::safely_abort; +pub use self::recovery::{cancel, punish, redeem, refund, safely_abort}; pub use self::state::*; pub use self::swap::{run, run_until}; mod behaviour; -pub mod cancel; pub mod event_loop; mod execution_setup; -pub mod refund; +mod recovery; mod spot_price; pub mod state; pub mod swap; diff --git a/swap/src/protocol/alice/recovery.rs b/swap/src/protocol/alice/recovery.rs new file mode 100644 index 00000000..dd4a7b86 --- /dev/null +++ b/swap/src/protocol/alice/recovery.rs @@ -0,0 +1,5 @@ +pub mod cancel; +pub mod punish; +pub mod redeem; +pub mod refund; +pub mod safely_abort; diff --git a/swap/src/protocol/alice/cancel.rs b/swap/src/protocol/alice/recovery/cancel.rs similarity index 100% rename from swap/src/protocol/alice/cancel.rs rename to swap/src/protocol/alice/recovery/cancel.rs diff --git a/swap/src/protocol/alice/recovery/punish.rs b/swap/src/protocol/alice/recovery/punish.rs new file mode 100644 index 00000000..488017be --- /dev/null +++ b/swap/src/protocol/alice/recovery/punish.rs @@ -0,0 +1,98 @@ +use crate::bitcoin::{self, ExpiredTimelocks, Txid}; +use crate::database::{Database, Swap}; +use crate::protocol::alice::AliceState; +use anyhow::{bail, Result}; +use std::sync::Arc; +use uuid::Uuid; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + // Errors indicating the the swap can *currently* not be refunded but might be later + #[error("Cannot punish because swap is not in a cancelled state. Make sure to cancel the swap before trying to punish or use --force.")] + SwapNotCancelled, + #[error("The punish timelock has not expired yet because the timelock has not expired. Please try again later")] + PunishTimelockNotExpiredYet, + + // Errors indicating that the swap cannot be refunded because it is in a abort/final state + // state + #[error("Cannot punish swap because it is in state {0} where no BTC was locked. Try aborting instead.")] + NoBtcLocked(AliceState), + #[error("Cannot punish swap because it is in state {0} which is not punishable")] + SwapNotPunishable(AliceState), +} + +pub async fn punish( + swap_id: Uuid, + bitcoin_wallet: Arc, + db: Arc, + force: bool, +) -> Result> { + let state = db.get_state(swap_id)?.try_into_alice()?.into(); + + let state3 = if force { + match state { + + // In case no XMR has been locked, move to Safely Aborted + AliceState::Started { .. } => bail!(Error::NoBtcLocked(state)), + + // Punish potentially possible (no knowledge of cancel transaction) + AliceState::BtcLocked { state3, .. } + | AliceState::XmrLockTransactionSent {state3, ..} + | AliceState::XmrLocked {state3, ..} + | AliceState::XmrLockTransferProofSent {state3, ..} + | AliceState::EncSigLearned {state3, ..} + | AliceState::CancelTimelockExpired {state3, ..} + + // Punish possible due to cancel transaction already being published + | AliceState::BtcCancelled {state3, ..} + | AliceState::BtcPunishable {state3, ..} => { + state3 + } + + // If the swap was refunded it cannot be punished + AliceState::BtcRefunded {..} + // Alice already in final state + | AliceState::BtcRedeemed + | AliceState::XmrRefunded + | AliceState::BtcPunished + | AliceState::SafelyAborted => bail!(Error::SwapNotPunishable(state)), + } + } else { + match state { + AliceState::Started { .. } => { + bail!(Error::NoBtcLocked(state)) + } + + AliceState::BtcCancelled { state3, .. } | AliceState::BtcPunishable { state3, .. } => { + state3 + } + + AliceState::BtcRefunded { .. } + | AliceState::BtcRedeemed + | AliceState::XmrRefunded + | AliceState::BtcPunished + | AliceState::SafelyAborted => bail!(Error::SwapNotPunishable(state)), + + _ => return Ok(Err(Error::SwapNotCancelled)), + } + }; + + tracing::info!(%swap_id, "Trying to manually punish swap"); + + if !force { + tracing::debug!(%swap_id, "Checking if punish timelock is expired"); + + if let ExpiredTimelocks::Cancel = state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { + return Ok(Err(Error::PunishTimelockNotExpiredYet)); + } + } + + let txid = state3.punish_btc(&bitcoin_wallet).await?; + + let state = AliceState::BtcPunished; + let db_state = (&state).into(); + db.insert_latest_state(swap_id, Swap::Alice(db_state)) + .await?; + + Ok(Ok((txid, state))) +} diff --git a/swap/src/protocol/alice/recovery/redeem.rs b/swap/src/protocol/alice/recovery/redeem.rs new file mode 100644 index 00000000..12999db9 --- /dev/null +++ b/swap/src/protocol/alice/recovery/redeem.rs @@ -0,0 +1,84 @@ +use crate::bitcoin::{ExpiredTimelocks, Txid, Wallet}; +use crate::database::{Database, Swap}; +use crate::protocol::alice::AliceState; +use anyhow::{bail, Result}; +use std::sync::Arc; +use uuid::Uuid; + +pub enum Finality { + Await, + NotAwait, +} + +impl Finality { + pub fn from_bool(do_not_await_finality: bool) -> Self { + if do_not_await_finality { + Self::NotAwait + } else { + Self::Await + } + } +} + +pub async fn redeem( + swap_id: Uuid, + bitcoin_wallet: Arc, + db: Arc, + force: bool, + finality: Finality, +) -> Result<(Txid, AliceState)> { + let state = db.get_state(swap_id)?.try_into_alice()?.into(); + + match state { + AliceState::EncSigLearned { + state3, + encrypted_signature, + .. + } => { + tracing::info!(%swap_id, "Trying to redeem swap"); + + if !force { + tracing::debug!(%swap_id, "Checking if timelocks have expired"); + + let expired_timelocks = state3.expired_timelocks(bitcoin_wallet.as_ref()).await?; + match expired_timelocks { + ExpiredTimelocks::None => (), + _ => bail!("{:?} timelock already expired, consider using refund or punish. You can use --force to publish the redeem transaction, but be aware that it is not safe to do so anymore!", expired_timelocks) + } + } + + let redeem_tx = state3.signed_redeem_transaction(*encrypted_signature)?; + let (txid, subscription) = bitcoin_wallet.broadcast(redeem_tx, "redeem").await?; + + if let Finality::Await = finality { + subscription.wait_until_final().await?; + } + + let state = AliceState::BtcRedeemed; + let db_state = (&state).into(); + + db.insert_latest_state(swap_id, Swap::Alice(db_state)) + .await?; + + Ok((txid, state)) + } + + AliceState::Started { .. } + | AliceState::BtcLocked { .. } + | AliceState::XmrLockTransactionSent { .. } + | AliceState::XmrLocked { .. } + | AliceState::XmrLockTransferProofSent { .. } + | AliceState::CancelTimelockExpired { .. } + | AliceState::BtcCancelled { .. } + | AliceState::BtcRefunded { .. } + | AliceState::BtcPunishable { .. } + | AliceState::BtcRedeemed + | AliceState::XmrRefunded + | AliceState::BtcPunished + | AliceState::SafelyAborted => bail!( + "Cannot redeem swap {} because it is in state {} which cannot be manually redeemed", + swap_id, + state + ), + } +} diff --git a/swap/src/protocol/alice/refund.rs b/swap/src/protocol/alice/recovery/refund.rs similarity index 100% rename from swap/src/protocol/alice/refund.rs rename to swap/src/protocol/alice/recovery/refund.rs diff --git a/swap/src/protocol/alice/recovery/safely_abort.rs b/swap/src/protocol/alice/recovery/safely_abort.rs new file mode 100644 index 00000000..605c5029 --- /dev/null +++ b/swap/src/protocol/alice/recovery/safely_abort.rs @@ -0,0 +1,38 @@ +use crate::database::{Database, Swap}; +use crate::protocol::alice::AliceState; +use anyhow::{bail, Result}; +use std::sync::Arc; +use uuid::Uuid; + +pub async fn safely_abort(swap_id: Uuid, db: Arc) -> Result { + let state = db.get_state(swap_id)?.try_into_alice()?.into(); + + match state { + AliceState::Started { .. } | AliceState::BtcLocked { .. } => { + let state = AliceState::SafelyAborted; + + let db_state = (&state).into(); + db.insert_latest_state(swap_id, Swap::Alice(db_state)) + .await?; + + Ok(state) + } + + AliceState::XmrLockTransactionSent { .. } + | AliceState::XmrLocked { .. } + | AliceState::XmrLockTransferProofSent { .. } + | AliceState::EncSigLearned { .. } + | AliceState::CancelTimelockExpired { .. } + | AliceState::BtcCancelled { .. } + | AliceState::BtcRefunded { .. } + | AliceState::BtcPunishable { .. } + | AliceState::BtcRedeemed + | AliceState::XmrRefunded + | AliceState::BtcPunished + | AliceState::SafelyAborted => bail!( + "Cannot safely abort swap {} because it is in state {} which cannot be safely aborted", + swap_id, + state + ), + } +} diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index 3840d28f..d8b7d004 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -510,6 +510,18 @@ impl State3 { Ok(()) } + pub async fn punish_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result { + let signed_tx_punish = self.signed_punish_transaction()?; + + async { + let (txid, subscription) = bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?; + subscription.wait_until_final().await?; + + Result::<_, anyhow::Error>::Ok(txid) + } + .await + } + pub fn signed_redeem_transaction( &self, sig: bitcoin::EncryptedSignature, diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 1b90d9f7..14002f60 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -302,16 +302,7 @@ async fn next_state( transfer_proof, state3, } => { - let signed_tx_punish = state3.signed_punish_transaction()?; - - let punish = async { - let (txid, subscription) = - bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?; - subscription.wait_until_final().await?; - - Result::<_, anyhow::Error>::Ok(txid) - } - .await; + let punish = state3.punish_btc(bitcoin_wallet).await; match punish { Ok(_) => AliceState::BtcPunished, diff --git a/swap/tests/alice_manually_punishes_after_bob_dead.rs b/swap/tests/alice_manually_punishes_after_bob_dead.rs new file mode 100644 index 00000000..1bf71d62 --- /dev/null +++ b/swap/tests/alice_manually_punishes_after_bob_dead.rs @@ -0,0 +1,91 @@ +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::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. +#[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)); + + 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) = alice::cancel( + alice_swap.swap_id, + alice_swap.bitcoin_wallet, + alice_swap.db, + false, + ) + .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) = alice::punish( + alice_swap.swap_id, + alice_swap.bitcoin_wallet, + alice_swap.db, + false, + ) + .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_punished(bob_state).await; + + Ok(()) + }) + .await; +} diff --git a/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs b/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs new file mode 100644 index 00000000..38a0da9b --- /dev/null +++ b/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs @@ -0,0 +1,42 @@ +pub mod harness; + +use harness::alice_run_until::is_encsig_learned; +use harness::SlowCancelConfig; +use swap::protocol::alice::redeem::Finality; +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)); + + 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) = alice::redeem( + alice_swap.swap_id, + alice_swap.bitcoin_wallet, + alice_swap.db, + false, + 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; +} diff --git a/swap/tests/alice_punishes_after_restart_punish_timelock_expired.rs b/swap/tests/alice_punishes_after_restart_bob_dead.rs similarity index 85% rename from swap/tests/alice_punishes_after_restart_punish_timelock_expired.rs rename to swap/tests/alice_punishes_after_restart_bob_dead.rs index 9157a19c..377b7b47 100644 --- a/swap/tests/alice_punishes_after_restart_punish_timelock_expired.rs +++ b/swap/tests/alice_punishes_after_restart_bob_dead.rs @@ -8,9 +8,9 @@ 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. +/// the encsig and fail to refund or redeem. Alice cancels and punishes. #[tokio::test] -async fn alice_punishes_after_restart_if_punish_timelock_expired() { +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; @@ -26,12 +26,13 @@ async fn alice_punishes_after_restart_if_punish_timelock_expired() { let alice_state = alice_swap.await??; - // Ensure punish timelock is expired + // 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.punish_timelock) + .wait_until_confirmed_with(state3.cancel_timelock) .await?; } else { panic!("Alice in unexpected state {}", alice_state);