diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index adedb9b0..5f520241 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -73,7 +73,7 @@ async fn run_until_internal( return Ok(state); } - match state { + let new_state = match state { AliceState::Started { state3 } => { timeout( env_config.bob_time_to_act, @@ -88,22 +88,7 @@ async fn run_until_internal( }) .await?; - let state = AliceState::BtcLocked { state3 }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await + AliceState::BtcLocked { state3 } } AliceState::BtcLocked { state3 } => { // Record the current monero wallet block height so we don't have to scan from @@ -123,105 +108,60 @@ async fn run_until_internal( .send_transfer_proof(transfer_proof) .await?; - let state = AliceState::XmrLocked { + AliceState::XmrLocked { state3, monero_wallet_restore_blockheight, - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } AliceState::XmrLocked { state3, monero_wallet_restore_blockheight, - } => { - let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { - ExpiredTimelocks::None => { - select! { - _ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } + } => match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { + ExpiredTimelocks::None => { + select! { + _ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => { + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, } - enc_sig = event_loop_handle.recv_encrypted_signature() => { - tracing::info!("Received encrypted signature"); + } + enc_sig = event_loop_handle.recv_encrypted_signature() => { + tracing::info!("Received encrypted signature"); - AliceState::EncSigLearned { - state3, - encrypted_signature: Box::new(enc_sig?), - monero_wallet_restore_blockheight, - } + AliceState::EncSigLearned { + state3, + encrypted_signature: Box::new(enc_sig?), + monero_wallet_restore_blockheight, } } } - _ => AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - }, - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } + } + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, + }, AliceState::EncSigLearned { state3, encrypted_signature, monero_wallet_restore_blockheight, - } => { - let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { - ExpiredTimelocks::None => { - match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete( - *encrypted_signature, - state3.a.clone(), - state3.s_a.to_secpfun_scalar(), - state3.B, - ) { - Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { - Ok((_, finality)) => match finality.await { - Ok(_) => AliceState::BtcRedeemed, - Err(e) => { - bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) - } - }, + } => match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { + ExpiredTimelocks::None => { + match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete( + *encrypted_signature, + state3.a.clone(), + state3.s_a.to_secpfun_scalar(), + state3.B, + ) { + Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { + Ok((_, finality)) => match finality.await { + Ok(_) => AliceState::BtcRedeemed, Err(e) => { - error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); - state3 - .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) - .await?; - - AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - } + bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e) } }, Err(e) => { - error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e); + error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e); state3 .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) .await?; @@ -231,29 +171,25 @@ async fn run_until_internal( monero_wallet_restore_blockheight, } } + }, + Err(e) => { + error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e); + state3 + .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) + .await?; + + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } } } - _ => AliceState::CancelTimelockExpired { - state3, - monero_wallet_restore_blockheight, - }, - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } + } + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, + }, AliceState::CancelTimelockExpired { state3, monero_wallet_restore_blockheight, @@ -281,24 +217,10 @@ async fn run_until_internal( // returned mined block height } - let state = AliceState::BtcCancelled { + AliceState::BtcCancelled { state3, monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet, - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } AliceState::BtcCancelled { state3, @@ -314,7 +236,7 @@ async fn run_until_internal( status.is_confirmed_with(state3.punish_timelock) }); - let state = tokio::select! { + select! { seen_refund = seen_refund_tx => { seen_refund.context("Failed to monitor refund transaction")?; let published_refund_tx = bitcoin_wallet.get_raw_transaction(tx_refund.txid()).await?; @@ -338,23 +260,7 @@ async fn run_until_internal( monero_wallet_restore_blockheight, } } - }; - - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } AliceState::BtcRefunded { spend_key, @@ -367,11 +273,7 @@ async fn run_until_internal( .create_from(spend_key, view_key, monero_wallet_restore_blockheight) .await?; - let state = AliceState::XmrRefunded; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - Ok(state) + AliceState::XmrRefunded } AliceState::BtcPunishable { state3, @@ -409,51 +311,36 @@ async fn run_until_internal( state3.a.clone(), state3.S_b_bitcoin, )?; - let state = AliceState::BtcRefunded { + AliceState::BtcRefunded { spend_key, state3, monero_wallet_restore_blockheight, - }; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await + } } Either::Left((Err(e), _)) => { bail!(e.context("Failed to monitor refund transaction")) } - Either::Right(_) => { - let state = AliceState::BtcPunished; - let db_state = (&state).into(); - db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) - .await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - bitcoin_wallet.clone(), - monero_wallet, - env_config, - swap_id, - db, - ) - .await - } + Either::Right(_) => AliceState::BtcPunished, } } - AliceState::XmrRefunded => Ok(AliceState::XmrRefunded), - AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed), - AliceState::BtcPunished => Ok(AliceState::BtcPunished), - AliceState::SafelyAborted => Ok(AliceState::SafelyAborted), - } + AliceState::XmrRefunded => AliceState::XmrRefunded, + AliceState::BtcRedeemed => AliceState::BtcRedeemed, + AliceState::BtcPunished => AliceState::BtcPunished, + AliceState::SafelyAborted => AliceState::SafelyAborted, + }; + + let db_state = (&new_state).into(); + db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) + .await?; + run_until_internal( + new_state, + is_target_state, + event_loop_handle, + bitcoin_wallet, + monero_wallet, + env_config, + swap_id, + db, + ) + .await } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index f6b7ea78..0a7dabe6 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -66,7 +66,7 @@ async fn run_until_internal( return Ok(state); } - match state { + let new_state = match state { BobState::Started { btc_amount } => { let bitcoin_refund_address = bitcoin_wallet.new_address().await?; @@ -80,21 +80,7 @@ async fn run_until_internal( ) .await?; - let state = BobState::ExecutionSetupDone(state2); - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + BobState::ExecutionSetupDone(state2) } BobState::ExecutionSetupDone(state2) => { // Do not lock Bitcoin if not connected to Alice. @@ -107,28 +93,12 @@ async fn run_until_internal( .context("Failed to sign Bitcoin lock transaction")?; let (..) = bitcoin_wallet.broadcast(signed_tx, "lock").await?; - let state = BobState::BtcLocked(state3); - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + BobState::BtcLocked(state3) } // Bob has locked Btc // Watch for Alice to Lock Xmr or for cancel timelock to elapse BobState::BtcLocked(state3) => { - let state = if let ExpiredTimelocks::None = - state3.current_epoch(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state3.current_epoch(bitcoin_wallet.as_ref()).await? { event_loop_handle.dial().await?; let transfer_proof_watcher = event_loop_handle.recv_transfer_proof(); @@ -163,30 +133,14 @@ async fn run_until_internal( } else { let state4 = state3.cancel(); BobState::CancelTimelockExpired(state4) - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::XmrLockProofReceived { state, lock_transfer_proof, monero_wallet_restore_blockheight, } => { - let state = if let ExpiredTimelocks::None = - state.current_epoch(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state.current_epoch(bitcoin_wallet.as_ref()).await? { event_loop_handle.dial().await?; let xmr_lock_watcher = state.clone().watch_for_lock_xmr( @@ -217,27 +171,10 @@ async fn run_until_internal( } else { let state4 = state.cancel(); BobState::CancelTimelockExpired(state4) - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::XmrLocked(state) => { - let state = if let ExpiredTimelocks::None = - state.expired_timelock(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? { event_loop_handle.dial().await?; // Alice has locked Xmr // Bob sends Alice his key @@ -261,26 +198,10 @@ async fn run_until_internal( } } else { BobState::CancelTimelockExpired(state) - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::EncSigSent(state) => { - let state = if let ExpiredTimelocks::None = - state.expired_timelock(bitcoin_wallet.as_ref()).await? - { + if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? { let state_clone = state.clone(); let redeem_watcher = state_clone.watch_for_redeem_btc(bitcoin_wallet.as_ref()); let cancel_timelock_expires = @@ -296,22 +217,7 @@ async fn run_until_internal( } } else { BobState::CancelTimelockExpired(state) - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet.clone(), - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::BtcRedeemed(state) => { // Bob redeems XMR using revealed s_a @@ -326,23 +232,9 @@ async fn run_until_internal( tracing::info!("Sent XMR to {} in tx {}", receive_monero_address, tx_hash.0); } - let state = BobState::XmrRedeemed { + BobState::XmrRedeemed { tx_lock_id: state.tx_lock_id(), - }; - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } BobState::CancelTimelockExpired(state4) => { if state4 @@ -353,26 +245,11 @@ async fn run_until_internal( state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?; } - let state = BobState::BtcCancelled(state4); - db.insert_latest_state(swap_id, Swap::Bob(state.clone().into())) - .await?; - - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + BobState::BtcCancelled(state4) } BobState::BtcCancelled(state) => { // Bob has cancelled the swap - let state = match state.expired_timelock(bitcoin_wallet.as_ref()).await? { + match state.expired_timelock(bitcoin_wallet.as_ref()).await? { ExpiredTimelocks::None => { bail!( "Internal error: canceled state reached before cancel timelock was expired" @@ -385,28 +262,28 @@ async fn run_until_internal( ExpiredTimelocks::Punish => BobState::BtcPunished { tx_lock_id: state.tx_lock_id(), }, - }; - - let db_state = state.clone().into(); - db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; - run_until_internal( - state, - is_target_state, - event_loop_handle, - db, - bitcoin_wallet, - monero_wallet, - swap_id, - env_config, - receive_monero_address, - ) - .await + } } - BobState::BtcRefunded(state4) => Ok(BobState::BtcRefunded(state4)), - BobState::BtcPunished { tx_lock_id } => Ok(BobState::BtcPunished { tx_lock_id }), - BobState::SafelyAborted => Ok(BobState::SafelyAborted), - BobState::XmrRedeemed { tx_lock_id } => Ok(BobState::XmrRedeemed { tx_lock_id }), - } + BobState::BtcRefunded(state4) => BobState::BtcRefunded(state4), + BobState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id }, + BobState::SafelyAborted => BobState::SafelyAborted, + BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, + }; + + let db_state = new_state.clone().into(); + db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; + run_until_internal( + new_state, + is_target_state, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + swap_id, + env_config, + receive_monero_address, + ) + .await } pub async fn request_price_and_setup(