mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-06-21 05:14:40 -04:00
Improve resilience of balance assertions
Sometimes, a single sync is not enough because we are still waiting for the block to be mined. We introduce an abstraction that loops on fetching the latest balance with a certain timeout for asserting the balance.
This commit is contained in:
parent
a4c70dfe94
commit
0a82ce989b
1 changed files with 223 additions and 82 deletions
|
@ -2,13 +2,16 @@ mod bitcoind;
|
||||||
mod electrs;
|
mod electrs;
|
||||||
|
|
||||||
use crate::testutils;
|
use crate::testutils;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
use async_trait::async_trait;
|
||||||
use bitcoin_harness::{BitcoindRpcApi, Client};
|
use bitcoin_harness::{BitcoindRpcApi, Client};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use get_port::get_port;
|
use get_port::get_port;
|
||||||
use libp2p::core::Multiaddr;
|
use libp2p::core::Multiaddr;
|
||||||
use libp2p::{PeerId, Swarm};
|
use libp2p::{PeerId, Swarm};
|
||||||
use monero_harness::{image, Monero};
|
use monero_harness::{image, Monero};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fmt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -147,93 +150,83 @@ impl TestContext {
|
||||||
pub async fn assert_alice_redeemed(&mut self, state: AliceState) {
|
pub async fn assert_alice_redeemed(&mut self, state: AliceState) {
|
||||||
assert!(matches!(state, AliceState::BtcRedeemed));
|
assert!(matches!(state, AliceState::BtcRedeemed));
|
||||||
|
|
||||||
self.alice_bitcoin_wallet.sync().await.unwrap();
|
assert_eventual_balance(
|
||||||
|
self.alice_bitcoin_wallet.as_ref(),
|
||||||
|
Ordering::Equal,
|
||||||
|
self.alice_redeemed_btc_balance(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.balance().await.unwrap();
|
assert_eventual_balance(
|
||||||
assert_eq!(
|
self.alice_monero_wallet.as_ref(),
|
||||||
btc_balance_after_swap,
|
Ordering::Less,
|
||||||
self.alice_starting_balances.btc + self.btc_amount
|
self.alice_redeemed_xmr_balance(),
|
||||||
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let xmr_balance_after_swap = self.alice_monero_wallet.get_balance().await.unwrap();
|
|
||||||
assert!(
|
|
||||||
xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.xmr_amount,
|
|
||||||
"{} !< {} - {}",
|
|
||||||
xmr_balance_after_swap,
|
|
||||||
self.alice_starting_balances.xmr,
|
|
||||||
self.xmr_amount
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_alice_refunded(&mut self, state: AliceState) {
|
pub async fn assert_alice_refunded(&mut self, state: AliceState) {
|
||||||
assert!(matches!(state, AliceState::XmrRefunded));
|
assert!(matches!(state, AliceState::XmrRefunded));
|
||||||
|
|
||||||
self.alice_bitcoin_wallet.sync().await.unwrap();
|
assert_eventual_balance(
|
||||||
|
self.alice_bitcoin_wallet.as_ref(),
|
||||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.balance().await.unwrap();
|
Ordering::Equal,
|
||||||
assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc);
|
self.alice_refunded_btc_balance(),
|
||||||
|
)
|
||||||
// Ensure that Alice's balance is refreshed as we use a newly created wallet
|
.await
|
||||||
self.alice_monero_wallet.refresh().await.unwrap();
|
.unwrap();
|
||||||
let xmr_balance_after_swap = self.alice_monero_wallet.get_balance().await.unwrap();
|
|
||||||
|
|
||||||
// Alice pays fees - comparison does not take exact lock fee into account
|
// Alice pays fees - comparison does not take exact lock fee into account
|
||||||
assert!(
|
assert_eventual_balance(
|
||||||
xmr_balance_after_swap > self.alice_starting_balances.xmr - self.xmr_amount,
|
self.alice_monero_wallet.as_ref(),
|
||||||
"{} > {} - {}",
|
Ordering::Greater,
|
||||||
xmr_balance_after_swap,
|
self.alice_refunded_xmr_balance(),
|
||||||
self.alice_starting_balances.xmr,
|
)
|
||||||
self.xmr_amount
|
.await
|
||||||
);
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_alice_punished(&self, state: AliceState) {
|
pub async fn assert_alice_punished(&self, state: AliceState) {
|
||||||
assert!(matches!(state, AliceState::BtcPunished));
|
assert!(matches!(state, AliceState::BtcPunished));
|
||||||
|
|
||||||
self.alice_bitcoin_wallet.sync().await.unwrap();
|
assert_eventual_balance(
|
||||||
|
self.alice_bitcoin_wallet.as_ref(),
|
||||||
|
Ordering::Equal,
|
||||||
|
self.alice_punished_btc_balance(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.balance().await.unwrap();
|
assert_eventual_balance(
|
||||||
assert_eq!(
|
self.alice_monero_wallet.as_ref(),
|
||||||
btc_balance_after_swap,
|
Ordering::Less,
|
||||||
self.alice_starting_balances.btc + self.btc_amount
|
self.alice_punished_xmr_balance(),
|
||||||
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
)
|
||||||
);
|
.await
|
||||||
|
.unwrap();
|
||||||
let xmr_balance_after_swap = self.alice_monero_wallet.get_balance().await.unwrap();
|
|
||||||
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.xmr_amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_bob_redeemed(&self, state: BobState) {
|
pub async fn assert_bob_redeemed(&self, state: BobState) {
|
||||||
self.bob_bitcoin_wallet.sync().await.unwrap();
|
assert_eventual_balance(
|
||||||
|
self.bob_bitcoin_wallet.as_ref(),
|
||||||
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
|
Ordering::Equal,
|
||||||
tx_lock_id
|
self.bob_redeemed_btc_balance(state).await.unwrap(),
|
||||||
} else {
|
)
|
||||||
panic!("Bob in not in xmr redeemed state: {:?}", state);
|
.await
|
||||||
};
|
.unwrap();
|
||||||
|
|
||||||
let lock_tx_bitcoin_fee = self
|
|
||||||
.bob_bitcoin_wallet
|
|
||||||
.transaction_fee(lock_tx_id)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
btc_balance_after_swap,
|
|
||||||
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
|
|
||||||
);
|
|
||||||
|
|
||||||
// unload the generated wallet by opening the original wallet
|
// unload the generated wallet by opening the original wallet
|
||||||
self.bob_monero_wallet.re_open().await.unwrap();
|
self.bob_monero_wallet.re_open().await.unwrap();
|
||||||
// refresh the original wallet to make sure the balance is caught up
|
|
||||||
self.bob_monero_wallet.refresh().await.unwrap();
|
|
||||||
|
|
||||||
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
assert_eventual_balance(
|
||||||
self.bob_monero_wallet.refresh().await.unwrap();
|
self.bob_monero_wallet.as_ref(),
|
||||||
let xmr_balance_after_swap = self.bob_monero_wallet.get_balance().await.unwrap();
|
Ordering::Greater,
|
||||||
assert!(xmr_balance_after_swap > self.bob_starting_balances.xmr);
|
self.bob_redeemed_xmr_balance(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_bob_refunded(&self, state: BobState) {
|
pub async fn assert_bob_refunded(&self, state: BobState) {
|
||||||
|
@ -266,33 +259,181 @@ impl TestContext {
|
||||||
// Since we cannot be sure who submitted it we have to assert accordingly
|
// Since we cannot be sure who submitted it we have to assert accordingly
|
||||||
assert!(alice_submitted_cancel || bob_submitted_cancel);
|
assert!(alice_submitted_cancel || bob_submitted_cancel);
|
||||||
|
|
||||||
let xmr_balance_after_swap = self.bob_monero_wallet.get_balance().await.unwrap();
|
assert_eventual_balance(
|
||||||
assert_eq!(xmr_balance_after_swap, self.bob_starting_balances.xmr);
|
self.bob_monero_wallet.as_ref(),
|
||||||
|
Ordering::Equal,
|
||||||
|
self.bob_refunded_xmr_balance(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_bob_punished(&self, state: BobState) {
|
pub async fn assert_bob_punished(&self, state: BobState) {
|
||||||
self.bob_bitcoin_wallet.sync().await.unwrap();
|
assert_eventual_balance(
|
||||||
|
self.bob_bitcoin_wallet.as_ref(),
|
||||||
|
Ordering::Equal,
|
||||||
|
self.bob_punished_btc_balance(state).await.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eventual_balance(
|
||||||
|
self.bob_monero_wallet.as_ref(),
|
||||||
|
Ordering::Equal,
|
||||||
|
self.bob_punished_xmr_balance(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alice_redeemed_xmr_balance(&self) -> monero::Amount {
|
||||||
|
self.alice_starting_balances.xmr - self.xmr_amount
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alice_redeemed_btc_balance(&self) -> bitcoin::Amount {
|
||||||
|
self.alice_starting_balances.btc + self.btc_amount
|
||||||
|
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bob_redeemed_xmr_balance(&self) -> monero::Amount {
|
||||||
|
self.bob_starting_balances.xmr
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn bob_redeemed_btc_balance(&self, state: BobState) -> Result<bitcoin::Amount> {
|
||||||
|
self.bob_bitcoin_wallet.sync().await?;
|
||||||
|
|
||||||
|
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
|
||||||
|
tx_lock_id
|
||||||
|
} else {
|
||||||
|
bail!("Bob in not in xmr redeemed state: {:?}", state);
|
||||||
|
};
|
||||||
|
|
||||||
|
let lock_tx_bitcoin_fee = self.bob_bitcoin_wallet.transaction_fee(lock_tx_id).await?;
|
||||||
|
|
||||||
|
Ok(self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alice_refunded_xmr_balance(&self) -> monero::Amount {
|
||||||
|
self.alice_starting_balances.xmr - self.xmr_amount
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alice_refunded_btc_balance(&self) -> bitcoin::Amount {
|
||||||
|
self.alice_starting_balances.btc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bob_refunded_xmr_balance(&self) -> monero::Amount {
|
||||||
|
self.bob_starting_balances.xmr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alice_punished_xmr_balance(&self) -> monero::Amount {
|
||||||
|
self.alice_starting_balances.xmr - self.xmr_amount
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alice_punished_btc_balance(&self) -> bitcoin::Amount {
|
||||||
|
self.alice_starting_balances.btc + self.btc_amount
|
||||||
|
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bob_punished_xmr_balance(&self) -> monero::Amount {
|
||||||
|
self.bob_starting_balances.xmr
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn bob_punished_btc_balance(&self, state: BobState) -> Result<bitcoin::Amount> {
|
||||||
|
self.bob_bitcoin_wallet.sync().await?;
|
||||||
|
|
||||||
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
||||||
tx_lock_id
|
tx_lock_id
|
||||||
} else {
|
} else {
|
||||||
panic!("Bob in not in btc punished state: {:?}", state);
|
bail!("Bob in not in btc punished state: {:?}", state);
|
||||||
};
|
};
|
||||||
|
|
||||||
let lock_tx_bitcoin_fee = self
|
let lock_tx_bitcoin_fee = self.bob_bitcoin_wallet.transaction_fee(lock_tx_id).await?;
|
||||||
.bob_bitcoin_wallet
|
|
||||||
.transaction_fee(lock_tx_id)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap();
|
Ok(self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee)
|
||||||
assert_eq!(
|
}
|
||||||
btc_balance_after_swap,
|
}
|
||||||
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
|
|
||||||
|
async fn assert_eventual_balance<A: fmt::Display + PartialOrd>(
|
||||||
|
wallet: &impl Wallet<Amount = A>,
|
||||||
|
ordering: Ordering,
|
||||||
|
expected: A,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ordering_str = match ordering {
|
||||||
|
Ordering::Less => "less than",
|
||||||
|
Ordering::Equal => "equal to",
|
||||||
|
Ordering::Greater => "greater than",
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_balance = wallet.get_balance().await?;
|
||||||
|
|
||||||
|
let assertion = async {
|
||||||
|
while current_balance.partial_cmp(&expected).unwrap() != ordering {
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
|
||||||
|
wallet.refresh().await?;
|
||||||
|
current_balance = wallet.get_balance().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"Assertion successful! Balance {} is {} {}",
|
||||||
|
current_balance,
|
||||||
|
ordering_str,
|
||||||
|
expected
|
||||||
);
|
);
|
||||||
|
|
||||||
let xmr_balance_after_swap = self.bob_monero_wallet.get_balance().await.unwrap();
|
Result::<_, anyhow::Error>::Ok(())
|
||||||
assert_eq!(xmr_balance_after_swap, self.bob_starting_balances.xmr);
|
};
|
||||||
|
|
||||||
|
let timeout = Duration::from_secs(10);
|
||||||
|
|
||||||
|
tokio::time::timeout(timeout, assertion)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Expected balance to be {} {} after at most {}s but was {}",
|
||||||
|
ordering_str,
|
||||||
|
expected,
|
||||||
|
timeout.as_secs(),
|
||||||
|
current_balance
|
||||||
|
)
|
||||||
|
})??;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
trait Wallet {
|
||||||
|
type Amount;
|
||||||
|
|
||||||
|
async fn refresh(&self) -> Result<()>;
|
||||||
|
async fn get_balance(&self) -> Result<Self::Amount>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Wallet for monero::Wallet {
|
||||||
|
type Amount = monero::Amount;
|
||||||
|
|
||||||
|
async fn refresh(&self) -> Result<()> {
|
||||||
|
self.refresh().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_balance(&self) -> Result<Self::Amount> {
|
||||||
|
self.get_balance().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Wallet for bitcoin::Wallet {
|
||||||
|
type Amount = bitcoin::Amount;
|
||||||
|
|
||||||
|
async fn refresh(&self) -> Result<()> {
|
||||||
|
self.sync().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_balance(&self) -> Result<Self::Amount> {
|
||||||
|
self.balance().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue