From 3593f5323ae09d43f8b0428c5b4e313a63a5114c Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Thu, 21 Jan 2021 16:39:30 +1100 Subject: [PATCH] Bob saves lock proof after received so he can resume swap --- monero-harness/src/rpc/wallet.rs | 2 +- swap/src/database/bob.rs | 28 +++++++ swap/src/monero.rs | 4 +- swap/src/protocol/alice/steps.rs | 3 + swap/src/protocol/bob/state.rs | 15 +++- swap/src/protocol/bob/swap.rs | 74 ++++++++++++++----- ...h_restart_bob_after_lock_proof_received.rs | 34 +++++++++ 7 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 swap/tests/happy_path_restart_bob_after_lock_proof_received.rs diff --git a/monero-harness/src/rpc/wallet.rs b/monero-harness/src/rpc/wallet.rs index 8445572a..d31a8b21 100644 --- a/monero-harness/src/rpc/wallet.rs +++ b/monero-harness/src/rpc/wallet.rs @@ -382,7 +382,7 @@ pub struct Transfer { pub unsigned_txset: String, } -#[derive(Clone, Copy, Debug, Deserialize)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] pub struct BlockHeight { pub height: u32, } diff --git a/swap/src/database/bob.rs b/swap/src/database/bob.rs index 6eaf159a..e21bb8d2 100644 --- a/swap/src/database/bob.rs +++ b/swap/src/database/bob.rs @@ -1,8 +1,10 @@ use crate::{ + monero::TransferProof, protocol::{bob, bob::BobState}, SwapAmounts, }; use ::bitcoin::hashes::core::fmt::Display; +use monero_harness::rpc::wallet::BlockHeight; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] @@ -17,6 +19,11 @@ pub enum Bob { BtcLocked { state3: bob::State3, }, + XmrLockProofReceived { + state: bob::State3, + lock_transfer_proof: TransferProof, + monero_wallet_restore_blockheight: BlockHeight, + }, XmrLocked { state4: bob::State4, }, @@ -43,6 +50,15 @@ impl From for Bob { BobState::Started { state0, amounts } => Bob::Started { state0, amounts }, BobState::Negotiated(state2) => Bob::Negotiated { state2 }, BobState::BtcLocked(state3) => Bob::BtcLocked { state3 }, + BobState::XmrLockProofReceived { + state, + lock_transfer_proof, + monero_wallet_restore_blockheight, + } => Bob::XmrLockProofReceived { + state, + lock_transfer_proof, + monero_wallet_restore_blockheight, + }, BobState::XmrLocked(state4) => Bob::XmrLocked { state4 }, BobState::EncSigSent(state4) => Bob::EncSigSent { state4 }, BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5), @@ -66,6 +82,15 @@ impl From for BobState { Bob::Started { state0, amounts } => BobState::Started { state0, amounts }, Bob::Negotiated { state2 } => BobState::Negotiated(state2), Bob::BtcLocked { state3 } => BobState::BtcLocked(state3), + Bob::XmrLockProofReceived { + state, + lock_transfer_proof, + monero_wallet_restore_blockheight, + } => BobState::XmrLockProofReceived { + state, + lock_transfer_proof, + monero_wallet_restore_blockheight, + }, Bob::XmrLocked { state4 } => BobState::XmrLocked(state4), Bob::EncSigSent { state4 } => BobState::EncSigSent(state4), Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5), @@ -87,6 +112,9 @@ impl Display for Bob { Bob::Started { .. } => write!(f, "Started"), Bob::Negotiated { .. } => f.write_str("Negotiated"), Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"), + Bob::XmrLockProofReceived { .. } => { + f.write_str("XMR lock transaction transfer proof received") + } Bob::XmrLocked { .. } => f.write_str("Monero locked"), Bob::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"), Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"), diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 1b65bc10..caec8864 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -139,7 +139,7 @@ impl Display for Amount { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TransferProof { tx_hash: TxHash, #[serde(with = "monero_private_key")] @@ -159,7 +159,7 @@ impl TransferProof { } // TODO: add constructor/ change String to fixed length byte array -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct TxHash(pub String); impl From for String { diff --git a/swap/src/protocol/alice/steps.rs b/swap/src/protocol/alice/steps.rs index fddbed95..1d593241 100644 --- a/swap/src/protocol/alice/steps.rs +++ b/swap/src/protocol/alice/steps.rs @@ -150,6 +150,9 @@ pub async fn wait_for_bitcoin_encrypted_signature( .recv_message3() .await .context("Failed to receive Bitcoin encrypted signature from Bob")?; + + tracing::debug!("Message 3 received, returning it"); + Ok(msg3.tx_redeem_encsig) } diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 9e0de62e..e0e3e875 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -6,7 +6,7 @@ use crate::{ }, config::Config, monero, - monero::monero_private_key, + monero::{monero_private_key, TransferProof}, protocol::{alice, bob}, ExpiredTimelocks, SwapAmounts, }; @@ -16,6 +16,7 @@ use ecdsa_fun::{ nonce::Deterministic, Signature, }; +use monero_harness::rpc::wallet::BlockHeight; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use sha2::Sha256; @@ -29,6 +30,11 @@ pub enum BobState { }, Negotiated(State2), BtcLocked(State3), + XmrLockProofReceived { + state: State3, + lock_transfer_proof: TransferProof, + monero_wallet_restore_blockheight: BlockHeight, + }, XmrLocked(State4), EncSigSent(State4), BtcRedeemed(State5), @@ -50,6 +56,9 @@ impl fmt::Display for BobState { BobState::Started { .. } => write!(f, "started"), BobState::Negotiated(..) => write!(f, "negotiated"), BobState::BtcLocked(..) => write!(f, "btc is locked"), + BobState::XmrLockProofReceived { .. } => { + write!(f, "XMR lock transaction transfer proof received") + } BobState::XmrLocked(..) => write!(f, "xmr is locked"), BobState::EncSigSent(..) => write!(f, "encrypted signature is sent"), BobState::BtcRedeemed(..) => write!(f, "btc is redeemed"), @@ -311,7 +320,7 @@ impl State3 { pub async fn watch_for_lock_xmr( self, xmr_wallet: &W, - msg: alice::Message2, + transfer_proof: TransferProof, monero_wallet_restore_blockheight: u32, ) -> Result where @@ -326,7 +335,7 @@ impl State3 { .watch_for_transfer( S, self.v.public(), - msg.tx_lock_proof, + transfer_proof, self.xmr, self.min_monero_confirmations, ) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 5cf65b5d..85dcd2be 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -11,7 +11,7 @@ use async_recursion::async_recursion; use rand::{rngs::OsRng, CryptoRng, RngCore}; use std::sync::Arc; use tokio::select; -use tracing::{debug, info}; +use tracing::info; use uuid::Uuid; pub fn is_complete(state: &BobState) -> bool { @@ -28,6 +28,10 @@ pub fn is_btc_locked(state: &BobState) -> bool { matches!(state, BobState::BtcLocked(..)) } +pub fn is_lock_proof_received(state: &BobState) -> bool { + matches!(state, BobState::XmrLockProofReceived { .. }) +} + pub fn is_xmr_locked(state: &BobState) -> bool { matches!(state, BobState::XmrLocked(..)) } @@ -155,23 +159,12 @@ where msg2 = msg2_watcher => { let msg2 = msg2?; - info!("Received XMR lock transaction transfer proof from Alice, watching for transfer confirmations"); - debug!("Transfer proof: {:?}", msg2.tx_lock_proof); - - let xmr_lock_watcher = state3.clone() - .watch_for_lock_xmr(monero_wallet.as_ref(), msg2, monero_wallet_restore_blockheight.height); - let cancel_timelock_expires = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); - - select! { - state4 = xmr_lock_watcher => { - BobState::XmrLocked(state4?) - }, - _ = cancel_timelock_expires => { - let state4 = state3.state4(); - BobState::CancelTimelockExpired(state4) - } - } + BobState::XmrLockProofReceived { + state: state3, + lock_transfer_proof: msg2.tx_lock_proof, + monero_wallet_restore_blockheight + } }, _ = cancel_timelock_expires => { let state4 = state3.state4(); @@ -197,6 +190,53 @@ where ) .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? + { + event_loop_handle.dial().await?; + + let xmr_lock_watcher = state.clone().watch_for_lock_xmr( + monero_wallet.as_ref(), + lock_transfer_proof, + monero_wallet_restore_blockheight.height, + ); + let cancel_timelock_expires = + state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()); + + select! { + state4 = xmr_lock_watcher => { + BobState::XmrLocked(state4?) + }, + _ = cancel_timelock_expires => { + let state4 = state.state4(); + BobState::CancelTimelockExpired(state4) + } + } + } else { + let state4 = state.state4(); + 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, + rng, + swap_id, + config, + ) + .await + } BobState::XmrLocked(state) => { let state = if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? diff --git a/swap/tests/happy_path_restart_bob_after_lock_proof_received.rs b/swap/tests/happy_path_restart_bob_after_lock_proof_received.rs new file mode 100644 index 00000000..6422ccd9 --- /dev/null +++ b/swap/tests/happy_path_restart_bob_after_lock_proof_received.rs @@ -0,0 +1,34 @@ +use swap::protocol::{ + alice, bob, + bob::{swap::is_lock_proof_received, BobState}, +}; + +pub mod testutils; + +#[tokio::test] +async fn given_bob_restarts_after_lock_proof_received_resume_swap() { + testutils::setup_test(|mut ctx| async move { + let alice_swap = ctx.new_swap_as_alice().await; + let bob_swap = ctx.new_swap_as_bob().await; + + let alice_handle = alice::run(alice_swap); + let alice_swap_handle = tokio::spawn(alice_handle); + + let bob_state = bob::run_until(bob_swap, is_lock_proof_received) + .await + .unwrap(); + + assert!(matches!(bob_state, BobState::XmrLockProofReceived {..})); + + let bob_swap = ctx.recover_bob_from_db().await; + assert!(matches!(bob_swap.state, BobState::XmrLockProofReceived {..})); + + let bob_state = bob::run(bob_swap).await.unwrap(); + + ctx.assert_bob_redeemed(bob_state).await; + + let alice_state = alice_swap_handle.await.unwrap().unwrap(); + ctx.assert_alice_redeemed(alice_state).await; + }) + .await; +}