383: Improve resilience of balance assertions r=thomaseizinger a=thomaseizinger

The final commit is the relevant patch!

It sits on top of several refactoring commits that happened while I was debugging why things didn't work as expected. Turned out to be reasonably useful so I just left them in :)

385: Bump anyhow from 1.0.39 to 1.0.40 r=thomaseizinger a=dependabot[bot]

Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.39 to 1.0.40.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/dtolnay/anyhow/releases">anyhow's releases</a>.</em></p>
<blockquote>
<h2>1.0.40</h2>
<ul>
<li>Reduce memory footprint of errors on Rust versions 1.51+ (<a href="https://github-redirect.dependabot.com/dtolnay/anyhow/issues/145">#145</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="704622f25d"><code>704622f</code></a> Release 1.0.40</li>
<li><a href="64ac0c00a9"><code>64ac0c0</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/dtolnay/anyhow/issues/145">#145</a> from dtolnay/addrof</li>
<li><a href="ef082670ea"><code>ef08267</code></a> Eliminate functionally duplicate vtable methods on rustc 1.51+</li>
<li><a href="1295b1fef9"><code>1295b1f</code></a> Add additional builds on 1.50 and 1.51 validating addr_of codepath</li>
<li><a href="be89adf403"><code>be89adf</code></a> Detect whether ptr::addr_of is supported by current compiler</li>
<li><a href="ac64560c42"><code>ac64560</code></a> Switch object_ref return from real ref to Ref ptr</li>
<li><a href="2987c9b59e"><code>2987c9b</code></a> Ignore redundant_else pedantic clippy lint</li>
<li><a href="827bb9d4c6"><code>827bb9d</code></a> Catch some warnings in addr_of-related codepaths</li>
<li><a href="ce0041866d"><code>ce00418</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/dtolnay/anyhow/issues/144">#144</a> from dtolnay/ptr</li>
<li><a href="3c32aa7dcd"><code>3c32aa7</code></a> Relax Sized bound on Own, Ref, Mut ptrs</li>
<li>Additional commits viewable in <a href="https://github.com/dtolnay/anyhow/compare/1.0.39...1.0.40">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=anyhow&package-manager=cargo&previous-version=1.0.39&new-version=1.0.40)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

386: Bump hyper from 0.14.4 to 0.14.5 r=thomaseizinger a=dependabot[bot]

