mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-06 13:34:38 -04:00
Add alice punish test
Use reusable test init functions for happy path test Extract tracing setup to reusable function Move test initialization to seperate functions Increase stack size in CI Fix monero max finality time Force Bob swarm polling to send message 2 Run Bob state to xmr_locked in punish test to force the sending of message2. Previously Bob state was run until btc_locked. Although this was the right thing to do, message2 was not being sent as the swarm was not polled in btc_locked. Alice punish test passes. Add info logging to executor
This commit is contained in:
parent
5fef68322a
commit
c91e9652aa
8 changed files with 731 additions and 540 deletions
|
@ -5,7 +5,6 @@ use crate::{
|
|||
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use conquer_once::Lazy;
|
||||
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
||||
use futures::{
|
||||
future::{select, Either},
|
||||
|
@ -29,13 +28,6 @@ use xmr_btc::{
|
|||
monero::Transfer,
|
||||
};
|
||||
|
||||
// The maximum we assume we need to wait from the moment the monero transaction
|
||||
// is mined to the moment it reaches finality. We set 15 confirmations for now
|
||||
// (based on Kraken). 1.5 multiplier in case the blockchain is slower than
|
||||
// usually. Average of 2 minutes block time
|
||||
static MONERO_MAX_FINALITY_TIME: Lazy<Duration> =
|
||||
Lazy::new(|| Duration::from_secs_f64(15f64 * 1.5 * 2f64 * 60f64));
|
||||
|
||||
pub async fn negotiate(
|
||||
amounts: SwapAmounts,
|
||||
a: bitcoin::SecretKey,
|
||||
|
@ -180,8 +172,11 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn wait_for_bitcoin_encrypted_signature(swarm: &mut Swarm) -> Result<EncryptedSignature> {
|
||||
let event = timeout(*MONERO_MAX_FINALITY_TIME, swarm.next())
|
||||
pub async fn wait_for_bitcoin_encrypted_signature(
|
||||
swarm: &mut Swarm,
|
||||
timeout_duration: Duration,
|
||||
) -> Result<EncryptedSignature> {
|
||||
let event = timeout(timeout_duration, swarm.next())
|
||||
.await
|
||||
.context("Failed to receive Bitcoin encrypted signature from Bob")?;
|
||||
|
||||
|
|
|
@ -24,7 +24,8 @@ use futures::{
|
|||
};
|
||||
use libp2p::request_response::ResponseChannel;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, sync::Arc};
|
||||
use tracing::info;
|
||||
use xmr_btc::{
|
||||
alice::State3,
|
||||
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
|
||||
|
@ -86,328 +87,377 @@ pub enum AliceState {
|
|||
SafelyAborted,
|
||||
}
|
||||
|
||||
pub async fn swap<R>(
|
||||
impl fmt::Display for AliceState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AliceState::Started { .. } => write!(f, "started"),
|
||||
AliceState::Negotiated { .. } => write!(f, "negotiated"),
|
||||
AliceState::BtcLocked { .. } => write!(f, "btc_locked"),
|
||||
AliceState::XmrLocked { .. } => write!(f, "xmr_locked"),
|
||||
AliceState::EncSignLearned { .. } => write!(f, "encsig_sent"),
|
||||
AliceState::BtcRedeemed => write!(f, "btc_redeemed"),
|
||||
AliceState::BtcCancelled { .. } => write!(f, "btc_cancelled"),
|
||||
AliceState::BtcRefunded { .. } => write!(f, "btc_refunded"),
|
||||
AliceState::Punished => write!(f, "punished"),
|
||||
AliceState::SafelyAborted => write!(f, "safely_aborted"),
|
||||
AliceState::BtcPunishable { .. } => write!(f, "btc_punishable"),
|
||||
AliceState::XmrRefunded => write!(f, "xmr_refunded"),
|
||||
AliceState::WaitingToCancel { .. } => write!(f, "waiting_to_cancel"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn swap(
|
||||
state: AliceState,
|
||||
swarm: Swarm,
|
||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||
monero_wallet: Arc<crate::monero::Wallet>,
|
||||
) -> Result<AliceState>
|
||||
where
|
||||
R: RngCore + CryptoRng + Send,
|
||||
{
|
||||
run_until(state, is_complete, swarm, bitcoin_wallet, monero_wallet).await
|
||||
config: Config,
|
||||
) -> Result<(AliceState, Swarm)> {
|
||||
run_until(
|
||||
state,
|
||||
is_complete,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// TODO: use macro or generics
|
||||
pub fn is_complete(state: &AliceState) -> bool {
|
||||
matches!(state, AliceState::XmrRefunded| AliceState::BtcRedeemed | AliceState::Punished | AliceState::SafelyAborted)
|
||||
matches!(
|
||||
state,
|
||||
AliceState::XmrRefunded
|
||||
| AliceState::BtcRedeemed
|
||||
| AliceState::Punished
|
||||
| AliceState::SafelyAborted
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_xmr_locked(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::XmrLocked{..}
|
||||
)
|
||||
}
|
||||
|
||||
// State machine driver for swap execution
|
||||
#[async_recursion]
|
||||
pub async fn run_until(
|
||||
state: AliceState,
|
||||
is_state: fn(&AliceState) -> bool,
|
||||
is_target_state: fn(&AliceState) -> bool,
|
||||
mut swarm: Swarm,
|
||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||
monero_wallet: Arc<crate::monero::Wallet>,
|
||||
config: Config,
|
||||
) -> Result<AliceState> {
|
||||
if is_state(&state) {
|
||||
Ok(state)
|
||||
) -> Result<(AliceState, Swarm)> {
|
||||
info!("{}", state);
|
||||
if is_target_state(&state) {
|
||||
Ok((state, swarm))
|
||||
} else {
|
||||
match state {
|
||||
AliceState::Started {
|
||||
amounts,
|
||||
a,
|
||||
s_a,
|
||||
v_a,
|
||||
} => {
|
||||
let (channel, state3) = negotiate(
|
||||
AliceState::Started {
|
||||
amounts,
|
||||
a,
|
||||
s_a,
|
||||
v_a,
|
||||
&mut swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::Negotiated {
|
||||
channel,
|
||||
} => {
|
||||
let (channel, state3) = negotiate(
|
||||
amounts,
|
||||
state3,
|
||||
},
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::Negotiated {
|
||||
state3,
|
||||
channel,
|
||||
amounts,
|
||||
} => {
|
||||
let _ = wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
|
||||
a,
|
||||
s_a,
|
||||
v_a,
|
||||
&mut swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::BtcLocked {
|
||||
channel,
|
||||
amounts,
|
||||
state3,
|
||||
},
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::BtcLocked {
|
||||
channel,
|
||||
amounts,
|
||||
state3,
|
||||
} => {
|
||||
lock_xmr(
|
||||
run_until(
|
||||
AliceState::Negotiated {
|
||||
channel,
|
||||
amounts,
|
||||
state3,
|
||||
},
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::Negotiated {
|
||||
state3,
|
||||
channel,
|
||||
amounts,
|
||||
state3.clone(),
|
||||
&mut swarm,
|
||||
monero_wallet.clone(),
|
||||
)
|
||||
.await?;
|
||||
} => {
|
||||
let _ =
|
||||
wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::XmrLocked { state3 },
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::XmrLocked { state3 } => {
|
||||
// Our Monero is locked, we need to go through the cancellation process if this
|
||||
// step fails
|
||||
match wait_for_bitcoin_encrypted_signature(&mut swarm).await {
|
||||
Ok(encrypted_signature) => {
|
||||
run_until(
|
||||
AliceState::EncSignLearned {
|
||||
state3,
|
||||
encrypted_signature,
|
||||
},
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(_) => {
|
||||
run_until(
|
||||
AliceState::WaitingToCancel { state3 },
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
run_until(
|
||||
AliceState::BtcLocked {
|
||||
channel,
|
||||
amounts,
|
||||
state3,
|
||||
},
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::BtcLocked {
|
||||
channel,
|
||||
amounts,
|
||||
state3,
|
||||
} => {
|
||||
lock_xmr(
|
||||
channel,
|
||||
amounts,
|
||||
state3.clone(),
|
||||
&mut swarm,
|
||||
monero_wallet.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::XmrLocked { state3 },
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::XmrLocked { state3 } => {
|
||||
// Our Monero is locked, we need to go through the cancellation process if this
|
||||
// step fails
|
||||
match wait_for_bitcoin_encrypted_signature(
|
||||
&mut swarm,
|
||||
config.monero_max_finality_time,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(encrypted_signature) => {
|
||||
run_until(
|
||||
AliceState::EncSignLearned {
|
||||
state3,
|
||||
encrypted_signature,
|
||||
},
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(_) => {
|
||||
run_until(
|
||||
AliceState::WaitingToCancel { state3 },
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AliceState::EncSignLearned {
|
||||
state3,
|
||||
encrypted_signature,
|
||||
} => {
|
||||
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
|
||||
AliceState::EncSignLearned {
|
||||
state3,
|
||||
encrypted_signature,
|
||||
&state3.tx_lock,
|
||||
state3.a.clone(),
|
||||
state3.s_a,
|
||||
state3.B,
|
||||
&state3.redeem_address,
|
||||
) {
|
||||
Ok(tx) => tx,
|
||||
Err(_) => {
|
||||
return run_until(
|
||||
AliceState::WaitingToCancel { state3 },
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
} => {
|
||||
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
|
||||
encrypted_signature,
|
||||
&state3.tx_lock,
|
||||
state3.a.clone(),
|
||||
state3.s_a,
|
||||
state3.B,
|
||||
&state3.redeem_address,
|
||||
) {
|
||||
Ok(tx) => tx,
|
||||
Err(_) => {
|
||||
return run_until(
|
||||
AliceState::WaitingToCancel { state3 },
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(Franck): Error handling is delicate here.
|
||||
// If Bob sees this transaction he can redeem Monero
|
||||
// e.g. If the Bitcoin node is down then the user needs to take action.
|
||||
publish_bitcoin_redeem_transaction(
|
||||
signed_tx_redeem,
|
||||
bitcoin_wallet.clone(),
|
||||
config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::BtcRedeemed,
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::WaitingToCancel { state3 } => {
|
||||
let tx_cancel = publish_cancel_transaction(
|
||||
state3.tx_lock.clone(),
|
||||
state3.a.clone(),
|
||||
state3.B,
|
||||
state3.refund_timelock,
|
||||
state3.tx_cancel_sig_bob.clone(),
|
||||
bitcoin_wallet.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::BtcCancelled { state3, tx_cancel },
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::BtcCancelled { state3, tx_cancel } => {
|
||||
let tx_cancel_height = bitcoin_wallet
|
||||
.transaction_block_height(tx_cancel.txid())
|
||||
.await;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(Franck): Error handling is delicate here.
|
||||
// If Bob sees this transaction he can redeem Monero
|
||||
// e.g. If the Bitcoin node is down then the user needs to take action.
|
||||
publish_bitcoin_redeem_transaction(signed_tx_redeem, bitcoin_wallet.clone(), config)
|
||||
let (tx_refund, published_refund_tx) = wait_for_bitcoin_refund(
|
||||
&tx_cancel,
|
||||
tx_cancel_height,
|
||||
state3.punish_timelock,
|
||||
&state3.refund_address,
|
||||
bitcoin_wallet.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::BtcRedeemed,
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::WaitingToCancel { state3 } => {
|
||||
let tx_cancel = publish_cancel_transaction(
|
||||
state3.tx_lock.clone(),
|
||||
state3.a.clone(),
|
||||
state3.B,
|
||||
state3.refund_timelock,
|
||||
state3.tx_cancel_sig_bob.clone(),
|
||||
bitcoin_wallet.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
run_until(
|
||||
AliceState::BtcCancelled { state3, tx_cancel },
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
AliceState::BtcCancelled { state3, tx_cancel } => {
|
||||
let tx_cancel_height = bitcoin_wallet
|
||||
.transaction_block_height(tx_cancel.txid())
|
||||
.await;
|
||||
|
||||
let (tx_refund, published_refund_tx) = wait_for_bitcoin_refund(
|
||||
&tx_cancel,
|
||||
tx_cancel_height,
|
||||
state3.punish_timelock,
|
||||
&state3.refund_address,
|
||||
bitcoin_wallet.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// TODO(Franck): Review error handling
|
||||
match published_refund_tx {
|
||||
None => {
|
||||
run_until(
|
||||
AliceState::BtcPunishable { tx_refund, state3 },
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(published_refund_tx) => {
|
||||
run_until(
|
||||
AliceState::BtcRefunded {
|
||||
tx_refund,
|
||||
published_refund_tx,
|
||||
state3,
|
||||
},
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
// TODO(Franck): Review error handling
|
||||
match published_refund_tx {
|
||||
None => {
|
||||
run_until(
|
||||
AliceState::BtcPunishable { tx_refund, state3 },
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(published_refund_tx) => {
|
||||
run_until(
|
||||
AliceState::BtcRefunded {
|
||||
tx_refund,
|
||||
published_refund_tx,
|
||||
state3,
|
||||
},
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AliceState::BtcRefunded {
|
||||
tx_refund,
|
||||
published_refund_tx,
|
||||
state3,
|
||||
} => {
|
||||
let spend_key = extract_monero_private_key(
|
||||
published_refund_tx,
|
||||
AliceState::BtcRefunded {
|
||||
tx_refund,
|
||||
state3.s_a,
|
||||
state3.a.clone(),
|
||||
state3.S_b_bitcoin,
|
||||
)?;
|
||||
let view_key = state3.v;
|
||||
published_refund_tx,
|
||||
state3,
|
||||
} => {
|
||||
let spend_key = extract_monero_private_key(
|
||||
published_refund_tx,
|
||||
tx_refund,
|
||||
state3.s_a,
|
||||
state3.a.clone(),
|
||||
state3.S_b_bitcoin,
|
||||
)?;
|
||||
let view_key = state3.v;
|
||||
|
||||
monero_wallet
|
||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
||||
.await?;
|
||||
monero_wallet
|
||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
||||
.await?;
|
||||
|
||||
Ok(AliceState::XmrRefunded)
|
||||
}
|
||||
AliceState::BtcPunishable { tx_refund, state3 } => {
|
||||
let signed_tx_punish = build_bitcoin_punish_transaction(
|
||||
&state3.tx_lock,
|
||||
state3.refund_timelock,
|
||||
&state3.punish_address,
|
||||
state3.punish_timelock,
|
||||
state3.tx_punish_sig_bob.clone(),
|
||||
state3.a.clone(),
|
||||
state3.B,
|
||||
)?;
|
||||
Ok((AliceState::XmrRefunded, swarm))
|
||||
}
|
||||
AliceState::BtcPunishable { tx_refund, state3 } => {
|
||||
let signed_tx_punish = build_bitcoin_punish_transaction(
|
||||
&state3.tx_lock,
|
||||
state3.refund_timelock,
|
||||
&state3.punish_address,
|
||||
state3.punish_timelock,
|
||||
state3.tx_punish_sig_bob.clone(),
|
||||
state3.a.clone(),
|
||||
state3.B,
|
||||
)?;
|
||||
|
||||
let punish_tx_finalised = publish_bitcoin_punish_transaction(
|
||||
signed_tx_punish,
|
||||
bitcoin_wallet.clone(),
|
||||
config,
|
||||
);
|
||||
let punish_tx_finalised = publish_bitcoin_punish_transaction(
|
||||
signed_tx_punish,
|
||||
bitcoin_wallet.clone(),
|
||||
config,
|
||||
);
|
||||
|
||||
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
||||
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());
|
||||
|
||||
pin_mut!(punish_tx_finalised);
|
||||
pin_mut!(refund_tx_seen);
|
||||
pin_mut!(punish_tx_finalised);
|
||||
pin_mut!(refund_tx_seen);
|
||||
|
||||
match select(punish_tx_finalised, refund_tx_seen).await {
|
||||
Either::Left(_) => {
|
||||
run_until(
|
||||
AliceState::Punished,
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Either::Right((published_refund_tx, _)) => {
|
||||
run_until(
|
||||
AliceState::BtcRefunded {
|
||||
tx_refund,
|
||||
published_refund_tx,
|
||||
state3,
|
||||
},
|
||||
is_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
match select(punish_tx_finalised, refund_tx_seen).await {
|
||||
Either::Left(_) => {
|
||||
run_until(
|
||||
AliceState::Punished,
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Either::Right((published_refund_tx, _)) => {
|
||||
run_until(
|
||||
AliceState::BtcRefunded {
|
||||
tx_refund,
|
||||
published_refund_tx,
|
||||
state3,
|
||||
},
|
||||
is_target_state,
|
||||
swarm,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet,
|
||||
config,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
AliceState::XmrRefunded => Ok((AliceState::XmrRefunded, swarm)),
|
||||
AliceState::BtcRedeemed => Ok((AliceState::BtcRedeemed, swarm)),
|
||||
AliceState::Punished => Ok((AliceState::Punished, swarm)),
|
||||
AliceState::SafelyAborted => Ok((AliceState::SafelyAborted, swarm)),
|
||||
}
|
||||
AliceState::XmrRefunded => Ok(AliceState::XmrRefunded),
|
||||
AliceState::BtcRedeemed => Ok(AliceState::BtcRedeemed),
|
||||
AliceState::Punished => Ok(AliceState::Punished),
|
||||
AliceState::SafelyAborted => Ok(AliceState::SafelyAborted),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ use anyhow::Result;
|
|||
use async_recursion::async_recursion;
|
||||
use libp2p::{core::Multiaddr, PeerId};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
use std::{fmt, sync::Arc};
|
||||
use tracing::{debug, info};
|
||||
use uuid::Uuid;
|
||||
use xmr_btc::bob::{self};
|
||||
|
||||
|
@ -33,6 +33,24 @@ pub enum BobState {
|
|||
SafelyAborted,
|
||||
}
|
||||
|
||||
impl fmt::Display for BobState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BobState::Started { .. } => write!(f, "started"),
|
||||
BobState::Negotiated(..) => write!(f, "negotiated"),
|
||||
BobState::BtcLocked(..) => write!(f, "btc_locked"),
|
||||
BobState::XmrLocked(..) => write!(f, "xmr_locked"),
|
||||
BobState::EncSigSent(..) => write!(f, "encsig_sent"),
|
||||
BobState::BtcRedeemed(_) => write!(f, "btc_redeemed"),
|
||||
BobState::Cancelled(_) => write!(f, "cancelled"),
|
||||
BobState::BtcRefunded => write!(f, "btc_refunded"),
|
||||
BobState::XmrRedeemed => write!(f, "xmr_redeemed"),
|
||||
BobState::Punished => write!(f, "punished"),
|
||||
BobState::SafelyAborted => write!(f, "safely_aborted"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn swap<R>(
|
||||
state: BobState,
|
||||
swarm: Swarm,
|
||||
|
@ -42,22 +60,47 @@ pub async fn swap<R>(
|
|||
rng: R,
|
||||
swap_id: Uuid,
|
||||
) -> Result<BobState>
|
||||
where
|
||||
R: RngCore + CryptoRng + Send,
|
||||
where
|
||||
R: RngCore + CryptoRng + Send,
|
||||
{
|
||||
run_until(state, is_complete, swarm, db, bitcoin_wallet, monero_wallet, rng, swap_id).await
|
||||
run_until(
|
||||
state,
|
||||
is_complete,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// TODO: use macro or generics
|
||||
pub fn is_complete(state: &BobState) -> bool {
|
||||
matches!(state, BobState::BtcRefunded| BobState::XmrRedeemed | BobState::Punished | BobState::SafelyAborted)
|
||||
matches!(
|
||||
state,
|
||||
BobState::BtcRefunded
|
||||
| BobState::XmrRedeemed
|
||||
| BobState::Punished
|
||||
| BobState::SafelyAborted
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_btc_locked(state: &BobState) -> bool {
|
||||
matches!(state, BobState::BtcLocked(..))
|
||||
}
|
||||
|
||||
pub fn is_xmr_locked(state: &BobState) -> bool {
|
||||
matches!(state, BobState::XmrLocked(..))
|
||||
}
|
||||
|
||||
// State machine driver for swap execution
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[async_recursion]
|
||||
pub async fn run_until<R>(
|
||||
state: BobState,
|
||||
is_state: fn(&BobState) -> bool,
|
||||
is_target_state: fn(&BobState) -> bool,
|
||||
mut swarm: Swarm,
|
||||
db: Database,
|
||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||
|
@ -68,7 +111,8 @@ pub async fn run_until<R>(
|
|||
where
|
||||
R: RngCore + CryptoRng + Send,
|
||||
{
|
||||
if is_state(&state) {
|
||||
info!("{}", state);
|
||||
if is_target_state(&state) {
|
||||
Ok(state)
|
||||
} else {
|
||||
match state {
|
||||
|
@ -86,10 +130,10 @@ where
|
|||
&mut rng,
|
||||
bitcoin_wallet.clone(),
|
||||
)
|
||||
.await?;
|
||||
.await?;
|
||||
run_until(
|
||||
BobState::Negotiated(state2, peer_id),
|
||||
is_state,
|
||||
is_target_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
|
@ -97,7 +141,7 @@ where
|
|||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
.await
|
||||
}
|
||||
BobState::Negotiated(state2, alice_peer_id) => {
|
||||
// Alice and Bob have exchanged info
|
||||
|
@ -105,7 +149,7 @@ where
|
|||
// db.insert_latest_state(state);
|
||||
run_until(
|
||||
BobState::BtcLocked(state3, alice_peer_id),
|
||||
is_state,
|
||||
is_target_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
|
@ -113,7 +157,7 @@ where
|
|||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
.await
|
||||
}
|
||||
// Bob has locked Btc
|
||||
// Watch for Alice to Lock Xmr or for t1 to elapse
|
||||
|
@ -129,7 +173,7 @@ where
|
|||
};
|
||||
run_until(
|
||||
BobState::XmrLocked(state4, alice_peer_id),
|
||||
is_state,
|
||||
is_target_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
|
@ -137,7 +181,7 @@ where
|
|||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
.await
|
||||
}
|
||||
BobState::XmrLocked(state, alice_peer_id) => {
|
||||
// Alice has locked Xmr
|
||||
|
@ -162,7 +206,7 @@ where
|
|||
|
||||
run_until(
|
||||
BobState::EncSigSent(state, alice_peer_id),
|
||||
is_state,
|
||||
is_target_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
|
@ -170,7 +214,7 @@ where
|
|||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
.await
|
||||
}
|
||||
BobState::EncSigSent(state, ..) => {
|
||||
// Watch for redeem
|
||||
|
@ -178,47 +222,47 @@ where
|
|||
let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref());
|
||||
|
||||
tokio::select! {
|
||||
val = redeem_watcher => {
|
||||
run_until(
|
||||
BobState::BtcRedeemed(val?),
|
||||
is_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ = t1_timeout => {
|
||||
// Check whether TxCancel has been published.
|
||||
// We should not fail if the transaction is already on the blockchain
|
||||
if state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await.is_err() {
|
||||
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
|
||||
val = redeem_watcher => {
|
||||
run_until(
|
||||
BobState::BtcRedeemed(val?),
|
||||
is_target_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ = t1_timeout => {
|
||||
// Check whether TxCancel has been published.
|
||||
// We should not fail if the transaction is already on the blockchain
|
||||
if state.check_for_tx_cancel(bitcoin_wallet.as_ref()).await.is_err() {
|
||||
state.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
|
||||
}
|
||||
|
||||
run_until(
|
||||
BobState::Cancelled(state),
|
||||
is_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
swap_id
|
||||
)
|
||||
.await
|
||||
run_until(
|
||||
BobState::Cancelled(state),
|
||||
is_target_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
rng,
|
||||
swap_id
|
||||
)
|
||||
.await
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BobState::BtcRedeemed(state) => {
|
||||
// Bob redeems XMR using revealed s_a
|
||||
state.claim_xmr(monero_wallet.as_ref()).await?;
|
||||
run_until(
|
||||
BobState::XmrRedeemed,
|
||||
is_state,
|
||||
is_target_state,
|
||||
swarm,
|
||||
db,
|
||||
bitcoin_wallet,
|
||||
|
@ -226,21 +270,40 @@ where
|
|||
rng,
|
||||
swap_id,
|
||||
)
|
||||
.await
|
||||
.await
|
||||
}
|
||||
BobState::Cancelled(_state) => {
|
||||
// Bob has cancelled the swap
|
||||
// If <t2 Bob refunds
|
||||
// if unimplemented!("<t2") {
|
||||
// // Submit TxRefund
|
||||
// abort(BobState::BtcRefunded, io).await
|
||||
// } else {
|
||||
// // Bob failed to refund in time and has been punished
|
||||
// abort(BobState::Punished, io).await
|
||||
// }
|
||||
Ok(BobState::BtcRefunded)
|
||||
}
|
||||
BobState::BtcRefunded => {
|
||||
info!("btc refunded");
|
||||
Ok(BobState::BtcRefunded)
|
||||
}
|
||||
BobState::Punished => {
|
||||
info!("punished");
|
||||
Ok(BobState::Punished)
|
||||
}
|
||||
BobState::SafelyAborted => {
|
||||
info!("safely aborted");
|
||||
Ok(BobState::SafelyAborted)
|
||||
}
|
||||
BobState::XmrRedeemed => {
|
||||
info!("xmr redeemed");
|
||||
Ok(BobState::XmrRedeemed)
|
||||
}
|
||||
BobState::Cancelled(_state) => Ok(BobState::BtcRefunded),
|
||||
BobState::BtcRefunded => Ok(BobState::BtcRefunded),
|
||||
BobState::Punished => Ok(BobState::Punished),
|
||||
BobState::SafelyAborted => Ok(BobState::SafelyAborted),
|
||||
BobState::XmrRedeemed => Ok(BobState::XmrRedeemed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// // State machine driver for recovery execution
|
||||
// #[async_recursion]
|
||||
// pub async fn abort(state: BobState, io: Io) -> Result<BobState> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue