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
|
/// The argument `bitcoin_tx_lock_timeout` is used to determine how long we will
|
||||||
/// wait for Bob, the counterparty, to lock up the bitcoin.
|
/// 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>>,
|
network: Arc<Mutex<N>>,
|
||||||
bitcoin_client: Arc<B>,
|
bitcoin_client: Arc<B>,
|
||||||
|
monero_client: Arc<M>,
|
||||||
// TODO: Replace this with a new, slimmer struct?
|
// TODO: Replace this with a new, slimmer struct?
|
||||||
State3 {
|
State3 {
|
||||||
a,
|
a,
|
||||||
@ -93,10 +94,13 @@ where
|
|||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
|
M: monero::WatchForTransferImproved + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum SwapFailed {
|
enum SwapFailed {
|
||||||
BeforeBtcLock(Reason),
|
BeforeBtcLock(Reason),
|
||||||
|
AfterBtcLock(Reason),
|
||||||
AfterXmrLock(Reason),
|
AfterXmrLock(Reason),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,15 +150,30 @@ where
|
|||||||
scalar: s_a.into_ed25519(),
|
scalar: s_a.into_ed25519(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let public_spend_key = S_a + S_b_monero;
|
||||||
|
let public_view_key = v.public();
|
||||||
|
|
||||||
co.yield_(Action::LockXmr {
|
co.yield_(Action::LockXmr {
|
||||||
amount: xmr,
|
amount: xmr,
|
||||||
public_spend_key: S_a + S_b_monero,
|
public_spend_key,
|
||||||
public_view_key: v.public(),
|
public_view_key,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// TODO: Watch for LockXmr using watch-only wallet. Doing so will prevent Alice
|
let monero_joint_address = monero::Address::standard(
|
||||||
// from cancelling/refunding unnecessarily.
|
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 tx_redeem_encsig = {
|
||||||
let mut guard = network.as_ref().lock().await;
|
let mut guard = network.as_ref().lock().await;
|
||||||
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::ops::{Add, Sub};
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
pub use curve25519_dalek::scalar::Scalar;
|
pub use curve25519_dalek::scalar::Scalar;
|
||||||
pub use monero::*;
|
pub use monero::{Address, Network, PrivateKey, PublicKey};
|
||||||
|
|
||||||
pub const MIN_CONFIRMATIONS: u32 = 10;
|
pub const MIN_CONFIRMATIONS: u32 = 10;
|
||||||
|
|
||||||
@ -154,6 +154,16 @@ pub trait WatchForTransfer {
|
|||||||
) -> Result<(), InsufficientFunds>;
|
) -> 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)]
|
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||||
#[error("transaction does not pay enough: expected {expected:?}, got {actual:?}")]
|
#[error("transaction does not pay enough: expected {expected:?}, got {actual:?}")]
|
||||||
pub struct InsufficientFunds {
|
pub struct InsufficientFunds {
|
||||||
|
@ -22,9 +22,11 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn happy_path() {
|
async fn happy_path() {
|
||||||
let cli = Cli::default();
|
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".to_string(),
|
||||||
|
"alice-watch-only".to_string(),
|
||||||
"bob".to_string(),
|
"bob".to_string(),
|
||||||
|
"bob-watch-only".to_string(),
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -97,9 +99,11 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn both_refund() {
|
async fn both_refund() {
|
||||||
let cli = Cli::default();
|
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".to_string(),
|
||||||
|
"alice-watch-only".to_string(),
|
||||||
"bob".to_string(),
|
"bob".to_string(),
|
||||||
|
"bob-watch-only".to_string(),
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -174,9 +178,11 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn alice_punishes() {
|
async fn alice_punishes() {
|
||||||
let cli = Cli::default();
|
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".to_string(),
|
||||||
|
"alice-watch-only".to_string(),
|
||||||
"bob".to_string(),
|
"bob".to_string(),
|
||||||
|
"bob-watch-only".to_string(),
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -135,8 +135,14 @@ pub async fn init_test(
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let alice_monero_wallet = wallet::monero::Wallet(monero.wallet("alice").unwrap().client());
|
let alice_monero_wallet = wallet::monero::Wallet {
|
||||||
let bob_monero_wallet = wallet::monero::Wallet(monero.wallet("bob").unwrap().client());
|
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)
|
let alice_btc_wallet = wallet::bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
||||||
.await
|
.await
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
||||||
|
use futures::TryFutureExt;
|
||||||
|
use monero::{Address, Network, PrivateKey};
|
||||||
use monero_harness::rpc::wallet;
|
use monero_harness::rpc::wallet;
|
||||||
use std::{str::FromStr, time::Duration};
|
use std::{str::FromStr, time::Duration};
|
||||||
use xmr_btc::monero::{
|
use xmr_btc::monero::{
|
||||||
Address, Amount, CreateWalletForOutput, InsufficientFunds, Network, PrivateKey, PrivateViewKey,
|
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 {
|
impl Wallet {
|
||||||
/// Get the balance of the primary account.
|
/// Get the balance of the primary account.
|
||||||
pub async fn get_balance(&self) -> Result<Amount> {
|
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))
|
Ok(Amount::from_piconero(amount))
|
||||||
}
|
}
|
||||||
@ -31,7 +38,7 @@ impl Transfer for Wallet {
|
|||||||
Address::standard(Network::Mainnet, public_spend_key, public_view_key.into());
|
Address::standard(Network::Mainnet, public_spend_key, public_view_key.into());
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.0
|
.inner
|
||||||
.transfer(0, amount.as_piconero(), &destination_address.to_string())
|
.transfer(0, amount.as_piconero(), &destination_address.to_string())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -57,7 +64,7 @@ impl CreateWalletForOutput for Wallet {
|
|||||||
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key);
|
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key);
|
||||||
|
|
||||||
let _ = self
|
let _ = self
|
||||||
.0
|
.inner
|
||||||
.generate_from_keys(
|
.generate_from_keys(
|
||||||
&address.to_string(),
|
&address.to_string(),
|
||||||
Some(&private_spend_key.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
|
// 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
|
// errors warrant a retry, but the strategy should probably differ per case
|
||||||
let proof = self
|
let proof = self
|
||||||
.0
|
.inner
|
||||||
.check_tx_key(
|
.check_tx_key(
|
||||||
&String::from(transfer_proof.tx_hash()),
|
&String::from(transfer_proof.tx_hash()),
|
||||||
&transfer_proof.tx_key().to_string(),
|
&transfer_proof.tx_key().to_string(),
|
||||||
@ -124,3 +131,54 @@ impl WatchForTransfer for Wallet {
|
|||||||
Ok(())
|
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(
|
let mut action_generator = alice::action_generator(
|
||||||
network,
|
network,
|
||||||
bitcoin_wallet.clone(),
|
bitcoin_wallet.clone(),
|
||||||
|
monero_wallet.clone(),
|
||||||
state,
|
state,
|
||||||
BITCOIN_TX_LOCK_TIMEOUT,
|
BITCOIN_TX_LOCK_TIMEOUT,
|
||||||
);
|
);
|
||||||
@ -233,9 +234,11 @@ async fn swap_as_bob(
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn on_chain_happy_path() {
|
async fn on_chain_happy_path() {
|
||||||
let cli = Cli::default();
|
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".to_string(),
|
||||||
|
"alice-watch-only".to_string(),
|
||||||
"bob".to_string(),
|
"bob".to_string(),
|
||||||
|
"bob-watch-only".to_string(),
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -328,9 +331,11 @@ async fn on_chain_happy_path() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn on_chain_both_refund_if_alice_never_redeems() {
|
async fn on_chain_both_refund_if_alice_never_redeems() {
|
||||||
let cli = Cli::default();
|
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".to_string(),
|
||||||
|
"alice-watch-only".to_string(),
|
||||||
"bob".to_string(),
|
"bob".to_string(),
|
||||||
|
"bob-watch-only".to_string(),
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -423,9 +428,11 @@ async fn on_chain_both_refund_if_alice_never_redeems() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() {
|
async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() {
|
||||||
let cli = Cli::default();
|
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".to_string(),
|
||||||
|
"alice-watch-only".to_string(),
|
||||||
"bob".to_string(),
|
"bob".to_string(),
|
||||||
|
"bob-watch-only".to_string(),
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user