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>
This commit is contained in:
bors[bot] 2021-03-29 22:31:43 +00:00 committed by GitHub
commit 6fb495b6ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 355 additions and 339 deletions

View File

@ -127,4 +127,3 @@ jobs:
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} "" run: cargo test --package swap --all-features --test ${{ matrix.test_name }} ""
env: env:
MONERO_ADDITIONAL_SLEEP_PERIOD: 60000 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
View File

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

View File

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

View File

@ -1,15 +1,15 @@
use crate::testutils::init_tracing;
use monero_harness::Monero; use monero_harness::Monero;
use spectral::prelude::*; use spectral::prelude::*;
use std::time::Duration; use std::time::Duration;
use testcontainers::clients::Cli; use testcontainers::clients::Cli;
use tokio::time; use tokio::time;
use tracing_subscriber::util::SubscriberInitExt;
mod testutils;
#[tokio::test] #[tokio::test]
async fn init_miner_and_mine_to_miner_address() { 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 tc = Cli::default();
let (monero, _monerod_container) = Monero::new(&tc, vec![]).await.unwrap(); let (monero, _monerod_container) = Monero::new(&tc, vec![]).await.unwrap();

View File

@ -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()
}

View File

@ -1,15 +1,15 @@
use crate::testutils::init_tracing;
use monero_harness::{Monero, MoneroWalletRpc}; use monero_harness::{Monero, MoneroWalletRpc};
use spectral::prelude::*; use spectral::prelude::*;
use std::time::Duration; use std::time::Duration;
use testcontainers::clients::Cli; use testcontainers::clients::Cli;
use tokio::time::sleep; use tokio::time::sleep;
use tracing_subscriber::util::SubscriberInitExt;
mod testutils;
#[tokio::test] #[tokio::test]
async fn fund_transfer_and_check_tx_key() { 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_alice: u64 = 1_000_000_000_000;
let fund_bob = 0; let fund_bob = 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
use anyhow::Result; use anyhow::Result;
use tracing_log::LogTracer;
use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::FmtSubscriber; use tracing_subscriber::FmtSubscriber;
@ -8,9 +7,6 @@ pub fn init_tracing(level: LevelFilter) -> Result<()> {
return Ok(()); 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 is_terminal = atty::is(atty::Stream::Stderr);
let builder = FmtSubscriber::builder() let builder = FmtSubscriber::builder()

View File

@ -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;
@ -28,8 +31,7 @@ use testcontainers::{Container, Docker, RunArgs};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tokio::time::interval; use tokio::time::interval;
use tracing::dispatcher::DefaultGuard; use tracing_subscriber::util::SubscriberInitExt;
use tracing_log::LogTracer;
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
@ -148,108 +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.as_ref().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
.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
);
} }
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.as_ref().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.as_ref().refresh().await.unwrap(); .unwrap();
let xmr_balance_after_swap = self
.alice_monero_wallet
.as_ref()
.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.as_ref().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
.as_ref()
.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.as_ref().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.as_ref().refresh().await.unwrap(); self.bob_monero_wallet.as_ref(),
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().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,7 +243,7 @@ impl TestContext {
.await .await
.unwrap(); .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 let alice_submitted_cancel = btc_balance_after_swap
== self.bob_starting_balances.btc == self.bob_starting_balances.btc
@ -282,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.as_ref().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.as_ref().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.as_ref().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
} }
} }
@ -320,7 +445,10 @@ where
{ {
let cli = Cli::default(); 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(); let env_config = C::get_config();
@ -659,24 +787,6 @@ struct Containers<'a> {
electrs: Container<'a, Cli, electrs::Electrs>, 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 { pub mod alice_run_until {
use swap::protocol::alice::AliceState; use swap::protocol::alice::AliceState;