Bumps [hyper](https://github.com/hyperium/hyper) from 0.14.4 to 0.14.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/hyperium/hyper/releases">hyper's releases</a>.</em></p>
<blockquote>
<h2>v0.14.5</h2>
<h2>Bug Fixes</h2>
<ul>
<li><strong>client:</strong> omit default port from automatic Host headers (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2441">#2441</a>) (<a href="0b11eee9bd">0b11eee9</a>)</li>
<li><strong>headers:</strong> Support multiple Content-Length values on same line (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2471">#2471</a>) (<a href="48fdaf1606">48fdaf16</a>, closes <a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2470">#2470</a>)</li>
<li><strong>server:</strong> skip automatic Content-Length headers when not allowed (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2216">#2216</a>) (<a href="8cbf9527df">8cbf9527</a>, closes <a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2215">#2215</a>)</li>
</ul>
<h2>Features</h2>
<ul>
<li><strong>client:</strong> allow HTTP/0.9 responses behind a flag (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2473">#2473</a>) (<a href="68d4e4a3db">68d4e4a3</a>, closes <a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2468">#2468</a>)</li>
<li><strong>server:</strong> add <code>AddrIncoming::from_listener</code> constructor (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2439">#2439</a>) (<a href="4c946af49c">4c946af4</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/hyperium/hyper/blob/master/CHANGELOG.md">hyper's changelog</a>.</em></p>
<blockquote>
<h3>v0.14.5 (2021-03-26)</h3>
<h4>Bug Fixes</h4>
<ul>
<li><strong>client:</strong> omit default port from automatic Host headers (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2441">#2441</a>) (<a href="0b11eee9bd">0b11eee9</a>)</li>
<li><strong>headers:</strong> Support multiple Content-Length values on same line (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2471">#2471</a>) (<a href="48fdaf1606">48fdaf16</a>, closes <a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2470">#2470</a>)</li>
<li><strong>server:</strong> skip automatic Content-Length headers when not allowed (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2216">#2216</a>) (<a href="8cbf9527df">8cbf9527</a>, closes <a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2215">#2215</a>)</li>
</ul>
<h4>Features</h4>
<ul>
<li><strong>client:</strong> allow HTTP/0.9 responses behind a flag (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2473">#2473</a>) (<a href="68d4e4a3db">68d4e4a3</a>, closes <a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2468">#2468</a>)</li>
<li><strong>server:</strong> add <code>AddrIncoming::from_listener</code> constructor (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2439">#2439</a>) (<a href="4c946af49c">4c946af4</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="98e7e0bd15"><code>98e7e0b</code></a> v0.14.5</li>
<li><a href="895e4cf3fb"><code>895e4cf</code></a> refactor(ffi): return null ptr instead of aborting in C API (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2478">#2478</a>)</li>
<li><a href="68d4e4a3db"><code>68d4e4a</code></a> feat(client): allow HTTP/0.9 responses behind a flag (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2473">#2473</a>)</li>
<li><a href="51ed71b0a6"><code>51ed71b</code></a> docs(client): use Method::POST to match the example in <a href="https://hyper.rs/guide">https://hyper.rs/guide</a>...</li>
<li><a href="41f99578a5"><code>41f9957</code></a> refactor(dependencies): update to socket2 v0.4.0 (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2472">#2472</a>)</li>
<li><a href="48fdaf1606"><code>48fdaf1</code></a> fix(headers): Support multiple Content-Length values on same line (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2471">#2471</a>)</li>
<li><a href="eb0e718696"><code>eb0e718</code></a> docs(body): add links to to_bytes and aggregate (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2464">#2464</a>)</li>
<li><a href="297a068454"><code>297a068</code></a> docs(examples): upgrade tokio version (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2456">#2456</a>)</li>
<li><a href="34085afef6"><code>34085af</code></a> docs(examples): use hyper v0.14 and full feature (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2451">#2451</a>)</li>
<li><a href="8cbf9527df"><code>8cbf952</code></a> fix(server): skip automatic Content-Length headers when not allowed (<a href="https://github-redirect.dependabot.com/hyperium/hyper/issues/2216">#2216</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/hyperium/hyper/compare/v0.14.4...v0.14.5">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hyper&package-manager=cargo&previous-version=0.14.4&new-version=0.14.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
pull/361/head
bors[bot] 3 years ago committed by GitHub
commit 6fb495b6ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -127,4 +127,3 @@ jobs:
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} ""
env:
MONERO_ADDITIONAL_SLEEP_PERIOD: 60000
RUST_MIN_STACK: 16777216 # 16 MB. Default is 8MB. This is fine as in tests we start 2 programs: Alice and Bob.

25
Cargo.lock generated

@ -88,9 +88,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.39"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
[[package]]
name = "arrayref"
@ -136,17 +136,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "async-recursion"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.48"
@ -1408,9 +1397,9 @@ checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "hyper"
version = "0.14.4"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7"
checksum = "8bf09f61b52cfcf4c00de50df88ae423d6c02354e385a86341133b5338630ad1"
dependencies = [
"bytes",
"futures-channel",
@ -1423,7 +1412,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project 1.0.5",
"socket2 0.3.19",
"socket2 0.4.0",
"tokio",
"tower-service",
"tracing",
@ -2015,7 +2004,6 @@ dependencies = [
"testcontainers 0.12.0",
"tokio",
"tracing",
"tracing-log",
"tracing-subscriber",
]
@ -3433,7 +3421,6 @@ version = "0.3.0"
dependencies = [
"anyhow",
"async-compression",
"async-recursion",
"async-trait",
"atty",
"backoff",
@ -3484,7 +3471,6 @@ dependencies = [
"toml",
"tracing",
"tracing-futures",
"tracing-log",
"tracing-subscriber",
"url",
"uuid",
@ -3874,6 +3860,7 @@ dependencies = [
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]

@ -14,5 +14,4 @@ spectral = "0.6"
testcontainers = "0.12"
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "time", "macros"] }
tracing = "0.1"
tracing-log = "0.1"
tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter"] }
tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter", "tracing-log"] }

@ -1,15 +1,15 @@
use crate::testutils::init_tracing;
use monero_harness::Monero;
use spectral::prelude::*;
use std::time::Duration;
use testcontainers::clients::Cli;
use tokio::time;
mod testutils;
use tracing_subscriber::util::SubscriberInitExt;
#[tokio::test]
async fn init_miner_and_mine_to_miner_address() {
let _guard = init_tracing();
let _guard = tracing_subscriber::fmt()
.with_env_filter("warn,test=debug,monero_harness=debug,monero_rpc=debug")
.set_default();
let tc = Cli::default();
let (monero, _monerod_container) = Monero::new(&tc, vec![]).await.unwrap();

@ -1,28 +0,0 @@
use tracing::subscriber::DefaultGuard;
use tracing_log::LogTracer;
/// Utility function to initialize logging in the test environment.
/// Note that you have to keep the `_guard` in scope after calling in test:
///
/// ```rust
/// let _guard = init_tracing();
/// ```
pub fn init_tracing() -> DefaultGuard {
// converts all log records into tracing events
// Note: Make sure to initialize without unwrapping, otherwise this causes
// trouble when running multiple tests.
let _ = LogTracer::init();
let global_filter = tracing::Level::WARN;
let test_filter = tracing::Level::DEBUG;
let monero_harness_filter = tracing::Level::DEBUG;
let monero_rpc_filter = tracing::Level::DEBUG;
use tracing_subscriber::util::SubscriberInitExt as _;
tracing_subscriber::fmt()
.with_env_filter(format!(
"{},test={},monero_harness={},monero_rpc={}",
global_filter, test_filter, monero_harness_filter, monero_rpc_filter,
))
.set_default()
}

@ -1,15 +1,15 @@
use crate::testutils::init_tracing;
use monero_harness::{Monero, MoneroWalletRpc};
use spectral::prelude::*;
use std::time::Duration;
use testcontainers::clients::Cli;
use tokio::time::sleep;
mod testutils;
use tracing_subscriber::util::SubscriberInitExt;
#[tokio::test]
async fn fund_transfer_and_check_tx_key() {
let _guard = init_tracing();
let _guard = tracing_subscriber::fmt()
.with_env_filter("warn,test=debug,monero_harness=debug,monero_rpc=debug")
.set_default();
let fund_alice: u64 = 1_000_000_000_000;
let fund_bob = 0;

@ -488,7 +488,7 @@ struct CheckTxKeyParams {
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct CheckTxKey {
pub confirmations: u32,
pub confirmations: u64,
pub received: u64,
}

@ -11,7 +11,6 @@ name = "swap"
[dependencies]
anyhow = "1"
async-compression = { version = "0.3", features = ["bzip2", "tokio"] }
async-recursion = "0.3"
async-trait = "0.1"
atty = "0.2"
backoff = { version = "0.3", features = ["tokio"] }
@ -53,8 +52,7 @@ tokio-util = { version = "0.6", features = ["io"] }
toml = "0.5"
tracing = { version = "0.1", features = ["attributes"] }
tracing-futures = { version = "0.2", features = ["std-future", "futures-03"] }
tracing-log = "0.1"
tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono"] }
tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
url = { version = "2", features = ["serde"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
void = "1"

@ -12,7 +12,7 @@ pub struct Config {
pub bitcoin_punish_timelock: PunishTimelock,
pub bitcoin_network: bitcoin::Network,
pub monero_avg_block_time: Duration,
pub monero_finality_confirmations: u32,
pub monero_finality_confirmations: u64,
pub monero_network: monero::Network,
}

@ -11,7 +11,6 @@ use std::str::FromStr;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::time::Interval;
use tracing::{debug, info};
use url::Url;
#[derive(Debug)]
@ -34,9 +33,9 @@ impl Wallet {
"Unable to create Monero wallet, please ensure that the monero-wallet-rpc is available",
)?;
debug!("Created Monero wallet {}", name);
tracing::debug!("Created Monero wallet {}", name);
} else {
debug!("Opened Monero wallet {}", name);
tracing::debug!("Opened Monero wallet {}", name);
}
Self::connect(client, name, env_config).await
@ -271,7 +270,7 @@ pub struct WatchRequest {
pub public_spend_key: PublicKey,
pub public_view_key: PublicViewKey,
pub transfer_proof: TransferProof,
pub conf_target: u32,
pub conf_target: u64,
pub expected: Amount,
}
@ -280,14 +279,16 @@ async fn wait_for_confirmations<Fut>(
fetch_tx: impl Fn(String) -> Fut,
mut check_interval: Interval,
expected: Amount,
conf_target: u32,
conf_target: u64,
) -> Result<(), InsufficientFunds>
where
Fut: Future<Output = Result<CheckTxKey>>,
{
let mut seen_confirmations = 0u32;
let mut seen_confirmations = 0u64;
while seen_confirmations < conf_target {
check_interval.tick().await; // tick() at the beginning of the loop so every `continue` tick()s as well
let tx = match fetch_tx(txid.clone()).await {
Ok(proof) => proof,
Err(error) => {
@ -310,10 +311,8 @@ where
if tx.confirmations > seen_confirmations {
seen_confirmations = tx.confirmations;
info!(%txid, "Monero lock tx has {} out of {} confirmations", tx.confirmations, conf_target);
tracing::info!(%txid, "Monero lock tx has {} out of {} confirmations", tx.confirmations, conf_target);
}
check_interval.tick().await;
}
Ok(())
@ -323,7 +322,7 @@ where
mod tests {
use super::*;
use monero_rpc::wallet::CheckTxKey;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
#[tokio::test]
@ -364,9 +363,9 @@ mod tests {
#[tokio::test]
async fn visual_log_check() {
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
const MAX_REQUESTS: u32 = 20;
const MAX_REQUESTS: u64 = 20;
let requests = Arc::new(AtomicU32::new(0));
let requests = Arc::new(AtomicU64::new(0));
let result = wait_for_confirmations(
String::from("TXID"),

@ -361,7 +361,7 @@ impl State3 {
pub fn lock_xmr_watch_request(
&self,
transfer_proof: TransferProof,
conf_target: u32,
conf_target: u64,
) -> WatchRequest {
let S_a = monero::PublicKey::from_private_key(&monero::PrivateKey { scalar: self.s_a });

@ -1,7 +1,6 @@
//! Run an XMR/BTC swap in the role of Alice.
//! Alice holds XMR and wishes receive BTC.
use crate::bitcoin::{ExpiredTimelocks, TxRedeem};
use crate::database::Database;
use crate::env::Config;
use crate::monero_ext::ScalarExt;
use crate::protocol::alice;
@ -9,13 +8,10 @@ use crate::protocol::alice::event_loop::EventLoopHandle;
use crate::protocol::alice::AliceState;
use crate::{bitcoin, database, monero};
use anyhow::{bail, Context, Result};
use async_recursion::async_recursion;
use rand::{CryptoRng, RngCore};
use std::sync::Arc;
use tokio::select;
use tokio::time::timeout;
use tracing::{error, info};
use uuid::Uuid;
trait Rng: RngCore + CryptoRng + Send {}
@ -37,41 +33,40 @@ pub async fn run(swap: alice::Swap) -> Result<AliceState> {
#[tracing::instrument(name = "swap", skip(swap,is_target_state), fields(id = %swap.swap_id))]
pub async fn run_until(
swap: alice::Swap,
mut swap: alice::Swap,
is_target_state: fn(&AliceState) -> bool,
) -> Result<AliceState> {
run_until_internal(
swap.state,
is_target_state,
swap.event_loop_handle,
swap.bitcoin_wallet,
swap.monero_wallet,
swap.env_config,
swap.swap_id,
swap.db,
)
.await
let mut current_state = swap.state;
while !is_target_state(&current_state) {
current_state = next_state(
current_state,
&mut swap.event_loop_handle,
swap.bitcoin_wallet.as_ref(),
swap.monero_wallet.as_ref(),
&swap.env_config,
)
.await?;
let db_state = (&current_state).into();
swap.db
.insert_latest_state(swap.swap_id, database::Swap::Alice(db_state))
.await?;
}
Ok(current_state)
}
// State machine driver for swap execution
#[async_recursion]
#[allow(clippy::too_many_arguments)]
async fn run_until_internal(
async fn next_state(
state: AliceState,
is_target_state: fn(&AliceState) -> bool,
mut event_loop_handle: EventLoopHandle,
bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>,
env_config: Config,
swap_id: Uuid,
db: Arc<Database>,
event_loop_handle: &mut EventLoopHandle,
bitcoin_wallet: &bitcoin::Wallet,
monero_wallet: &monero::Wallet,
env_config: &Config,
) -> Result<AliceState> {
info!("Current state: {}", state);
if is_target_state(&state) {
return Ok(state);
}
let new_state = match state {
Ok(match state {
AliceState::Started { state3 } => {
timeout(
env_config.bob_time_to_act,
@ -121,10 +116,10 @@ async fn run_until_internal(
AliceState::XmrLocked {
state3,
monero_wallet_restore_blockheight,
} => match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
} => match state3.expired_timelocks(bitcoin_wallet).await? {
ExpiredTimelocks::None => {
select! {
_ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => {
_ = state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet) => {
AliceState::CancelTimelockExpired {
state3,
monero_wallet_restore_blockheight,
@ -150,7 +145,7 @@ async fn run_until_internal(
state3,
encrypted_signature,
monero_wallet_restore_blockheight,
} => match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? {
} => match state3.expired_timelocks(bitcoin_wallet).await? {
ExpiredTimelocks::None => {
match TxRedeem::new(&state3.tx_lock, &state3.redeem_address).complete(
*encrypted_signature,
@ -168,7 +163,7 @@ async fn run_until_internal(
Err(e) => {
error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e);
state3
.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref())
.wait_for_cancel_timelock_to_expire(bitcoin_wallet)
.await?;
AliceState::CancelTimelockExpired {
@ -180,7 +175,7 @@ async fn run_until_internal(
Err(e) => {
error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e);
state3
.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref())
.wait_for_cancel_timelock_to_expire(bitcoin_wallet)
.await?;
AliceState::CancelTimelockExpired {
@ -336,20 +331,5 @@ async fn run_until_internal(
AliceState::BtcRedeemed => AliceState::BtcRedeemed,
AliceState::BtcPunished => AliceState::BtcPunished,
AliceState::SafelyAborted => AliceState::SafelyAborted,
};
let db_state = (&new_state).into();
db.insert_latest_state(swap_id, database::Swap::Alice(db_state))
.await?;
run_until_internal(
new_state,
is_target_state,
event_loop_handle,
bitcoin_wallet,
monero_wallet,
env_config,
swap_id,
db,
)
.await
})
}

@ -83,7 +83,7 @@ pub struct State0 {
cancel_timelock: CancelTimelock,
punish_timelock: PunishTimelock,
refund_address: bitcoin::Address,
min_monero_confirmations: u32,
min_monero_confirmations: u64,
}
impl State0 {
@ -94,7 +94,7 @@ impl State0 {
cancel_timelock: CancelTimelock,
punish_timelock: PunishTimelock,
refund_address: bitcoin::Address,
min_monero_confirmations: u32,
min_monero_confirmations: u64,
) -> Self {
let b = bitcoin::SecretKey::new_random(rng);
@ -185,7 +185,7 @@ pub struct State1 {
redeem_address: bitcoin::Address,
punish_address: bitcoin::Address,
tx_lock: bitcoin::TxLock,
min_monero_confirmations: u32,
min_monero_confirmations: u64,
}
impl State1 {
@ -245,7 +245,7 @@ pub struct State2 {
tx_lock: bitcoin::TxLock,
tx_cancel_sig_a: Signature,
tx_refund_encsig: bitcoin::EncryptedSignature,
min_monero_confirmations: u32,
min_monero_confirmations: u64,
}
impl State2 {
@ -302,7 +302,7 @@ pub struct State3 {
tx_lock: bitcoin::TxLock,
tx_cancel_sig_a: Signature,
tx_refund_encsig: bitcoin::EncryptedSignature,
min_monero_confirmations: u32,
min_monero_confirmations: u64,
}
impl State3 {
@ -485,23 +485,17 @@ pub struct State5 {
s_b: monero::Scalar,
v: monero::PrivateViewKey,
tx_lock: bitcoin::TxLock,
monero_wallet_restore_blockheight: BlockHeight,
pub monero_wallet_restore_blockheight: BlockHeight,
}
impl State5 {
pub async fn claim_xmr(&self, monero_wallet: &monero::Wallet) -> Result<()> {
pub fn xmr_keys(&self) -> (monero::PrivateKey, monero::PrivateViewKey) {
let s_b = monero::PrivateKey { scalar: self.s_b };
let s = self.s_a + s_b;
// NOTE: This actually generates and opens a new wallet, closing the currently
// open one.
monero_wallet
.create_from_and_load(s, self.v, self.monero_wallet_restore_blockheight)
.await?;
Ok(())
(s, self.v)
}
pub fn tx_lock_id(&self) -> bitcoin::Txid {
self.tx_lock.txid()
}

@ -1,17 +1,14 @@
use crate::bitcoin::ExpiredTimelocks;
use crate::database::{Database, Swap};
use crate::database::Swap;
use crate::env::Config;
use crate::protocol::bob;
use crate::protocol::bob::event_loop::EventLoopHandle;
use crate::protocol::bob::state::*;
use crate::{bitcoin, monero};
use anyhow::{bail, Context, Result};
use async_recursion::async_recursion;
use rand::rngs::OsRng;
use std::sync::Arc;
use tokio::select;
use tracing::trace;
use uuid::Uuid;
pub fn is_complete(state: &BobState) -> bool {
matches!(
@ -29,49 +26,48 @@ pub async fn run(swap: bob::Swap) -> Result<BobState> {
}
pub async fn run_until(
swap: bob::Swap,
mut swap: bob::Swap,
is_target_state: fn(&BobState) -> bool,
) -> Result<BobState> {
run_until_internal(
swap.state,
is_target_state,
swap.event_loop_handle,
swap.db,
swap.bitcoin_wallet,
swap.monero_wallet,
swap.swap_id,
swap.env_config,
swap.receive_monero_address,
)
.await
let mut current_state = swap.state;
while !is_target_state(&current_state) {
current_state = next_state(
current_state,
&mut swap.event_loop_handle,
swap.bitcoin_wallet.as_ref(),
swap.monero_wallet.as_ref(),
&swap.env_config,
swap.receive_monero_address,
)
.await?;
let db_state = current_state.clone().into();
swap.db
.insert_latest_state(swap.swap_id, Swap::Bob(db_state))
.await?;
}
Ok(current_state)
}
// State machine driver for swap execution
#[allow(clippy::too_many_arguments)]
#[async_recursion]
async fn run_until_internal(
async fn next_state(
state: BobState,
is_target_state: fn(&BobState) -> bool,
mut event_loop_handle: EventLoopHandle,
db: Database,
bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>,
swap_id: Uuid,
env_config: Config,
event_loop_handle: &mut EventLoopHandle,
bitcoin_wallet: &bitcoin::Wallet,
monero_wallet: &monero::Wallet,
env_config: &Config,
receive_monero_address: monero::Address,
) -> Result<BobState> {
trace!("Current state: {}", state);
if is_target_state(&state) {
return Ok(state);
}
let new_state = match state {
Ok(match state {
BobState::Started { btc_amount } => {
let bitcoin_refund_address = bitcoin_wallet.new_address().await?;
let state2 = request_price_and_setup(
btc_amount,
&mut event_loop_handle,
event_loop_handle,
env_config,
bitcoin_refund_address,
)
@ -93,10 +89,10 @@ async fn run_until_internal(
// Bob has locked Btc
// Watch for Alice to Lock Xmr or for cancel timelock to elapse
BobState::BtcLocked(state3) => {
if let ExpiredTimelocks::None = state3.current_epoch(bitcoin_wallet.as_ref()).await? {
if let ExpiredTimelocks::None = state3.current_epoch(bitcoin_wallet).await? {
let transfer_proof_watcher = event_loop_handle.recv_transfer_proof();
let cancel_timelock_expires =
state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());
state3.wait_for_cancel_timelock_to_expire(bitcoin_wallet);
// Record the current monero wallet block height so we don't have to scan from
// block 0 once we create the redeem wallet.
@ -133,7 +129,7 @@ async fn run_until_internal(
lock_transfer_proof,
monero_wallet_restore_blockheight,
} => {
if let ExpiredTimelocks::None = state.current_epoch(bitcoin_wallet.as_ref()).await? {
if let ExpiredTimelocks::None = state.current_epoch(bitcoin_wallet).await? {
let watch_request = state.lock_xmr_watch_request(lock_transfer_proof);
select! {
@ -142,13 +138,13 @@ async fn run_until_internal(
Ok(()) => BobState::XmrLocked(state.xmr_locked(monero_wallet_restore_blockheight)),
Err(e) => {
tracing::warn!("Waiting for refund because insufficient Monero have been locked! {}", e);
state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()).await?;
state.wait_for_cancel_timelock_to_expire(bitcoin_wallet).await?;
BobState::CancelTimelockExpired(state.cancel())
},
}
}
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => {
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet) => {
BobState::CancelTimelockExpired(state.cancel())
}
}
@ -157,7 +153,7 @@ async fn run_until_internal(
}
}
BobState::XmrLocked(state) => {
if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? {
if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet).await? {
// Alice has locked Xmr
// Bob sends Alice his key
@ -165,7 +161,7 @@ async fn run_until_internal(
_ = event_loop_handle.send_encrypted_signature(state.tx_redeem_encsig()) => {
BobState::EncSigSent(state)
},
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => {
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet) => {
BobState::CancelTimelockExpired(state.cancel())
}
}
@ -174,12 +170,12 @@ async fn run_until_internal(
}
}
BobState::EncSigSent(state) => {
if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet.as_ref()).await? {
if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet).await? {
select! {
state5 = state.watch_for_redeem_btc(bitcoin_wallet.as_ref()) => {
state5 = state.watch_for_redeem_btc(bitcoin_wallet) => {
BobState::BtcRedeemed(state5?)
},
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) => {
_ = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet) => {
BobState::CancelTimelockExpired(state.cancel())
}
}
@ -188,8 +184,13 @@ async fn run_until_internal(
}
}
BobState::BtcRedeemed(state) => {
// Bob redeems XMR using revealed s_a
state.claim_xmr(monero_wallet.as_ref()).await?;
let (spend_key, view_key) = state.xmr_keys();
// NOTE: This actually generates and opens a new wallet, closing the currently
// open one.
monero_wallet
.create_from_and_load(spend_key, view_key, state.monero_wallet_restore_blockheight)
.await?;
// Ensure that the generated wallet is synced so we have a proper balance
monero_wallet.refresh().await?;
@ -205,26 +206,22 @@ async fn run_until_internal(
}
}
BobState::CancelTimelockExpired(state4) => {
if state4
.check_for_tx_cancel(bitcoin_wallet.as_ref())
.await
.is_err()
{
state4.submit_tx_cancel(bitcoin_wallet.as_ref()).await?;
if state4.check_for_tx_cancel(bitcoin_wallet).await.is_err() {
state4.submit_tx_cancel(bitcoin_wallet).await?;
}
BobState::BtcCancelled(state4)
}
BobState::BtcCancelled(state) => {
// Bob has cancelled the swap
match state.expired_timelock(bitcoin_wallet.as_ref()).await? {
match state.expired_timelock(bitcoin_wallet).await? {
ExpiredTimelocks::None => {
bail!(
"Internal error: canceled state reached before cancel timelock was expired"
);
}
ExpiredTimelocks::Cancel => {
state.refund_btc(bitcoin_wallet.as_ref()).await?;
state.refund_btc(bitcoin_wallet).await?;
BobState::BtcRefunded(state)
}
ExpiredTimelocks::Punish => BobState::BtcPunished {
@ -236,28 +233,13 @@ async fn run_until_internal(
BobState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id },
BobState::SafelyAborted => BobState::SafelyAborted,
BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id },
};
let db_state = new_state.clone().into();
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
run_until_internal(
new_state,
is_target_state,
event_loop_handle,
db,
bitcoin_wallet,
monero_wallet,
swap_id,
env_config,
receive_monero_address,
)
.await
})
}
pub async fn request_price_and_setup(
btc: bitcoin::Amount,
event_loop_handle: &mut EventLoopHandle,
env_config: Config,
env_config: &Config,
bitcoin_refund_address: bitcoin::Address,
) -> Result<bob::state::State2> {
let xmr = event_loop_handle.request_spot_price(btc).await?;

@ -1,5 +1,4 @@
use anyhow::Result;
use tracing_log::LogTracer;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::FmtSubscriber;
@ -8,9 +7,6 @@ pub fn init_tracing(level: LevelFilter) -> Result<()> {
return Ok(());
}
// We want upstream library log messages, just only at Info level.
LogTracer::init_with_filter(tracing_log::log::LevelFilter::Info)?;
let is_terminal = atty::is(atty::Stream::Stderr);
let builder = FmtSubscriber::builder()

@ -2,13 +2,16 @@ mod bitcoind;
mod electrs;
use crate::testutils;
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
use bitcoin_harness::{BitcoindRpcApi, Client};
use futures::Future;
use get_port::get_port;
use libp2p::core::Multiaddr;
use libp2p::{PeerId, Swarm};
use monero_harness::{image, Monero};
use std::cmp::Ordering;
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
@ -28,8 +31,7 @@ use testcontainers::{Container, Docker, RunArgs};
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use tokio::time::interval;
use tracing::dispatcher::DefaultGuard;
use tracing_log::LogTracer;
use tracing_subscriber::util::SubscriberInitExt;
use url::Url;
use uuid::Uuid;
@ -148,108 +150,83 @@ impl TestContext {
pub async fn assert_alice_redeemed(&mut self, state: AliceState) {
assert!(matches!(state, AliceState::BtcRedeemed));
self.alice_bitcoin_wallet.sync().await.unwrap();
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.alice_starting_balances.btc + self.btc_amount
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
);
assert_eventual_balance(
self.alice_bitcoin_wallet.as_ref(),
Ordering::Equal,
self.alice_redeemed_btc_balance(),
)
.await
.unwrap();
let xmr_balance_after_swap = self
.alice_monero_wallet
.as_ref()
.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
);
assert_eventual_balance(
self.alice_monero_wallet.as_ref(),
Ordering::Less,
self.alice_redeemed_xmr_balance(),
)
.await
.unwrap();
}
pub async fn assert_alice_refunded(&mut self, state: AliceState) {
assert!(matches!(state, AliceState::XmrRefunded));
self.alice_bitcoin_wallet.sync().await.unwrap();
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc);
// Ensure that Alice's balance is refreshed as we use a newly created wallet
self.alice_monero_wallet.as_ref().refresh().await.unwrap();
let xmr_balance_after_swap = self
.alice_monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert_eventual_balance(
self.alice_bitcoin_wallet.as_ref(),
Ordering::Equal,
self.alice_refunded_btc_balance(),
)
.await
.unwrap();
// Alice pays fees - comparison does not take exact lock fee into account
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
);
assert_eventual_balance(
self.alice_monero_wallet.as_ref(),
Ordering::Greater,
self.alice_refunded_xmr_balance(),
)
.await
.unwrap();
}
pub async fn assert_alice_punished(&self, state: AliceState) {
assert!(matches!(state, AliceState::BtcPunished));
self.alice_bitcoin_wallet.sync().await.unwrap();
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.alice_starting_balances.btc + self.btc_amount
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
);
assert_eventual_balance(
self.alice_bitcoin_wallet.as_ref(),
Ordering::Equal,
self.alice_punished_btc_balance(),
)
.await
.unwrap();
let xmr_balance_after_swap = self
.alice_monero_wallet
.as_ref()
.get_balance()
.await
.unwrap();
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.xmr_amount);
assert_eventual_balance(
self.alice_monero_wallet.as_ref(),
Ordering::Less,
self.alice_punished_xmr_balance(),
)
.await
.unwrap();
}
pub async fn assert_bob_redeemed(&self, state: BobState) {
self.bob_bitcoin_wallet.sync().await.unwrap();
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
tx_lock_id
} else {
panic!("Bob in not in xmr redeemed state: {:?}", state);
};
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.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
);
assert_eventual_balance(
self.bob_bitcoin_wallet.as_ref(),
Ordering::Equal,
self.bob_redeemed_btc_balance(state).await.unwrap(),
)
.await
.unwrap();
// unload the generated wallet by opening the original wallet
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
self.bob_monero_wallet.as_ref().refresh().await.unwrap();
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
assert!(xmr_balance_after_swap > self.bob_starting_balances.xmr);
assert_eventual_balance(
self.bob_monero_wallet.as_ref(),
Ordering::Greater,
self.bob_redeemed_xmr_balance(),
)
.await
.unwrap();
}
pub async fn assert_bob_refunded(&self, state: BobState) {
@ -266,7 +243,7 @@ impl TestContext {
.await
.unwrap();
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap();
let alice_submitted_cancel = btc_balance_after_swap
== self.bob_starting_balances.btc
@ -282,33 +259,181 @@ impl TestContext {
// Since we cannot be sure who submitted it we have to assert accordingly
assert!(alice_submitted_cancel || bob_submitted_cancel);
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
assert_eq!(xmr_balance_after_swap, self.bob_starting_balances.xmr);
assert_eventual_balance(
self.bob_monero_wallet.as_ref(),
Ordering::Equal,
self.bob_refunded_xmr_balance(),
)
.await
.unwrap();
}
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 {
tx_lock_id
} 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
.bob_bitcoin_wallet
.transaction_fee(lock_tx_id)
.await
.unwrap();
let lock_tx_bitcoin_fee = self.bob_bitcoin_wallet.transaction_fee(lock_tx_id).await?;
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
assert_eq!(
btc_balance_after_swap,
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
Ok(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.as_ref().get_balance().await.unwrap();
assert_eq!(xmr_balance_after_swap, self.bob_starting_balances.xmr);
Result::<_, anyhow::Error>::Ok(())
};
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
}
}
@ -320,7 +445,10 @@ where
{
let cli = Cli::default();
let _guard = init_tracing();
let _guard = tracing_subscriber::fmt()
.with_env_filter("warn,swap=debug,monero_harness=debug,monero_rpc=info,bitcoin_harness=info,testcontainers=info")
.with_test_writer()
.set_default();
let env_config = C::get_config();
@ -659,24 +787,6 @@ struct Containers<'a> {
electrs: Container<'a, Cli, electrs::Electrs>,
}
/// Utility function to initialize logging in the test environment.
/// Note that you have to keep the `_guard` in scope after calling in test:
///
/// ```rust
/// let _guard = init_tracing();
/// ```
pub fn init_tracing() -> DefaultGuard {
// converts all log records into tracing events
// Note: Make sure to initialize without unwrapping, otherwise this causes
// trouble when running multiple tests.
let _ = LogTracer::init();
use tracing_subscriber::util::SubscriberInitExt as _;
tracing_subscriber::fmt()
.with_env_filter("warn,swap=debug,monero_harness=debug,monero_rpc=info,bitcoin_harness=info,testcontainers=info")
.set_default()
}
pub mod alice_run_until {
use swap::protocol::alice::AliceState;

Loading…
Cancel
Save