mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Make Alice watch for Monero lock transaction without transfer proof
This commit is contained in:
parent
7371dfb055
commit
0288e004c5
@ -61,9 +61,10 @@ pub trait ReceiveBitcoinRedeemEncsig {
|
||||
///
|
||||
/// The argument `bitcoin_tx_lock_timeout` is used to determine how long we will
|
||||
/// wait for Bob, the counterparty, to lock up the bitcoin.
|
||||
pub fn action_generator<N, B>(
|
||||
pub fn action_generator<N, B, M>(
|
||||
network: Arc<Mutex<N>>,
|
||||
bitcoin_client: Arc<B>,
|
||||
monero_client: Arc<M>,
|
||||
// TODO: Replace this with a new, slimmer struct?
|
||||
State3 {
|
||||
a,
|
||||
@ -93,10 +94,13 @@ where
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
M: monero::WatchForTransferImproved + Send + Sync + 'static,
|
||||
{
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug)]
|
||||
enum SwapFailed {
|
||||
BeforeBtcLock(Reason),
|
||||
AfterBtcLock(Reason),
|
||||
AfterXmrLock(Reason),
|
||||
}
|
||||
|
||||
@ -146,15 +150,30 @@ where
|
||||
scalar: s_a.into_ed25519(),
|
||||
});
|
||||
|
||||
let public_spend_key = S_a + S_b_monero;
|
||||
let public_view_key = v.public();
|
||||
|
||||
co.yield_(Action::LockXmr {
|
||||
amount: xmr,
|
||||
public_spend_key: S_a + S_b_monero,
|
||||
public_view_key: v.public(),
|
||||
public_spend_key,
|
||||
public_view_key,
|
||||
})
|
||||
.await;
|
||||
|
||||
// TODO: Watch for LockXmr using watch-only wallet. Doing so will prevent Alice
|
||||
// from cancelling/refunding unnecessarily.
|
||||
let monero_joint_address = monero::Address::standard(
|
||||
monero::Network::Mainnet,
|
||||
public_spend_key,
|
||||
public_view_key.into(),
|
||||
);
|
||||
|
||||
if let Either::Right(_) = select(
|
||||
monero_client.watch_for_transfer_improved(monero_joint_address, xmr, v),
|
||||
poll_until_btc_has_expired.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(SwapFailed::AfterBtcLock(Reason::BtcExpired));
|
||||
};
|
||||
|
||||
let tx_redeem_encsig = {
|
||||
let mut guard = network.as_ref().lock().await;
|
||||
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
pub use curve25519_dalek::scalar::Scalar;
|
||||
pub use monero::*;
|
||||
pub use monero::{Address, Network, PrivateKey, PublicKey};
|
||||
|
||||
pub const MIN_CONFIRMATIONS: u32 = 10;
|
||||
|
||||
@ -154,6 +154,16 @@ pub trait WatchForTransfer {
|
||||
) -> Result<(), InsufficientFunds>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait WatchForTransferImproved {
|
||||
async fn watch_for_transfer_improved(
|
||||
&self,
|
||||
address: Address,
|
||||
amount: Amount,
|
||||
private_view_key: PrivateViewKey,
|
||||
) -> Result<(), InsufficientFunds>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||
#[error("transaction does not pay enough: expected {expected:?}, got {actual:?}")]
|
||||
pub struct InsufficientFunds {
|
||||
|
@ -22,9 +22,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn happy_path() {
|
||||
let cli = Cli::default();
|
||||
let (monero, _container) = Monero::new(&cli, Some("hp".to_string()), vec![
|
||||
let (monero, _container) = Monero::new(&cli, None, vec![
|
||||
"alice".to_string(),
|
||||
"alice-watch-only".to_string(),
|
||||
"bob".to_string(),
|
||||
"bob-watch-only".to_string(),
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
@ -97,9 +99,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn both_refund() {
|
||||
let cli = Cli::default();
|
||||
let (monero, _container) = Monero::new(&cli, Some("br".to_string()), vec![
|
||||
let (monero, _container) = Monero::new(&cli, None, vec![
|
||||
"alice".to_string(),
|
||||
"alice-watch-only".to_string(),
|
||||
"bob".to_string(),
|
||||
"bob-watch-only".to_string(),
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
@ -174,9 +178,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn alice_punishes() {
|
||||
let cli = Cli::default();
|
||||
let (monero, _containers) = Monero::new(&cli, Some("ap".to_string()), vec![
|
||||
let (monero, _container) = Monero::new(&cli, None, vec![
|
||||
"alice".to_string(),
|
||||
"alice-watch-only".to_string(),
|
||||
"bob".to_string(),
|
||||
"bob-watch-only".to_string(),
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -135,8 +135,14 @@ pub async fn init_test(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let alice_monero_wallet = wallet::monero::Wallet(monero.wallet("alice").unwrap().client());
|
||||
let bob_monero_wallet = wallet::monero::Wallet(monero.wallet("bob").unwrap().client());
|
||||
let alice_monero_wallet = wallet::monero::Wallet {
|
||||
inner: monero.wallet("alice").unwrap().client(),
|
||||
watch_only: monero.wallet("alice-watch-only").unwrap().client(),
|
||||
};
|
||||
let bob_monero_wallet = wallet::monero::Wallet {
|
||||
inner: monero.wallet("bob").unwrap().client(),
|
||||
watch_only: monero.wallet("bob-watch-only").unwrap().client(),
|
||||
};
|
||||
|
||||
let alice_btc_wallet = wallet::bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
||||
.await
|
||||
|
@ -1,19 +1,26 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
||||
use futures::TryFutureExt;
|
||||
use monero::{Address, Network, PrivateKey};
|
||||
use monero_harness::rpc::wallet;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
use xmr_btc::monero::{
|
||||
Address, Amount, CreateWalletForOutput, InsufficientFunds, Network, PrivateKey, PrivateViewKey,
|
||||
PublicKey, PublicViewKey, Transfer, TransferProof, TxHash, WatchForTransfer,
|
||||
PublicKey, PublicViewKey, Transfer, TransferProof, TxHash, WatchForTransfer, WatchForTransferImproved,
|
||||
};
|
||||
|
||||
pub struct Wallet(pub wallet::Client);
|
||||
pub struct Wallet {
|
||||
pub inner: wallet::Client,
|
||||
/// Secondary wallet which is only used to watch for the Monero lock
|
||||
/// transaction without needing a transfer proof.
|
||||
pub watch_only: wallet::Client,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
/// Get the balance of the primary account.
|
||||
pub async fn get_balance(&self) -> Result<Amount> {
|
||||
let amount = self.0.get_balance(0).await?;
|
||||
let amount = self.inner.get_balance(0).await?;
|
||||
|
||||
Ok(Amount::from_piconero(amount))
|
||||
}
|
||||
@ -31,7 +38,7 @@ impl Transfer for Wallet {
|
||||
Address::standard(Network::Mainnet, public_spend_key, public_view_key.into());
|
||||
|
||||
let res = self
|
||||
.0
|
||||
.inner
|
||||
.transfer(0, amount.as_piconero(), &destination_address.to_string())
|
||||
.await?;
|
||||
|
||||
@ -57,7 +64,7 @@ impl CreateWalletForOutput for Wallet {
|
||||
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key);
|
||||
|
||||
let _ = self
|
||||
.0
|
||||
.inner
|
||||
.generate_from_keys(
|
||||
&address.to_string(),
|
||||
Some(&private_spend_key.to_string()),
|
||||
@ -92,7 +99,7 @@ impl WatchForTransfer for Wallet {
|
||||
// in the blockchain yet, or not having enough confirmations on it. All these
|
||||
// errors warrant a retry, but the strategy should probably differ per case
|
||||
let proof = self
|
||||
.0
|
||||
.inner
|
||||
.check_tx_key(
|
||||
&String::from(transfer_proof.tx_hash()),
|
||||
&transfer_proof.tx_key().to_string(),
|
||||
@ -124,3 +131,54 @@ impl WatchForTransfer for Wallet {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WatchForTransferImproved for Wallet {
|
||||
async fn watch_for_transfer_improved(
|
||||
&self,
|
||||
address: Address,
|
||||
expected_amount: Amount,
|
||||
private_view_key: PrivateViewKey,
|
||||
) -> Result<(), InsufficientFunds> {
|
||||
let address = address.to_string();
|
||||
let private_view_key = PrivateKey::from(private_view_key).to_string();
|
||||
let load_address = || {
|
||||
self.watch_only
|
||||
.generate_from_keys(&address, None, &private_view_key)
|
||||
.map_err(backoff::Error::Transient)
|
||||
};
|
||||
|
||||
load_address
|
||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||
.await
|
||||
.expect("transient error is never returned");
|
||||
|
||||
let refresh = || self.watch_only.refresh().map_err(backoff::Error::Transient);
|
||||
|
||||
refresh
|
||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||
.await
|
||||
.expect("transient error is never returned");
|
||||
|
||||
let get_balance = || {
|
||||
self.watch_only
|
||||
.get_balance(0)
|
||||
.map_err(backoff::Error::Transient)
|
||||
};
|
||||
|
||||
let balance = get_balance
|
||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||
.await
|
||||
.expect("transient error is never returned");
|
||||
let balance = Amount::from_piconero(balance);
|
||||
|
||||
if balance != expected_amount {
|
||||
return Err(InsufficientFunds {
|
||||
expected: expected_amount,
|
||||
actual: balance,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ async fn swap_as_alice(
|
||||
let mut action_generator = alice::action_generator(
|
||||
network,
|
||||
bitcoin_wallet.clone(),
|
||||
monero_wallet.clone(),
|
||||
state,
|
||||
BITCOIN_TX_LOCK_TIMEOUT,
|
||||
);
|
||||
@ -233,9 +234,11 @@ async fn swap_as_bob(
|
||||
#[tokio::test]
|
||||
async fn on_chain_happy_path() {
|
||||
let cli = Cli::default();
|
||||
let (monero, _container) = Monero::new(&cli, Some("ochp".to_string()), vec![
|
||||
let (monero, _container) = Monero::new(&cli, None, vec![
|
||||
"alice".to_string(),
|
||||
"alice-watch-only".to_string(),
|
||||
"bob".to_string(),
|
||||
"bob-watch-only".to_string(),
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
@ -328,9 +331,11 @@ async fn on_chain_happy_path() {
|
||||
#[tokio::test]
|
||||
async fn on_chain_both_refund_if_alice_never_redeems() {
|
||||
let cli = Cli::default();
|
||||
let (monero, _container) = Monero::new(&cli, Some("ocbr".to_string()), vec![
|
||||
let (monero, _container) = Monero::new(&cli, None, vec![
|
||||
"alice".to_string(),
|
||||
"alice-watch-only".to_string(),
|
||||
"bob".to_string(),
|
||||
"bob-watch-only".to_string(),
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
@ -423,9 +428,11 @@ async fn on_chain_both_refund_if_alice_never_redeems() {
|
||||
#[tokio::test]
|
||||
async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() {
|
||||
let cli = Cli::default();
|
||||
let (monero, _container) = Monero::new(&cli, Some("ocap".to_string()), vec![
|
||||
let (monero, _container) = Monero::new(&cli, None, vec![
|
||||
"alice".to_string(),
|
||||
"alice-watch-only".to_string(),
|
||||
"bob".to_string(),
|
||||
"bob-watch-only".to_string(),
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
|
Loading…
Reference in New Issue
Block a user