wip: Provide enough funds to both parties

Also use cosntant backoff retry strategy as opposed to exponential
backoff. This is in case retrying several times quickly causes the
retry intervals to become large enough that the test is very slow
and/or the Bitcoin lock transaction expires.

The current problem occurs on the last message i.e. Bob sending
tx_redeem_encsig to Alice. The action is yielded for Bob to do it, but
Alice appears to never receive it (unconfirmed claim, requires more
logging).
This commit is contained in:
Lucas Soriano del Pino 2020-10-29 21:17:00 +11:00
parent eb6bbe6180
commit a37f43a1ba
7 changed files with 55 additions and 37 deletions

View File

@ -2,7 +2,7 @@
//! Alice holds XMR and wishes receive BTC. //! Alice holds XMR and wishes receive BTC.
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use backoff::{future::FutureOperation as _, ExponentialBackoff}; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
use genawaiter::GeneratorState; use genawaiter::GeneratorState;
use libp2p::{ use libp2p::{
core::{identity::Keypair, Multiaddr}, core::{identity::Keypair, Multiaddr},
@ -10,9 +10,9 @@ use libp2p::{
NetworkBehaviour, PeerId, NetworkBehaviour, PeerId,
}; };
use rand::rngs::OsRng; use rand::rngs::OsRng;
use std::sync::Arc; use std::{sync::Arc, time::Duration};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::{debug, info, warn}; use tracing::{info, warn};
use xmr_btc::alice; use xmr_btc::alice;
mod amounts; mod amounts;
@ -55,6 +55,8 @@ pub async fn swap(
impl Network { impl Network {
pub async fn send_message2(&mut self, proof: monero::TransferProof) { pub async fn send_message2(&mut self, proof: monero::TransferProof) {
tracing::debug!("Sending transfer proof");
match self.channel.take() { match self.channel.take() {
None => warn!("Channel not found, did you call this twice?"), None => warn!("Channel not found, did you call this twice?"),
Some(channel) => { Some(channel) => {
@ -67,6 +69,9 @@ pub async fn swap(
} }
} }
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
// to `ConstantBackoff`.
#[async_trait] #[async_trait]
impl ReceiveBitcoinRedeemEncsig for Network { impl ReceiveBitcoinRedeemEncsig for Network {
async fn receive_bitcoin_redeem_encsig(&mut self) -> xmr_btc::bitcoin::EncryptedSignature { async fn receive_bitcoin_redeem_encsig(&mut self) -> xmr_btc::bitcoin::EncryptedSignature {
@ -85,16 +90,13 @@ pub async fn swap(
Result::<_, backoff::Error<UnexpectedMessage>>::Ok(encsig) Result::<_, backoff::Error<UnexpectedMessage>>::Ok(encsig)
}) })
.retry(ExponentialBackoff { .retry(ConstantBackoff::new(Duration::from_secs(1)))
max_elapsed_time: None,
..Default::default()
})
.await .await
.expect("transient errors to be retried") .expect("transient errors to be retried")
} }
} }
debug!("swapping ..."); tracing::debug!("swapping ...");
let mut swarm = new_swarm(listen, local_port)?; let mut swarm = new_swarm(listen, local_port)?;
let message0: bob::Message0; let message0: bob::Message0;
@ -107,7 +109,6 @@ pub async fn swap(
info!("Connection established with: {}", id); info!("Connection established with: {}", id);
} }
OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => { OutEvent::Request(amounts::OutEvent::Btc { btc, channel }) => {
debug!("Got request from Bob to swap {}", btc);
let amounts = calculate_amounts(btc); let amounts = calculate_amounts(btc);
// TODO: We cache the last amounts returned, this needs improving along with // TODO: We cache the last amounts returned, this needs improving along with
// verification of message 0. // verification of message 0.
@ -138,7 +139,6 @@ pub async fn swap(
state0 = Some(state) state0 = Some(state)
} }
OutEvent::Message0(msg) => { OutEvent::Message0(msg) => {
debug!("got message 0 from Bob");
// We don't want Bob to be able to crash us by sending an out of // We don't want Bob to be able to crash us by sending an out of
// order message. Keep looping if Bob has not requested amounts. // order message. Keep looping if Bob has not requested amounts.
if last_amounts.is_some() { if last_amounts.is_some() {

View File

@ -2,7 +2,7 @@ use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use backoff::{future::FutureOperation as _, ExponentialBackoff}; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Transaction}; use bitcoin::{util::psbt::PartiallySignedTransaction, Address, Transaction};
use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind}; use bitcoin_harness::{bitcoind_rpc::PsbtBase64, Bitcoind};
use reqwest::Url; use reqwest::Url;
@ -113,14 +113,14 @@ impl BroadcastSignedTransaction for Wallet {
} }
} }
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
// to `ConstantBackoff`.
#[async_trait] #[async_trait]
impl WatchForRawTransaction for Wallet { impl WatchForRawTransaction for Wallet {
async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction { async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction {
(|| async { Ok(self.0.get_raw_transaction(txid).await?) }) (|| async { Ok(self.0.get_raw_transaction(txid).await?) })
.retry(ExponentialBackoff { .retry(ConstantBackoff::new(Duration::from_secs(1)))
max_elapsed_time: None,
..Default::default()
})
.await .await
.expect("transient errors to be retried") .expect("transient errors to be retried")
} }
@ -130,10 +130,7 @@ impl WatchForRawTransaction for Wallet {
impl BlockHeight for Wallet { impl BlockHeight for Wallet {
async fn block_height(&self) -> u32 { async fn block_height(&self) -> u32 {
(|| async { Ok(self.0.block_height().await?) }) (|| async { Ok(self.0.block_height().await?) })
.retry(ExponentialBackoff { .retry(ConstantBackoff::new(Duration::from_secs(1)))
max_elapsed_time: None,
..Default::default()
})
.await .await
.expect("transient errors to be retried") .expect("transient errors to be retried")
} }
@ -160,10 +157,7 @@ impl TransactionBlockHeight for Wallet {
Result::<_, backoff::Error<Error>>::Ok(block_height) Result::<_, backoff::Error<Error>>::Ok(block_height)
}) })
.retry(ExponentialBackoff { .retry(ConstantBackoff::new(Duration::from_secs(1)))
max_elapsed_time: None,
..Default::default()
})
.await .await
.expect("transient errors to be retried") .expect("transient errors to be retried")
} }

View File

@ -2,7 +2,7 @@
//! Bob holds BTC and wishes receive XMR. //! Bob holds BTC and wishes receive XMR.
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use backoff::{future::FutureOperation as _, ExponentialBackoff}; use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
use futures::{ use futures::{
channel::mpsc::{Receiver, Sender}, channel::mpsc::{Receiver, Sender},
FutureExt, StreamExt, FutureExt, StreamExt,
@ -10,7 +10,7 @@ use futures::{
use genawaiter::GeneratorState; use genawaiter::GeneratorState;
use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId}; use libp2p::{core::identity::Keypair, Multiaddr, NetworkBehaviour, PeerId};
use rand::rngs::OsRng; use rand::rngs::OsRng;
use std::{process, sync::Arc}; use std::{process, sync::Arc, time::Duration};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
@ -49,12 +49,17 @@ pub async fn swap(
) -> Result<()> { ) -> Result<()> {
struct Network(Swarm); struct Network(Swarm);
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
// to `ConstantBackoff`.
#[async_trait] #[async_trait]
impl ReceiveTransferProof for Network { impl ReceiveTransferProof for Network {
async fn receive_transfer_proof(&mut self) -> monero::TransferProof { async fn receive_transfer_proof(&mut self) -> monero::TransferProof {
#[derive(Debug)] #[derive(Debug)]
struct UnexpectedMessage; struct UnexpectedMessage;
tracing::debug!("Receiving transfer proof");
let future = self.0.next().shared(); let future = self.0.next().shared();
(|| async { (|| async {
@ -68,10 +73,7 @@ pub async fn swap(
Result::<_, backoff::Error<UnexpectedMessage>>::Ok(proof) Result::<_, backoff::Error<UnexpectedMessage>>::Ok(proof)
}) })
.retry(ExponentialBackoff { .retry(ConstantBackoff::new(Duration::from_secs(1)))
max_elapsed_time: None,
..Default::default()
})
.await .await
.expect("transient errors to be retried") .expect("transient errors to be retried")
} }

View File

@ -10,8 +10,6 @@ pub mod storage;
#[cfg(feature = "tor")] #[cfg(feature = "tor")]
pub mod tor; pub mod tor;
pub const ONE_BTC: u64 = 100_000_000;
const REFUND_TIMELOCK: u32 = 10; // Relative timelock, this is number of blocks. TODO: What should it be? const REFUND_TIMELOCK: u32 = 10; // Relative timelock, this is number of blocks. TODO: What should it be?
const PUNISH_TIMELOCK: u32 = 10; // FIXME: What should this be? const PUNISH_TIMELOCK: u32 = 10; // FIXME: What should this be?

View File

@ -75,6 +75,9 @@ impl CreateWalletForOutput for Wallet {
} }
} }
// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed
// to `ConstantBackoff`.
#[async_trait] #[async_trait]
impl WatchForTransfer for Wallet { impl WatchForTransfer for Wallet {
async fn watch_for_transfer( async fn watch_for_transfer(

View File

@ -6,6 +6,7 @@ use libp2p::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt::Debug, io, marker::PhantomData}; use std::{fmt::Debug, io, marker::PhantomData};
use tracing::debug;
use crate::SwapAmounts; use crate::SwapAmounts;
use xmr_btc::{alice, bob, monero}; use xmr_btc::{alice, bob, monero};

View File

@ -24,10 +24,12 @@ async fn swap() {
let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
let _ = bitcoind.init(5).await; let _ = bitcoind.init(5).await;
let btc = bitcoin::Amount::ONE_BTC; let btc = bitcoin::Amount::from_sat(1_000_000);
let _btc_alice = bitcoin::Amount::ZERO; let btc_alice = bitcoin::Amount::ZERO;
let btc_bob = btc * 10; 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 = 1_000_000_000_000;
let xmr_alice = xmr * 10; let xmr_alice = xmr * 10;
let xmr_bob = 0; let xmr_bob = 0;
@ -54,8 +56,8 @@ async fn swap() {
let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client())); let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client()));
let alice_swap = alice::swap( let alice_swap = alice::swap(
alice_btc_wallet, alice_btc_wallet.clone(),
alice_xmr_wallet, alice_xmr_wallet.clone(),
alice_multiaddr.clone(), alice_multiaddr.clone(),
None, None,
); );
@ -63,15 +65,33 @@ async fn swap() {
let (cmd_tx, mut _cmd_rx) = mpsc::channel(1); let (cmd_tx, mut _cmd_rx) = mpsc::channel(1);
let (mut rsp_tx, rsp_rx) = mpsc::channel(1); let (mut rsp_tx, rsp_rx) = mpsc::channel(1);
let bob_swap = bob::swap( let bob_swap = bob::swap(
bob_btc_wallet, bob_btc_wallet.clone(),
bob_xmr_wallet, bob_xmr_wallet.clone(),
btc.as_sat(), btc.as_sat(),
alice_multiaddr, alice_multiaddr,
cmd_tx, cmd_tx,
rsp_rx, rsp_rx,
); );
// automate the verification step by accepting any amounts sent over by Alice
rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap(); rsp_tx.try_send(swap::Rsp::VerifiedAmounts).unwrap();
try_join(alice_swap, bob_swap).await.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();
monero.wait_for_bob_wallet_block_height().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(xmr_btc::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);
} }