mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge pull request #54 from comit-network/bob-unhappy-paths
Punish Test
This commit is contained in:
commit
f88ed9183b
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -104,6 +104,7 @@ jobs:
|
|||||||
run: cargo test --workspace --all-features
|
run: cargo test --workspace --all-features
|
||||||
env:
|
env:
|
||||||
MONERO_ADDITIONAL_SLEEP_PERIOD: 60000
|
MONERO_ADDITIONAL_SLEEP_PERIOD: 60000
|
||||||
|
RUST_MIN_STACK: 10000000
|
||||||
|
|
||||||
- name: Build binary
|
- name: Build binary
|
||||||
run: |
|
run: |
|
||||||
|
@ -5,7 +5,6 @@ use crate::{
|
|||||||
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
SwapAmounts, PUNISH_TIMELOCK, REFUND_TIMELOCK,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use conquer_once::Lazy;
|
|
||||||
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
use ecdsa_fun::{adaptor::Adaptor, nonce::Deterministic};
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{select, Either},
|
future::{select, Either},
|
||||||
@ -29,13 +28,6 @@ use xmr_btc::{
|
|||||||
monero::Transfer,
|
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(
|
pub async fn negotiate(
|
||||||
amounts: SwapAmounts,
|
amounts: SwapAmounts,
|
||||||
a: bitcoin::SecretKey,
|
a: bitcoin::SecretKey,
|
||||||
@ -180,8 +172,11 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_bitcoin_encrypted_signature(swarm: &mut Swarm) -> Result<EncryptedSignature> {
|
pub async fn wait_for_bitcoin_encrypted_signature(
|
||||||
let event = timeout(*MONERO_MAX_FINALITY_TIME, swarm.next())
|
swarm: &mut Swarm,
|
||||||
|
timeout_duration: Duration,
|
||||||
|
) -> Result<EncryptedSignature> {
|
||||||
|
let event = timeout(timeout_duration, swarm.next())
|
||||||
.await
|
.await
|
||||||
.context("Failed to receive Bitcoin encrypted signature from Bob")?;
|
.context("Failed to receive Bitcoin encrypted signature from Bob")?;
|
||||||
|
|
||||||
|
@ -24,7 +24,8 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use libp2p::request_response::ResponseChannel;
|
use libp2p::request_response::ResponseChannel;
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use std::sync::Arc;
|
use std::{fmt, sync::Arc};
|
||||||
|
use tracing::info;
|
||||||
use xmr_btc::{
|
use xmr_btc::{
|
||||||
alice::State3,
|
alice::State3,
|
||||||
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
|
bitcoin::{TransactionBlockHeight, TxCancel, TxRefund, WatchForRawTransaction},
|
||||||
@ -86,293 +87,376 @@ pub enum AliceState {
|
|||||||
SafelyAborted,
|
SafelyAborted,
|
||||||
}
|
}
|
||||||
|
|
||||||
// State machine driver for swap execution
|
impl fmt::Display for AliceState {
|
||||||
#[async_recursion]
|
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(
|
pub async fn swap(
|
||||||
state: AliceState,
|
state: AliceState,
|
||||||
|
swarm: Swarm,
|
||||||
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
|
config: Config,
|
||||||
|
) -> Result<(AliceState, Swarm)> {
|
||||||
|
run_until(
|
||||||
|
state,
|
||||||
|
is_complete,
|
||||||
|
swarm,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_complete(state: &AliceState) -> bool {
|
||||||
|
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_target_state: fn(&AliceState) -> bool,
|
||||||
mut swarm: Swarm,
|
mut swarm: Swarm,
|
||||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
monero_wallet: Arc<crate::monero::Wallet>,
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Result<AliceState> {
|
) -> Result<(AliceState, Swarm)> {
|
||||||
match state {
|
info!("Current state:{}", state);
|
||||||
AliceState::Started {
|
if is_target_state(&state) {
|
||||||
amounts,
|
Ok((state, swarm))
|
||||||
a,
|
} else {
|
||||||
s_a,
|
match state {
|
||||||
v_a,
|
AliceState::Started {
|
||||||
} => {
|
|
||||||
let (channel, state3) = negotiate(
|
|
||||||
amounts,
|
amounts,
|
||||||
a,
|
a,
|
||||||
s_a,
|
s_a,
|
||||||
v_a,
|
v_a,
|
||||||
&mut swarm,
|
} => {
|
||||||
bitcoin_wallet.clone(),
|
let (channel, state3) = negotiate(
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
swap(
|
|
||||||
AliceState::Negotiated {
|
|
||||||
channel,
|
|
||||||
amounts,
|
amounts,
|
||||||
state3,
|
a,
|
||||||
},
|
s_a,
|
||||||
swarm,
|
v_a,
|
||||||
bitcoin_wallet,
|
&mut swarm,
|
||||||
monero_wallet,
|
bitcoin_wallet.clone(),
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
.await
|
|
||||||
}
|
|
||||||
AliceState::Negotiated {
|
|
||||||
state3,
|
|
||||||
channel,
|
|
||||||
amounts,
|
|
||||||
} => {
|
|
||||||
let _ = wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
swap(
|
run_until(
|
||||||
AliceState::BtcLocked {
|
AliceState::Negotiated {
|
||||||
channel,
|
channel,
|
||||||
amounts,
|
amounts,
|
||||||
state3,
|
state3,
|
||||||
},
|
},
|
||||||
swarm,
|
is_target_state,
|
||||||
bitcoin_wallet,
|
swarm,
|
||||||
monero_wallet,
|
bitcoin_wallet,
|
||||||
config,
|
monero_wallet,
|
||||||
)
|
config,
|
||||||
.await
|
)
|
||||||
}
|
.await
|
||||||
AliceState::BtcLocked {
|
}
|
||||||
channel,
|
AliceState::Negotiated {
|
||||||
amounts,
|
state3,
|
||||||
state3,
|
|
||||||
} => {
|
|
||||||
lock_xmr(
|
|
||||||
channel,
|
channel,
|
||||||
amounts,
|
amounts,
|
||||||
state3.clone(),
|
} => {
|
||||||
&mut swarm,
|
let _ =
|
||||||
monero_wallet.clone(),
|
wait_for_locked_bitcoin(state3.tx_lock.txid(), bitcoin_wallet.clone(), config)
|
||||||
)
|
.await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
swap(
|
run_until(
|
||||||
AliceState::XmrLocked { state3 },
|
AliceState::BtcLocked {
|
||||||
swarm,
|
channel,
|
||||||
bitcoin_wallet,
|
amounts,
|
||||||
monero_wallet,
|
state3,
|
||||||
config,
|
},
|
||||||
)
|
is_target_state,
|
||||||
.await
|
swarm,
|
||||||
}
|
bitcoin_wallet,
|
||||||
AliceState::XmrLocked { state3 } => {
|
monero_wallet,
|
||||||
// Our Monero is locked, we need to go through the cancellation process if this
|
config,
|
||||||
// step fails
|
)
|
||||||
match wait_for_bitcoin_encrypted_signature(&mut swarm).await {
|
.await
|
||||||
Ok(encrypted_signature) => {
|
}
|
||||||
swap(
|
AliceState::BtcLocked {
|
||||||
AliceState::EncSignLearned {
|
channel,
|
||||||
state3,
|
amounts,
|
||||||
encrypted_signature,
|
state3,
|
||||||
},
|
} => {
|
||||||
swarm,
|
lock_xmr(
|
||||||
bitcoin_wallet,
|
channel,
|
||||||
monero_wallet,
|
amounts,
|
||||||
config,
|
state3.clone(),
|
||||||
)
|
&mut swarm,
|
||||||
.await
|
monero_wallet.clone(),
|
||||||
}
|
)
|
||||||
Err(_) => {
|
.await?;
|
||||||
swap(
|
|
||||||
AliceState::WaitingToCancel { state3 },
|
run_until(
|
||||||
swarm,
|
AliceState::XmrLocked { state3 },
|
||||||
bitcoin_wallet,
|
is_target_state,
|
||||||
monero_wallet,
|
swarm,
|
||||||
config,
|
bitcoin_wallet,
|
||||||
)
|
monero_wallet,
|
||||||
.await
|
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 {
|
||||||
AliceState::EncSignLearned {
|
state3,
|
||||||
state3,
|
|
||||||
encrypted_signature,
|
|
||||||
} => {
|
|
||||||
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
|
|
||||||
encrypted_signature,
|
encrypted_signature,
|
||||||
&state3.tx_lock,
|
} => {
|
||||||
state3.a.clone(),
|
let signed_tx_redeem = match build_bitcoin_redeem_transaction(
|
||||||
state3.s_a,
|
encrypted_signature,
|
||||||
state3.B,
|
&state3.tx_lock,
|
||||||
&state3.redeem_address,
|
state3.a.clone(),
|
||||||
) {
|
state3.s_a,
|
||||||
Ok(tx) => tx,
|
state3.B,
|
||||||
Err(_) => {
|
&state3.redeem_address,
|
||||||
return swap(
|
) {
|
||||||
AliceState::WaitingToCancel { state3 },
|
Ok(tx) => tx,
|
||||||
swarm,
|
Err(_) => {
|
||||||
bitcoin_wallet,
|
return run_until(
|
||||||
monero_wallet,
|
AliceState::WaitingToCancel { state3 },
|
||||||
config,
|
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;
|
.await;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(Franck): Error handling is delicate here.
|
let (tx_refund, published_refund_tx) = wait_for_bitcoin_refund(
|
||||||
// If Bob sees this transaction he can redeem Monero
|
&tx_cancel,
|
||||||
// e.g. If the Bitcoin node is down then the user needs to take action.
|
tx_cancel_height,
|
||||||
publish_bitcoin_redeem_transaction(signed_tx_redeem, bitcoin_wallet.clone(), config)
|
state3.punish_timelock,
|
||||||
|
&state3.refund_address,
|
||||||
|
bitcoin_wallet.clone(),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
swap(
|
// TODO(Franck): Review error handling
|
||||||
AliceState::BtcRedeemed,
|
match published_refund_tx {
|
||||||
swarm,
|
None => {
|
||||||
bitcoin_wallet,
|
run_until(
|
||||||
monero_wallet,
|
AliceState::BtcPunishable { tx_refund, state3 },
|
||||||
config,
|
is_target_state,
|
||||||
)
|
swarm,
|
||||||
.await
|
bitcoin_wallet.clone(),
|
||||||
}
|
monero_wallet,
|
||||||
AliceState::WaitingToCancel { state3 } => {
|
config,
|
||||||
let tx_cancel = publish_cancel_transaction(
|
)
|
||||||
state3.tx_lock.clone(),
|
.await
|
||||||
state3.a.clone(),
|
}
|
||||||
state3.B,
|
Some(published_refund_tx) => {
|
||||||
state3.refund_timelock,
|
run_until(
|
||||||
state3.tx_cancel_sig_bob.clone(),
|
AliceState::BtcRefunded {
|
||||||
bitcoin_wallet.clone(),
|
tx_refund,
|
||||||
)
|
published_refund_tx,
|
||||||
.await?;
|
state3,
|
||||||
|
},
|
||||||
swap(
|
is_target_state,
|
||||||
AliceState::BtcCancelled { state3, tx_cancel },
|
swarm,
|
||||||
swarm,
|
bitcoin_wallet.clone(),
|
||||||
bitcoin_wallet,
|
monero_wallet,
|
||||||
monero_wallet,
|
config,
|
||||||
config,
|
)
|
||||||
)
|
.await
|
||||||
.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 => {
|
|
||||||
swap(
|
|
||||||
AliceState::BtcPunishable { tx_refund, state3 },
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
monero_wallet,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Some(published_refund_tx) => {
|
|
||||||
swap(
|
|
||||||
AliceState::BtcRefunded {
|
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
},
|
|
||||||
swarm,
|
|
||||||
bitcoin_wallet.clone(),
|
|
||||||
monero_wallet,
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
AliceState::BtcRefunded {
|
||||||
AliceState::BtcRefunded {
|
|
||||||
tx_refund,
|
|
||||||
published_refund_tx,
|
|
||||||
state3,
|
|
||||||
} => {
|
|
||||||
let spend_key = extract_monero_private_key(
|
|
||||||
published_refund_tx,
|
|
||||||
tx_refund,
|
tx_refund,
|
||||||
state3.s_a,
|
published_refund_tx,
|
||||||
state3.a.clone(),
|
state3,
|
||||||
state3.S_b_bitcoin,
|
} => {
|
||||||
)?;
|
let spend_key = extract_monero_private_key(
|
||||||
let view_key = state3.v;
|
published_refund_tx,
|
||||||
|
tx_refund,
|
||||||
|
state3.s_a,
|
||||||
|
state3.a.clone(),
|
||||||
|
state3.S_b_bitcoin,
|
||||||
|
)?;
|
||||||
|
let view_key = state3.v;
|
||||||
|
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
.create_and_load_wallet_for_output(spend_key, view_key)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(AliceState::XmrRefunded)
|
Ok((AliceState::XmrRefunded, swarm))
|
||||||
}
|
}
|
||||||
AliceState::BtcPunishable { tx_refund, state3 } => {
|
AliceState::BtcPunishable { tx_refund, state3 } => {
|
||||||
let signed_tx_punish = build_bitcoin_punish_transaction(
|
let signed_tx_punish = build_bitcoin_punish_transaction(
|
||||||
&state3.tx_lock,
|
&state3.tx_lock,
|
||||||
state3.refund_timelock,
|
state3.refund_timelock,
|
||||||
&state3.punish_address,
|
&state3.punish_address,
|
||||||
state3.punish_timelock,
|
state3.punish_timelock,
|
||||||
state3.tx_punish_sig_bob.clone(),
|
state3.tx_punish_sig_bob.clone(),
|
||||||
state3.a.clone(),
|
state3.a.clone(),
|
||||||
state3.B,
|
state3.B,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let punish_tx_finalised = publish_bitcoin_punish_transaction(
|
let punish_tx_finalised = publish_bitcoin_punish_transaction(
|
||||||
signed_tx_punish,
|
signed_tx_punish,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
config,
|
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!(punish_tx_finalised);
|
||||||
pin_mut!(refund_tx_seen);
|
pin_mut!(refund_tx_seen);
|
||||||
|
|
||||||
match select(punish_tx_finalised, refund_tx_seen).await {
|
match select(punish_tx_finalised, refund_tx_seen).await {
|
||||||
Either::Left(_) => {
|
Either::Left(_) => {
|
||||||
swap(
|
run_until(
|
||||||
AliceState::Punished,
|
AliceState::Punished,
|
||||||
swarm,
|
is_target_state,
|
||||||
bitcoin_wallet.clone(),
|
swarm,
|
||||||
monero_wallet,
|
bitcoin_wallet.clone(),
|
||||||
config,
|
monero_wallet,
|
||||||
)
|
config,
|
||||||
.await
|
)
|
||||||
}
|
.await
|
||||||
Either::Right((published_refund_tx, _)) => {
|
}
|
||||||
swap(
|
Either::Right((published_refund_tx, _)) => {
|
||||||
AliceState::BtcRefunded {
|
run_until(
|
||||||
tx_refund,
|
AliceState::BtcRefunded {
|
||||||
published_refund_tx,
|
tx_refund,
|
||||||
state3,
|
published_refund_tx,
|
||||||
},
|
state3,
|
||||||
swarm,
|
},
|
||||||
bitcoin_wallet.clone(),
|
is_target_state,
|
||||||
monero_wallet,
|
swarm,
|
||||||
config,
|
bitcoin_wallet.clone(),
|
||||||
)
|
monero_wallet,
|
||||||
.await
|
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 async_recursion::async_recursion;
|
||||||
use libp2p::{core::Multiaddr, PeerId};
|
use libp2p::{core::Multiaddr, PeerId};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use std::sync::Arc;
|
use std::{fmt, sync::Arc};
|
||||||
use tracing::debug;
|
use tracing::{debug, info};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use xmr_btc::bob::{self};
|
use xmr_btc::bob::{self};
|
||||||
|
|
||||||
@ -33,10 +33,73 @@ pub enum BobState {
|
|||||||
SafelyAborted,
|
SafelyAborted,
|
||||||
}
|
}
|
||||||
|
|
||||||
// State machine driver for swap execution
|
impl fmt::Display for BobState {
|
||||||
#[async_recursion]
|
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>(
|
pub async fn swap<R>(
|
||||||
state: BobState,
|
state: BobState,
|
||||||
|
swarm: Swarm,
|
||||||
|
db: Database,
|
||||||
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
|
monero_wallet: Arc<crate::monero::Wallet>,
|
||||||
|
rng: R,
|
||||||
|
swap_id: Uuid,
|
||||||
|
) -> Result<BobState>
|
||||||
|
where
|
||||||
|
R: RngCore + CryptoRng + Send,
|
||||||
|
{
|
||||||
|
run_until(
|
||||||
|
state,
|
||||||
|
is_complete,
|
||||||
|
swarm,
|
||||||
|
db,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
rng,
|
||||||
|
swap_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_complete(state: &BobState) -> bool {
|
||||||
|
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_target_state: fn(&BobState) -> bool,
|
||||||
mut swarm: Swarm,
|
mut swarm: Swarm,
|
||||||
db: Database,
|
db: Database,
|
||||||
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
bitcoin_wallet: Arc<crate::bitcoin::Wallet>,
|
||||||
@ -47,219 +110,195 @@ pub async fn swap<R>(
|
|||||||
where
|
where
|
||||||
R: RngCore + CryptoRng + Send,
|
R: RngCore + CryptoRng + Send,
|
||||||
{
|
{
|
||||||
match state {
|
info!("Current state: {}", state);
|
||||||
BobState::Started {
|
if is_target_state(&state) {
|
||||||
state0,
|
Ok(state)
|
||||||
amounts,
|
} else {
|
||||||
peer_id,
|
match state {
|
||||||
addr,
|
BobState::Started {
|
||||||
} => {
|
|
||||||
let state2 = negotiate(
|
|
||||||
state0,
|
state0,
|
||||||
amounts,
|
amounts,
|
||||||
&mut swarm,
|
peer_id,
|
||||||
addr,
|
addr,
|
||||||
&mut rng,
|
} => {
|
||||||
bitcoin_wallet.clone(),
|
let state2 = negotiate(
|
||||||
)
|
state0,
|
||||||
.await?;
|
amounts,
|
||||||
swap(
|
&mut swarm,
|
||||||
BobState::Negotiated(state2, peer_id),
|
addr,
|
||||||
swarm,
|
&mut rng,
|
||||||
db,
|
bitcoin_wallet.clone(),
|
||||||
bitcoin_wallet,
|
)
|
||||||
monero_wallet,
|
.await?;
|
||||||
rng,
|
run_until(
|
||||||
swap_id,
|
BobState::Negotiated(state2, peer_id),
|
||||||
)
|
is_target_state,
|
||||||
.await
|
swarm,
|
||||||
}
|
db,
|
||||||
BobState::Negotiated(state2, alice_peer_id) => {
|
bitcoin_wallet,
|
||||||
// Alice and Bob have exchanged info
|
monero_wallet,
|
||||||
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
|
rng,
|
||||||
// db.insert_latest_state(state);
|
swap_id,
|
||||||
swap(
|
)
|
||||||
BobState::BtcLocked(state3, alice_peer_id),
|
.await
|
||||||
swarm,
|
}
|
||||||
db,
|
BobState::Negotiated(state2, alice_peer_id) => {
|
||||||
bitcoin_wallet,
|
// Alice and Bob have exchanged info
|
||||||
monero_wallet,
|
let state3 = state2.lock_btc(bitcoin_wallet.as_ref()).await?;
|
||||||
rng,
|
// db.insert_latest_state(state);
|
||||||
swap_id,
|
run_until(
|
||||||
)
|
BobState::BtcLocked(state3, alice_peer_id),
|
||||||
.await
|
is_target_state,
|
||||||
}
|
swarm,
|
||||||
// Bob has locked Btc
|
db,
|
||||||
// Watch for Alice to Lock Xmr or for t1 to elapse
|
bitcoin_wallet,
|
||||||
BobState::BtcLocked(state3, alice_peer_id) => {
|
monero_wallet,
|
||||||
// todo: watch until t1, not indefinetely
|
rng,
|
||||||
let state4 = match swarm.next().await {
|
swap_id,
|
||||||
OutEvent::Message2(msg) => {
|
)
|
||||||
state3
|
.await
|
||||||
.watch_for_lock_xmr(monero_wallet.as_ref(), msg)
|
}
|
||||||
.await?
|
// Bob has locked Btc
|
||||||
}
|
// Watch for Alice to Lock Xmr or for t1 to elapse
|
||||||
other => panic!("unexpected event: {:?}", other),
|
BobState::BtcLocked(state3, alice_peer_id) => {
|
||||||
};
|
// todo: watch until t1, not indefinetely
|
||||||
swap(
|
let state4 = match swarm.next().await {
|
||||||
BobState::XmrLocked(state4, alice_peer_id),
|
OutEvent::Message2(msg) => {
|
||||||
swarm,
|
state3
|
||||||
db,
|
.watch_for_lock_xmr(monero_wallet.as_ref(), msg)
|
||||||
bitcoin_wallet,
|
.await?
|
||||||
monero_wallet,
|
|
||||||
rng,
|
|
||||||
swap_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
BobState::XmrLocked(state, alice_peer_id) => {
|
|
||||||
// Alice has locked Xmr
|
|
||||||
// Bob sends Alice his key
|
|
||||||
let tx_redeem_encsig = state.tx_redeem_encsig();
|
|
||||||
// Do we have to wait for a response?
|
|
||||||
// What if Alice fails to receive this? Should we always resend?
|
|
||||||
// todo: If we cannot dial Alice we should go to EncSigSent. Maybe dialing
|
|
||||||
// should happen in this arm?
|
|
||||||
swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig);
|
|
||||||
|
|
||||||
// Sadly we have to poll the swarm to get make sure the message is sent?
|
|
||||||
// FIXME: Having to wait for Alice's response here is a big problem, because
|
|
||||||
// we're stuck if she doesn't send her response back. I believe this is
|
|
||||||
// currently necessary, so we may have to rework this and/or how we use libp2p
|
|
||||||
match swarm.next().await {
|
|
||||||
OutEvent::Message3 => {
|
|
||||||
debug!("Got Message3 empty response");
|
|
||||||
}
|
|
||||||
other => panic!("unexpected event: {:?}", other),
|
|
||||||
};
|
|
||||||
|
|
||||||
swap(
|
|
||||||
BobState::EncSigSent(state, alice_peer_id),
|
|
||||||
swarm,
|
|
||||||
db,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
rng,
|
|
||||||
swap_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
BobState::EncSigSent(state, ..) => {
|
|
||||||
// Watch for redeem
|
|
||||||
let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref());
|
|
||||||
let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref());
|
|
||||||
|
|
||||||
tokio::select! {
|
|
||||||
val = redeem_watcher => {
|
|
||||||
swap(
|
|
||||||
BobState::BtcRedeemed(val?),
|
|
||||||
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?;
|
|
||||||
}
|
}
|
||||||
|
other => panic!("unexpected event: {:?}", other),
|
||||||
|
};
|
||||||
|
run_until(
|
||||||
|
BobState::XmrLocked(state4, alice_peer_id),
|
||||||
|
is_target_state,
|
||||||
|
swarm,
|
||||||
|
db,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
rng,
|
||||||
|
swap_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
BobState::XmrLocked(state, alice_peer_id) => {
|
||||||
|
// Alice has locked Xmr
|
||||||
|
// Bob sends Alice his key
|
||||||
|
let tx_redeem_encsig = state.tx_redeem_encsig();
|
||||||
|
// Do we have to wait for a response?
|
||||||
|
// What if Alice fails to receive this? Should we always resend?
|
||||||
|
// todo: If we cannot dial Alice we should go to EncSigSent. Maybe dialing
|
||||||
|
// should happen in this arm?
|
||||||
|
swarm.send_message3(alice_peer_id.clone(), tx_redeem_encsig);
|
||||||
|
|
||||||
swap(
|
// Sadly we have to poll the swarm to get make sure the message is sent?
|
||||||
BobState::Cancelled(state),
|
// FIXME: Having to wait for Alice's response here is a big problem, because
|
||||||
swarm,
|
// we're stuck if she doesn't send her response back. I believe this is
|
||||||
db,
|
// currently necessary, so we may have to rework this and/or how we use libp2p
|
||||||
bitcoin_wallet,
|
match swarm.next().await {
|
||||||
monero_wallet,
|
OutEvent::Message3 => {
|
||||||
rng,
|
debug!("Got Message3 empty response");
|
||||||
swap_id
|
}
|
||||||
)
|
other => panic!("unexpected event: {:?}", other),
|
||||||
.await
|
};
|
||||||
|
|
||||||
|
run_until(
|
||||||
|
BobState::EncSigSent(state, alice_peer_id),
|
||||||
|
is_target_state,
|
||||||
|
swarm,
|
||||||
|
db,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
rng,
|
||||||
|
swap_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
BobState::EncSigSent(state, ..) => {
|
||||||
|
// Watch for redeem
|
||||||
|
let redeem_watcher = state.watch_for_redeem_btc(bitcoin_wallet.as_ref());
|
||||||
|
let t1_timeout = state.wait_for_t1(bitcoin_wallet.as_ref());
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
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_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_target_state,
|
||||||
|
swarm,
|
||||||
|
db,
|
||||||
|
bitcoin_wallet,
|
||||||
|
monero_wallet,
|
||||||
|
rng,
|
||||||
|
swap_id,
|
||||||
|
)
|
||||||
|
.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::BtcRedeemed(state) => {
|
|
||||||
// Bob redeems XMR using revealed s_a
|
|
||||||
state.claim_xmr(monero_wallet.as_ref()).await?;
|
|
||||||
swap(
|
|
||||||
BobState::XmrRedeemed,
|
|
||||||
swarm,
|
|
||||||
db,
|
|
||||||
bitcoin_wallet,
|
|
||||||
monero_wallet,
|
|
||||||
rng,
|
|
||||||
swap_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
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> {
|
|
||||||
// match state {
|
|
||||||
// BobState::Started => {
|
|
||||||
// // Nothing has been commited by either party, abort swap.
|
|
||||||
// abort(BobState::SafelyAborted, io).await
|
|
||||||
// }
|
|
||||||
// BobState::Negotiated => {
|
|
||||||
// // Nothing has been commited by either party, abort swap.
|
|
||||||
// abort(BobState::SafelyAborted, io).await
|
|
||||||
// }
|
|
||||||
// BobState::BtcLocked => {
|
|
||||||
// // Bob has locked BTC and must refund it
|
|
||||||
// // Bob waits for alice to publish TxRedeem or t1
|
|
||||||
// if unimplemented!("TxRedeemSeen") {
|
|
||||||
// // Alice has redeemed revealing s_a
|
|
||||||
// abort(BobState::BtcRedeemed, io).await
|
|
||||||
// } else if unimplemented!("T1Elapsed") {
|
|
||||||
// // publish TxCancel or see if it has been published
|
|
||||||
// abort(BobState::Cancelled, io).await
|
|
||||||
// } else {
|
|
||||||
// Err(unimplemented!())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// BobState::XmrLocked => {
|
|
||||||
// // Alice has locked Xmr
|
|
||||||
// // Wait until t1
|
|
||||||
// if unimplemented!(">t1 and <t2") {
|
|
||||||
// // Bob publishes TxCancel
|
|
||||||
// abort(BobState::Cancelled, io).await
|
|
||||||
// } else {
|
|
||||||
// // >t2
|
|
||||||
// // submit TxCancel
|
|
||||||
// abort(BobState::Punished, io).await
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// BobState::Cancelled => {
|
|
||||||
// // 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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// BobState::BtcRedeemed => {
|
|
||||||
// // Bob uses revealed s_a to redeem XMR
|
|
||||||
// abort(BobState::XmrRedeemed, io).await
|
|
||||||
// }
|
|
||||||
// BobState::BtcRefunded => Ok(BobState::BtcRefunded),
|
|
||||||
// BobState::Punished => Ok(BobState::Punished),
|
|
||||||
// BobState::SafelyAborted => Ok(BobState::SafelyAborted),
|
|
||||||
// BobState::XmrRedeemed => Ok(BobState::XmrRedeemed),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use bitcoin_harness::Bitcoind;
|
use bitcoin_harness::Bitcoind;
|
||||||
use futures::{channel::mpsc, future::try_join};
|
use futures::future::try_join;
|
||||||
use libp2p::Multiaddr;
|
use libp2p::{Multiaddr, PeerId};
|
||||||
use monero_harness::Monero;
|
use monero_harness::Monero;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -10,228 +10,71 @@ use swap::{
|
|||||||
};
|
};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use testcontainers::clients::Cli;
|
use testcontainers::clients::Cli;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt as _;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use xmr_btc::{bitcoin, cross_curve_dleq};
|
use xmr_btc::{bitcoin, config::Config, cross_curve_dleq};
|
||||||
|
|
||||||
|
/// Run the following tests with RUST_MIN_STACK=10000000
|
||||||
|
|
||||||
#[ignore]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn swap() {
|
async fn happy_path() {
|
||||||
use tracing_subscriber::util::SubscriberInitExt as _;
|
|
||||||
let _guard = tracing_subscriber::fmt()
|
let _guard = tracing_subscriber::fmt()
|
||||||
.with_env_filter("swap=info,xmr_btc=info")
|
.with_env_filter("trace,hyper=warn")
|
||||||
.with_ansi(false)
|
|
||||||
.set_default();
|
.set_default();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
||||||
|
let _ = bitcoind.init(5).await;
|
||||||
|
let (monero, _container) =
|
||||||
|
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let btc_alice = bitcoin::Amount::ZERO;
|
||||||
|
let btc_bob = btc_to_swap * 10;
|
||||||
|
|
||||||
|
// this xmr value matches the logic of alice::calculate_amounts i.e. btc *
|
||||||
|
// 10_000 * 100
|
||||||
|
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
let xmr_alice = xmr_to_swap * 10;
|
||||||
|
let xmr_bob = xmr_btc::monero::Amount::from_piconero(0);
|
||||||
|
|
||||||
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876"
|
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876"
|
||||||
.parse()
|
.parse()
|
||||||
.expect("failed to parse Alice's address");
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
let cli = Cli::default();
|
let (alice_state, alice_swarm, alice_btc_wallet, alice_xmr_wallet, alice_peer_id) = init_alice(
|
||||||
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
&bitcoind,
|
||||||
dbg!(&bitcoind.node_url);
|
&monero,
|
||||||
let _ = bitcoind.init(5).await;
|
btc_to_swap,
|
||||||
|
btc_alice,
|
||||||
let btc = bitcoin::Amount::from_sat(1_000_000);
|
xmr_to_swap,
|
||||||
let btc_alice = bitcoin::Amount::ZERO;
|
xmr_alice,
|
||||||
let btc_bob = btc * 10;
|
|
||||||
|
|
||||||
// this xmr value matches the logic of alice::calculate_amounts i.e. btc *
|
|
||||||
// 10_000 * 100
|
|
||||||
let xmr = 1_000_000_000_000;
|
|
||||||
let xmr_alice = xmr * 10;
|
|
||||||
let xmr_bob = 0;
|
|
||||||
|
|
||||||
let alice_btc_wallet = Arc::new(
|
|
||||||
swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone())
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
let bob_btc_wallet = Arc::new(
|
|
||||||
swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone())
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
bitcoind
|
|
||||||
.mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (monero, _container) =
|
|
||||||
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
monero
|
|
||||||
.init(vec![("alice", xmr_alice), ("bob", xmr_bob)])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let alice_xmr_wallet = Arc::new(swap::monero::Wallet(
|
|
||||||
monero.wallet("alice").unwrap().client(),
|
|
||||||
));
|
|
||||||
let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client()));
|
|
||||||
|
|
||||||
let alice_behaviour = alice::Behaviour::default();
|
|
||||||
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
|
||||||
|
|
||||||
let db = Database::open(std::path::Path::new("../.swap-db/")).unwrap();
|
|
||||||
let alice_swap = alice::swap(
|
|
||||||
alice_btc_wallet.clone(),
|
|
||||||
alice_xmr_wallet.clone(),
|
|
||||||
db,
|
|
||||||
alice_multiaddr.clone(),
|
alice_multiaddr.clone(),
|
||||||
alice_transport,
|
)
|
||||||
alice_behaviour,
|
.await;
|
||||||
);
|
|
||||||
|
|
||||||
let db_dir = tempdir().unwrap();
|
let (bob_state, bob_swarm, bob_btc_wallet, bob_xmr_wallet, bob_db) = init_bob(
|
||||||
let db = Database::open(db_dir.path()).unwrap();
|
|
||||||
let (cmd_tx, mut _cmd_rx) = mpsc::channel(1);
|
|
||||||
let (mut rsp_tx, rsp_rx) = mpsc::channel(1);
|
|
||||||
let bob_behaviour = bob::Behaviour::default();
|
|
||||||
let bob_transport = build(bob_behaviour.identity()).unwrap();
|
|
||||||
let bob_swap = bob::swap(
|
|
||||||
bob_btc_wallet.clone(),
|
|
||||||
bob_xmr_wallet.clone(),
|
|
||||||
db,
|
|
||||||
btc.as_sat(),
|
|
||||||
alice_multiaddr,
|
alice_multiaddr,
|
||||||
cmd_tx,
|
alice_peer_id,
|
||||||
rsp_rx,
|
&bitcoind,
|
||||||
bob_transport,
|
&monero,
|
||||||
bob_behaviour,
|
btc_to_swap,
|
||||||
);
|
btc_bob,
|
||||||
|
xmr_to_swap,
|
||||||
|
xmr_bob,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
// automate the verification step by accepting any amounts sent over by Alice
|
|
||||||
rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap();
|
|
||||||
|
|
||||||
try_join(alice_swap, bob_swap).await.unwrap();
|
|
||||||
|
|
||||||
let btc_alice_final = alice_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
let btc_bob_final = bob_btc_wallet.as_ref().balance().await.unwrap();
|
|
||||||
|
|
||||||
let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap();
|
|
||||||
|
|
||||||
bob_xmr_wallet.as_ref().0.refresh().await.unwrap();
|
|
||||||
let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
btc_alice_final,
|
|
||||||
btc_alice + btc - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
|
||||||
);
|
|
||||||
assert!(btc_bob_final <= btc_bob - btc);
|
|
||||||
|
|
||||||
assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr);
|
|
||||||
assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn happy_path_recursive_executor() {
|
|
||||||
use tracing_subscriber::util::SubscriberInitExt as _;
|
|
||||||
let _guard = tracing_subscriber::fmt()
|
|
||||||
.with_env_filter("swap=info,xmr_btc=info")
|
|
||||||
.with_ansi(false)
|
|
||||||
.set_default();
|
|
||||||
|
|
||||||
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9876"
|
|
||||||
.parse()
|
|
||||||
.expect("failed to parse Alice's address");
|
|
||||||
|
|
||||||
let cli = Cli::default();
|
|
||||||
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
|
||||||
dbg!(&bitcoind.node_url);
|
|
||||||
let _ = bitcoind.init(5).await;
|
|
||||||
|
|
||||||
let btc = bitcoin::Amount::from_sat(1_000_000);
|
|
||||||
let btc_alice = bitcoin::Amount::ZERO;
|
|
||||||
let btc_bob = btc * 10;
|
|
||||||
|
|
||||||
// this xmr value matches the logic of alice::calculate_amounts i.e. btc *
|
|
||||||
// 10_000 * 100
|
|
||||||
let xmr = 1_000_000_000_000;
|
|
||||||
let xmr_alice = xmr * 10;
|
|
||||||
let xmr_bob = 0;
|
|
||||||
|
|
||||||
let alice_btc_wallet = Arc::new(
|
|
||||||
swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone())
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
let bob_btc_wallet = Arc::new(
|
|
||||||
swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone())
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
bitcoind
|
|
||||||
.mint(bob_btc_wallet.0.new_address().await.unwrap(), btc_bob)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (monero, _container) =
|
|
||||||
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
monero
|
|
||||||
.init(vec![("alice", xmr_alice), ("bob", xmr_bob)])
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let alice_xmr_wallet = Arc::new(swap::monero::Wallet(
|
|
||||||
monero.wallet("alice").unwrap().client(),
|
|
||||||
));
|
|
||||||
let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client()));
|
|
||||||
|
|
||||||
let amounts = SwapAmounts {
|
|
||||||
btc,
|
|
||||||
xmr: xmr_btc::monero::Amount::from_piconero(xmr),
|
|
||||||
};
|
|
||||||
|
|
||||||
let alice_behaviour = alice::Behaviour::default();
|
|
||||||
let alice_peer_id = alice_behaviour.peer_id().clone();
|
|
||||||
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
|
||||||
let rng = &mut OsRng;
|
|
||||||
let alice_state = {
|
|
||||||
let a = bitcoin::SecretKey::new_random(rng);
|
|
||||||
let s_a = cross_curve_dleq::Scalar::random(rng);
|
|
||||||
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);
|
|
||||||
AliceState::Started {
|
|
||||||
amounts,
|
|
||||||
a,
|
|
||||||
s_a,
|
|
||||||
v_a,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let alice_swarm =
|
|
||||||
alice::new_swarm(alice_multiaddr.clone(), alice_transport, alice_behaviour).unwrap();
|
|
||||||
let config = xmr_btc::config::Config::regtest();
|
|
||||||
let alice_swap = alice::swap::swap(
|
let alice_swap = alice::swap::swap(
|
||||||
alice_state,
|
alice_state,
|
||||||
alice_swarm,
|
alice_swarm,
|
||||||
alice_btc_wallet.clone(),
|
alice_btc_wallet.clone(),
|
||||||
alice_xmr_wallet.clone(),
|
alice_xmr_wallet.clone(),
|
||||||
config,
|
Config::regtest(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let bob_db_dir = tempdir().unwrap();
|
|
||||||
let bob_db = Database::open(bob_db_dir.path()).unwrap();
|
|
||||||
let bob_behaviour = bob::Behaviour::default();
|
|
||||||
let bob_transport = build(bob_behaviour.identity()).unwrap();
|
|
||||||
|
|
||||||
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
|
||||||
let state0 = xmr_btc::bob::State0::new(
|
|
||||||
rng,
|
|
||||||
btc,
|
|
||||||
xmr_btc::monero::Amount::from_piconero(xmr),
|
|
||||||
REFUND_TIMELOCK,
|
|
||||||
PUNISH_TIMELOCK,
|
|
||||||
refund_address,
|
|
||||||
);
|
|
||||||
let bob_state = BobState::Started {
|
|
||||||
state0,
|
|
||||||
amounts,
|
|
||||||
peer_id: alice_peer_id,
|
|
||||||
addr: alice_multiaddr,
|
|
||||||
};
|
|
||||||
let bob_swarm = bob::new_swarm(bob_transport, bob_behaviour).unwrap();
|
|
||||||
let bob_swap = bob::swap::swap(
|
let bob_swap = bob::swap::swap(
|
||||||
bob_state,
|
bob_state,
|
||||||
bob_swarm,
|
bob_swarm,
|
||||||
@ -254,10 +97,219 @@ async fn happy_path_recursive_executor() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
btc_alice_final,
|
btc_alice_final,
|
||||||
btc_alice + btc - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
btc_alice + btc_to_swap - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||||
);
|
);
|
||||||
assert!(btc_bob_final <= btc_bob - btc);
|
assert!(btc_bob_final <= btc_bob - btc_to_swap);
|
||||||
|
|
||||||
assert!(xmr_alice_final.as_piconero() <= xmr_alice - xmr);
|
assert!(xmr_alice_final <= xmr_alice - xmr_to_swap);
|
||||||
assert_eq!(xmr_bob_final.as_piconero(), xmr_bob + xmr);
|
assert_eq!(xmr_bob_final, xmr_bob + xmr_to_swap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn alice_punishes_if_bob_never_acts_after_fund() {
|
||||||
|
let _guard = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter("trace,hyper=warn")
|
||||||
|
.set_default();
|
||||||
|
|
||||||
|
let cli = Cli::default();
|
||||||
|
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
||||||
|
let _ = bitcoind.init(5).await;
|
||||||
|
let (monero, _container) =
|
||||||
|
Monero::new(&cli, None, vec!["alice".to_string(), "bob".to_string()])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_sat(1_000_000);
|
||||||
|
let xmr_to_swap = xmr_btc::monero::Amount::from_piconero(1_000_000_000_000);
|
||||||
|
|
||||||
|
let bob_btc_starting_balance = btc_to_swap * 10;
|
||||||
|
let bob_xmr_starting_balance = xmr_btc::monero::Amount::from_piconero(0);
|
||||||
|
|
||||||
|
let alice_btc_starting_balance = bitcoin::Amount::ZERO;
|
||||||
|
let alice_xmr_starting_balance = xmr_to_swap * 10;
|
||||||
|
|
||||||
|
let alice_multiaddr: Multiaddr = "/ip4/127.0.0.1/tcp/9877"
|
||||||
|
.parse()
|
||||||
|
.expect("failed to parse Alice's address");
|
||||||
|
|
||||||
|
let (alice_state, alice_swarm, alice_btc_wallet, alice_xmr_wallet, alice_peer_id) = init_alice(
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
alice_btc_starting_balance,
|
||||||
|
xmr_to_swap,
|
||||||
|
alice_xmr_starting_balance,
|
||||||
|
alice_multiaddr.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (bob_state, bob_swarm, bob_btc_wallet, bob_xmr_wallet, bob_db) = init_bob(
|
||||||
|
alice_multiaddr,
|
||||||
|
alice_peer_id,
|
||||||
|
&bitcoind,
|
||||||
|
&monero,
|
||||||
|
btc_to_swap,
|
||||||
|
bob_btc_starting_balance,
|
||||||
|
xmr_to_swap,
|
||||||
|
bob_xmr_starting_balance,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let bob_xmr_locked_fut = bob::swap::run_until(
|
||||||
|
bob_state,
|
||||||
|
bob::swap::is_xmr_locked,
|
||||||
|
bob_swarm,
|
||||||
|
bob_db,
|
||||||
|
bob_btc_wallet.clone(),
|
||||||
|
bob_xmr_wallet.clone(),
|
||||||
|
OsRng,
|
||||||
|
Uuid::new_v4(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let alice_fut = alice::swap::swap(
|
||||||
|
alice_state,
|
||||||
|
alice_swarm,
|
||||||
|
alice_btc_wallet.clone(),
|
||||||
|
alice_xmr_wallet.clone(),
|
||||||
|
Config::regtest(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait until alice has locked xmr and bob h as locked btc
|
||||||
|
let ((alice_state, _), _bob_state) = try_join(alice_fut, bob_xmr_locked_fut).await.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(alice_state, AliceState::Punished));
|
||||||
|
|
||||||
|
// todo: Add balance assertions
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
async fn init_alice(
|
||||||
|
bitcoind: &Bitcoind<'_>,
|
||||||
|
monero: &Monero,
|
||||||
|
btc_to_swap: bitcoin::Amount,
|
||||||
|
_btc_starting_balance: bitcoin::Amount,
|
||||||
|
xmr_to_swap: xmr_btc::monero::Amount,
|
||||||
|
xmr_starting_balance: xmr_btc::monero::Amount,
|
||||||
|
alice_multiaddr: Multiaddr,
|
||||||
|
) -> (
|
||||||
|
AliceState,
|
||||||
|
alice::Swarm,
|
||||||
|
Arc<swap::bitcoin::Wallet>,
|
||||||
|
Arc<swap::monero::Wallet>,
|
||||||
|
PeerId,
|
||||||
|
) {
|
||||||
|
monero
|
||||||
|
.init(vec![("alice", xmr_starting_balance.as_piconero())])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let alice_xmr_wallet = Arc::new(swap::monero::Wallet(
|
||||||
|
monero.wallet("alice").unwrap().client(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let alice_btc_wallet = Arc::new(
|
||||||
|
swap::bitcoin::Wallet::new("alice", bitcoind.node_url.clone())
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let amounts = SwapAmounts {
|
||||||
|
btc: btc_to_swap,
|
||||||
|
xmr: xmr_to_swap,
|
||||||
|
};
|
||||||
|
|
||||||
|
let alice_behaviour = alice::Behaviour::default();
|
||||||
|
let alice_peer_id = alice_behaviour.peer_id();
|
||||||
|
let alice_transport = build(alice_behaviour.identity()).unwrap();
|
||||||
|
let rng = &mut OsRng;
|
||||||
|
let alice_state = {
|
||||||
|
let a = bitcoin::SecretKey::new_random(rng);
|
||||||
|
let s_a = cross_curve_dleq::Scalar::random(rng);
|
||||||
|
let v_a = xmr_btc::monero::PrivateViewKey::new_random(rng);
|
||||||
|
AliceState::Started {
|
||||||
|
amounts,
|
||||||
|
a,
|
||||||
|
s_a,
|
||||||
|
v_a,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let alice_swarm = alice::new_swarm(alice_multiaddr, alice_transport, alice_behaviour).unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
alice_state,
|
||||||
|
alice_swarm,
|
||||||
|
alice_btc_wallet,
|
||||||
|
alice_xmr_wallet,
|
||||||
|
alice_peer_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
async fn init_bob(
|
||||||
|
alice_multiaddr: Multiaddr,
|
||||||
|
alice_peer_id: PeerId,
|
||||||
|
bitcoind: &Bitcoind<'_>,
|
||||||
|
monero: &Monero,
|
||||||
|
btc_to_swap: bitcoin::Amount,
|
||||||
|
btc_starting_balance: bitcoin::Amount,
|
||||||
|
xmr_to_swap: xmr_btc::monero::Amount,
|
||||||
|
xmr_stating_balance: xmr_btc::monero::Amount,
|
||||||
|
) -> (
|
||||||
|
BobState,
|
||||||
|
bob::Swarm,
|
||||||
|
Arc<swap::bitcoin::Wallet>,
|
||||||
|
Arc<swap::monero::Wallet>,
|
||||||
|
Database,
|
||||||
|
) {
|
||||||
|
let bob_btc_wallet = Arc::new(
|
||||||
|
swap::bitcoin::Wallet::new("bob", bitcoind.node_url.clone())
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
bitcoind
|
||||||
|
.mint(
|
||||||
|
bob_btc_wallet.0.new_address().await.unwrap(),
|
||||||
|
btc_starting_balance,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
monero
|
||||||
|
.init(vec![("bob", xmr_stating_balance.as_piconero())])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client()));
|
||||||
|
|
||||||
|
let amounts = SwapAmounts {
|
||||||
|
btc: btc_to_swap,
|
||||||
|
xmr: xmr_to_swap,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bob_db_dir = tempdir().unwrap();
|
||||||
|
let bob_db = Database::open(bob_db_dir.path()).unwrap();
|
||||||
|
let bob_behaviour = bob::Behaviour::default();
|
||||||
|
let bob_transport = build(bob_behaviour.identity()).unwrap();
|
||||||
|
|
||||||
|
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
||||||
|
let state0 = xmr_btc::bob::State0::new(
|
||||||
|
&mut OsRng,
|
||||||
|
btc_to_swap,
|
||||||
|
xmr_to_swap,
|
||||||
|
REFUND_TIMELOCK,
|
||||||
|
PUNISH_TIMELOCK,
|
||||||
|
refund_address,
|
||||||
|
);
|
||||||
|
let bob_state = BobState::Started {
|
||||||
|
state0,
|
||||||
|
amounts,
|
||||||
|
peer_id: alice_peer_id,
|
||||||
|
addr: alice_multiaddr,
|
||||||
|
};
|
||||||
|
let bob_swarm = bob::new_swarm(bob_transport, bob_behaviour).unwrap();
|
||||||
|
|
||||||
|
(bob_state, bob_swarm, bob_btc_wallet, bob_xmr_wallet, bob_db)
|
||||||
}
|
}
|
||||||
|
@ -799,6 +799,9 @@ impl State4 {
|
|||||||
t1_timeout.await;
|
t1_timeout.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn tx_lock_id(&self) -> bitcoin::Txid {
|
||||||
|
self.tx_lock.txid()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
@ -6,6 +6,7 @@ pub struct Config {
|
|||||||
pub bob_time_to_act: Duration,
|
pub bob_time_to_act: Duration,
|
||||||
pub bitcoin_finality_confirmations: u32,
|
pub bitcoin_finality_confirmations: u32,
|
||||||
pub bitcoin_avg_block_time: Duration,
|
pub bitcoin_avg_block_time: Duration,
|
||||||
|
pub monero_max_finality_time: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@ -14,6 +15,10 @@ impl Config {
|
|||||||
bob_time_to_act: *mainnet::BOB_TIME_TO_ACT,
|
bob_time_to_act: *mainnet::BOB_TIME_TO_ACT,
|
||||||
bitcoin_finality_confirmations: mainnet::BITCOIN_FINALITY_CONFIRMATIONS,
|
bitcoin_finality_confirmations: mainnet::BITCOIN_FINALITY_CONFIRMATIONS,
|
||||||
bitcoin_avg_block_time: *mainnet::BITCOIN_AVG_BLOCK_TIME,
|
bitcoin_avg_block_time: *mainnet::BITCOIN_AVG_BLOCK_TIME,
|
||||||
|
// We apply a scaling factor (1.5) so that the swap is not aborted when the
|
||||||
|
// blockchain is slow
|
||||||
|
monero_max_finality_time: (*mainnet::MONERO_AVG_BLOCK_TIME).mul_f64(1.5)
|
||||||
|
* mainnet::MONERO_FINALITY_CONFIRMATIONS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +27,10 @@ impl Config {
|
|||||||
bob_time_to_act: *regtest::BOB_TIME_TO_ACT,
|
bob_time_to_act: *regtest::BOB_TIME_TO_ACT,
|
||||||
bitcoin_finality_confirmations: regtest::BITCOIN_FINALITY_CONFIRMATIONS,
|
bitcoin_finality_confirmations: regtest::BITCOIN_FINALITY_CONFIRMATIONS,
|
||||||
bitcoin_avg_block_time: *regtest::BITCOIN_AVG_BLOCK_TIME,
|
bitcoin_avg_block_time: *regtest::BITCOIN_AVG_BLOCK_TIME,
|
||||||
|
// We apply a scaling factor (1.5) so that the swap is not aborted when the
|
||||||
|
// blockchain is slow
|
||||||
|
monero_max_finality_time: (*regtest::MONERO_AVG_BLOCK_TIME).mul_f64(1.5)
|
||||||
|
* regtest::MONERO_FINALITY_CONFIRMATIONS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,15 +44,23 @@ mod mainnet {
|
|||||||
pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 3;
|
pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 3;
|
||||||
|
|
||||||
pub static BITCOIN_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(10 * 60));
|
pub static BITCOIN_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(10 * 60));
|
||||||
|
|
||||||
|
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 15;
|
||||||
|
|
||||||
|
pub static MONERO_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(2 * 60));
|
||||||
}
|
}
|
||||||
|
|
||||||
mod regtest {
|
mod regtest {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// In test, set to 5 seconds to fail fast
|
// In test, we set a shorter time to fail fast
|
||||||
pub static BOB_TIME_TO_ACT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(10));
|
pub static BOB_TIME_TO_ACT: Lazy<Duration> = Lazy::new(|| Duration::from_secs(30));
|
||||||
|
|
||||||
pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 1;
|
pub static BITCOIN_FINALITY_CONFIRMATIONS: u32 = 1;
|
||||||
|
|
||||||
pub static BITCOIN_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(5));
|
pub static BITCOIN_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(5));
|
||||||
|
|
||||||
|
pub static MONERO_FINALITY_CONFIRMATIONS: u32 = 1;
|
||||||
|
|
||||||
|
pub static MONERO_AVG_BLOCK_TIME: Lazy<Duration> = Lazy::new(|| Duration::from_secs(60));
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use anyhow::Result;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Mul, Sub};
|
||||||
|
|
||||||
pub use curve25519_dalek::scalar::Scalar;
|
pub use curve25519_dalek::scalar::Scalar;
|
||||||
pub use monero::*;
|
pub use monero::*;
|
||||||
@ -97,6 +97,14 @@ impl Sub for Amount {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Mul<u64> for Amount {
|
||||||
|
type Output = Amount;
|
||||||
|
|
||||||
|
fn mul(self, rhs: u64) -> Self::Output {
|
||||||
|
Self(self.0 * rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Amount> for u64 {
|
impl From<Amount> for u64 {
|
||||||
fn from(from: Amount) -> u64 {
|
fn from(from: Amount) -> u64 {
|
||||||
from.0
|
from.0
|
||||||
|
Loading…
Reference in New Issue
Block a user