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:
bors[bot] 2021-01-21 12:48:41 +00:00 committed by GitHub
commit e2170e60a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 182 additions and 66 deletions

View File

@ -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,
}

View File

@ -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"),

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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,
)

View File

@ -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?

View File

@ -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 {..}));

View File

@ -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 {..}));

View File

@ -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;
}

View File

@ -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() {

View File

@ -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.

View File

@ -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

View File

@ -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 {..}));

View File

@ -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(..))
}
}