mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-02-17 21:24:11 -05:00
Merge #155
155: Bob saves lock proof after received so he can resume swap r=da-kami a=da-kami Introducing a new state was too much so I added the additional required information to the enum state rather than adding another struct. As discussed earlier we should cleanup the state machine (enum states vs data structs) at some point :) Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
commit
e2170e60a7
@ -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,
|
||||
}
|
||||
|
@ -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<BobState> 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<Bob> 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"),
|
||||
|
@ -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<TxHash> for String {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -49,20 +49,6 @@ pub fn is_complete(state: &AliceState) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_xmr_locked(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::XmrLocked{..}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::EncSigLearned{..}
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn run(swap: alice::Swap) -> Result<AliceState> {
|
||||
run_until(swap, is_complete).await
|
||||
}
|
||||
|
@ -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<W>(
|
||||
self,
|
||||
xmr_wallet: &W,
|
||||
msg: alice::Message2,
|
||||
transfer_proof: TransferProof,
|
||||
monero_wallet_restore_blockheight: u32,
|
||||
) -> Result<State4>
|
||||
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,
|
||||
)
|
||||
|
@ -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 {
|
||||
@ -24,18 +24,6 @@ pub fn is_complete(state: &BobState) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_btc_locked(state: &BobState) -> bool {
|
||||
matches!(state, BobState::BtcLocked(..))
|
||||
}
|
||||
|
||||
pub fn is_xmr_locked(state: &BobState) -> bool {
|
||||
matches!(state, BobState::XmrLocked(..))
|
||||
}
|
||||
|
||||
pub fn is_encsig_sent(state: &BobState) -> bool {
|
||||
matches!(state, BobState::EncSigSent(..))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run(swap: bob::Swap) -> Result<BobState> {
|
||||
run_until(swap, is_complete).await
|
||||
@ -155,23 +143,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 +174,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?
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod testutils;
|
||||
|
||||
use swap::protocol::{alice, alice::AliceState, bob};
|
||||
use testutils::alice_run_until::is_encsig_learned;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
|
||||
@ -11,7 +12,7 @@ async fn given_alice_restarts_after_encsig_is_learned_resume_swap() {
|
||||
let bob = bob::run(bob_swap);
|
||||
let bob_handle = tokio::spawn(bob);
|
||||
|
||||
let alice_state = alice::run_until(alice_swap, alice::swap::is_encsig_learned)
|
||||
let alice_state = alice::run_until(alice_swap, is_encsig_learned)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(alice_state, AliceState::EncSigLearned {..}));
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod testutils;
|
||||
|
||||
use swap::protocol::{alice, bob, bob::BobState};
|
||||
use testutils::bob_run_until::is_encsig_sent;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_restarts_after_encsig_is_sent_resume_swap() {
|
||||
@ -11,9 +12,7 @@ async fn given_bob_restarts_after_encsig_is_sent_resume_swap() {
|
||||
let alice = alice::run(alice_swap);
|
||||
let alice_handle = tokio::spawn(alice);
|
||||
|
||||
let bob_state = bob::run_until(bob_swap, bob::swap::is_encsig_sent)
|
||||
.await
|
||||
.unwrap();
|
||||
let bob_state = bob::run_until(bob_swap, is_encsig_sent).await.unwrap();
|
||||
|
||||
assert!(matches!(bob_state, BobState::EncSigSent {..}));
|
||||
|
||||
|
@ -0,0 +1,32 @@
|
||||
pub mod testutils;
|
||||
|
||||
use swap::protocol::{alice, bob, bob::BobState};
|
||||
use testutils::bob_run_until::is_lock_proof_received;
|
||||
|
||||
#[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;
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
pub mod testutils;
|
||||
|
||||
use swap::protocol::{
|
||||
alice, bob,
|
||||
bob::{swap::is_xmr_locked, BobState},
|
||||
};
|
||||
use swap::protocol::{alice, bob, bob::BobState};
|
||||
use testutils::bob_run_until::is_xmr_locked;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_restarts_after_xmr_is_locked_resume_swap() {
|
||||
|
@ -1,9 +1,7 @@
|
||||
pub mod testutils;
|
||||
|
||||
use swap::protocol::{
|
||||
alice, bob,
|
||||
bob::{swap::is_btc_locked, BobState},
|
||||
};
|
||||
use swap::protocol::{alice, bob, bob::BobState};
|
||||
use testutils::bob_run_until::is_btc_locked;
|
||||
|
||||
/// 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.
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod testutils;
|
||||
|
||||
use swap::protocol::{alice, alice::AliceState, bob};
|
||||
use testutils::alice_run_until::is_xmr_locked;
|
||||
|
||||
/// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice
|
||||
/// then also refunds.
|
||||
@ -13,9 +14,7 @@ async fn given_alice_restarts_after_xmr_is_locked_refund_swap() {
|
||||
let bob = bob::run(bob_swap);
|
||||
let bob_handle = tokio::spawn(bob);
|
||||
|
||||
let alice_state = alice::run_until(alice_swap, alice::swap::is_xmr_locked)
|
||||
.await
|
||||
.unwrap();
|
||||
let alice_state = alice::run_until(alice_swap, is_xmr_locked).await.unwrap();
|
||||
assert!(matches!(alice_state, AliceState::XmrLocked {..}));
|
||||
|
||||
// Alice does not act, Bob refunds
|
||||
|
@ -1,7 +1,8 @@
|
||||
use swap::protocol::{alice, alice::AliceState, bob};
|
||||
|
||||
pub mod testutils;
|
||||
|
||||
use swap::protocol::{alice, alice::AliceState, bob};
|
||||
use testutils::alice_run_until::is_encsig_learned;
|
||||
|
||||
/// Bob locks btc and Alice locks xmr. Alice fails to act so Bob refunds. Alice
|
||||
/// is forced to refund even though she learned the secret and would be able to
|
||||
/// redeem had the timelock not expired.
|
||||
@ -14,7 +15,7 @@ async fn given_alice_restarts_after_enc_sig_learned_and_bob_already_cancelled_re
|
||||
let bob = bob::run(bob_swap);
|
||||
let bob_handle = tokio::spawn(bob);
|
||||
|
||||
let alice_state = alice::run_until(alice_swap, alice::swap::is_encsig_learned)
|
||||
let alice_state = alice::run_until(alice_swap, is_encsig_learned)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(alice_state, AliceState::EncSigLearned {..}));
|
||||
|
@ -460,3 +460,41 @@ fn init_tracing() -> DefaultGuard {
|
||||
))
|
||||
.set_default()
|
||||
}
|
||||
|
||||
pub mod alice_run_until {
|
||||
use swap::protocol::alice::AliceState;
|
||||
|
||||
pub fn is_xmr_locked(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::XmrLocked{..}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::EncSigLearned{..}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bob_run_until {
|
||||
use swap::protocol::bob::BobState;
|
||||
|
||||
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(..))
|
||||
}
|
||||
|
||||
pub fn is_encsig_sent(state: &BobState) -> bool {
|
||||
matches!(state, BobState::EncSigSent(..))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user