From 870911bd5837470f7ec518d1ad319815c5e70420 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 09:06:19 +0000 Subject: [PATCH 01/41] build(deps): bump async-trait from 0.1.78 to 0.1.79 Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.78 to 0.1.79. - [Release notes](https://github.com/dtolnay/async-trait/releases) - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.78...0.1.79) --- updated-dependencies: - dependency-name: async-trait dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c58895b..48ca6b71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,9 +145,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.78" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", From a87ffaa63172befb5a174277f5d6380b87c658b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:25:28 +0000 Subject: [PATCH 02/41] build(deps): bump thomaseizinger/keep-a-changelog-new-release Bumps [thomaseizinger/keep-a-changelog-new-release](https://github.com/thomaseizinger/keep-a-changelog-new-release) from 2.0.0 to 3.0.0. - [Release notes](https://github.com/thomaseizinger/keep-a-changelog-new-release/releases) - [Changelog](https://github.com/thomaseizinger/keep-a-changelog-new-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/thomaseizinger/keep-a-changelog-new-release/compare/2.0.0...3.0.0) --- updated-dependencies: - dependency-name: thomaseizinger/keep-a-changelog-new-release dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/draft-new-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 5188be0a..fe857dc9 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -20,7 +20,7 @@ jobs: run: git checkout -b release/${{ github.event.inputs.version }} - name: Update changelog - uses: thomaseizinger/keep-a-changelog-new-release@2.0.0 + uses: thomaseizinger/keep-a-changelog-new-release@3.0.0 with: version: ${{ github.event.inputs.version }} changelogPath: CHANGELOG.md From cf0b9aa601ee64ad94dd05366b224cad1d014803 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:55:30 +0000 Subject: [PATCH 03/41] build(deps): bump pem from 3.0.3 to 3.0.4 Bumps [pem](https://github.com/jcreekmore/pem-rs) from 3.0.3 to 3.0.4. - [Changelog](https://github.com/jcreekmore/pem-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jcreekmore/pem-rs/compare/v3.0.3...v3.0.4) --- updated-dependencies: - dependency-name: pem dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41d94424..c51e72b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2804,11 +2804,11 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pem" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "serde", ] From 0d63087ab905edda2f0830ef58d26bfb901714dc Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Wed, 10 Apr 2024 09:53:32 +0200 Subject: [PATCH 04/41] ci: add cargo check job on stable rust --- .github/workflows/ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14c5a835..e2f869f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,3 +172,16 @@ jobs: - name: Run test ${{ matrix.test_name }} run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture + + check_stable: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4.1.2 + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2.7.3 + + - name: Run cargo check on stable rust + run: cargo check --all-targets From 4767c684674f6be01f7d21f2adc5109ed2db71f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:34:44 +0000 Subject: [PATCH 05/41] build(deps): bump anyhow from 1.0.81 to 1.0.82 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.81 to 1.0.82. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.81...1.0.82) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c51e72b2..26e3d2d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,9 +98,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arrayref" From a449d9b60061d602e377165d174ab3653a88c2bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:48:20 +0000 Subject: [PATCH 06/41] build(deps): bump time from 0.3.34 to 0.3.36 Bumps [time](https://github.com/time-rs/time) from 0.3.34 to 0.3.36. - [Release notes](https://github.com/time-rs/time/releases) - [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) - [Commits](https://github.com/time-rs/time/compare/v0.3.34...v0.3.36) --- updated-dependencies: - dependency-name: time dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26e3d2d5..8619a4d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4308,7 +4308,7 @@ dependencies = [ "tempfile", "testcontainers", "thiserror", - "time 0.3.34", + "time 0.3.36", "tokio", "tokio-socks", "tokio-tar", @@ -4477,9 +4477,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -4500,9 +4500,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -4749,7 +4749,7 @@ checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "thiserror", - "time 0.3.34", + "time 0.3.36", "tracing-subscriber 0.3.18", ] @@ -4850,7 +4850,7 @@ dependencies = [ "serde_json", "sharded-slab", "thread_local", - "time 0.3.34", + "time 0.3.36", "tracing", "tracing-core", "tracing-log 0.2.0", @@ -5095,7 +5095,7 @@ dependencies = [ "cfg-if 1.0.0", "git2", "rustversion", - "time 0.3.34", + "time 0.3.36", ] [[package]] From 23216dea07d09e9e3a26740a77d7291be790fa48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:00:31 +0000 Subject: [PATCH 07/41] build(deps): bump async-trait from 0.1.79 to 0.1.80 Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.79 to 0.1.80. - [Release notes](https://github.com/dtolnay/async-trait/releases) - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.79...0.1.80) --- updated-dependencies: - dependency-name: async-trait dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8619a4d0..1b03e2ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,9 +145,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", From f0f197de20347e4fd682b3a438db4b3799dead6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:24:28 +0000 Subject: [PATCH 08/41] build(deps): bump hyper from 1.2.0 to 1.3.0 Bumps [hyper](https://github.com/hyperium/hyper) from 1.2.0 to 1.3.0. - [Release notes](https://github.com/hyperium/hyper/releases) - [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/hyper/compare/v1.2.0...v1.3.0) --- updated-dependencies: - dependency-name: hyper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- swap/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b03e2ba..c25d2c1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1698,9 +1698,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "9f24ce812868d86d19daa79bf3bf9175bc44ea323391147a5e3abde2a283871b" dependencies = [ "bytes", "http 1.0.0", @@ -4278,7 +4278,7 @@ dependencies = [ "futures", "get-port", "hex", - "hyper 1.2.0", + "hyper 1.3.0", "itertools 0.12.1", "libp2p", "mockito", diff --git a/swap/Cargo.toml b/swap/Cargo.toml index b0c31790..68c3dfdb 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -77,7 +77,7 @@ zip = "0.5" [dev-dependencies] bitcoin-harness = "0.2.2" get-port = "3" -hyper = "1.2" +hyper = "1.3" mockito = "1.3.0" monero-harness = { path = "../monero-harness" } port_check = "0.2" From 76c72cceb2c463be5abbea121fcf1b41b6726d2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:27:29 +0000 Subject: [PATCH 09/41] build(deps): bump serde_json from 1.0.115 to 1.0.116 Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.115 to 1.0.116. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.115...v1.0.116) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b03e2ba..324ddcf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3765,9 +3765,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", From 6ba11b1738e2b900f99e1c95da50277019acea57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:50:10 +0000 Subject: [PATCH 10/41] build(deps): bump hyper from 1.3.0 to 1.3.1 Bumps [hyper](https://github.com/hyperium/hyper) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/hyperium/hyper/releases) - [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/hyper/compare/v1.3.0...v1.3.1) --- updated-dependencies: - dependency-name: hyper dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f02a73b3..b8cc035c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1698,9 +1698,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f24ce812868d86d19daa79bf3bf9175bc44ea323391147a5e3abde2a283871b" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "http 1.0.0", @@ -4278,7 +4278,7 @@ dependencies = [ "futures", "get-port", "hex", - "hyper 1.3.0", + "hyper 1.3.1", "itertools 0.12.1", "libp2p", "mockito", From 1ff67ec94348b004ea9223a29a5a32f6c1f7c24c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:50:26 +0000 Subject: [PATCH 11/41] build(deps): bump serde from 1.0.197 to 1.0.198 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.197 to 1.0.198. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.197...v1.0.198) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f02a73b3..669fa369 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3725,9 +3725,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] @@ -3754,9 +3754,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", From e389027439ce09f0f20779924a73830b73c4c821 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:17:01 +0000 Subject: [PATCH 12/41] build(deps): bump thiserror from 1.0.58 to 1.0.59 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.58 to 1.0.59. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.58...1.0.59) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52b0edd2..b1f3d9f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4438,18 +4438,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", From 59974535258c97949d5cf44f31f6eeafa2566f2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:18:11 +0000 Subject: [PATCH 13/41] build(deps): bump actions/checkout from 4.1.2 to 4.1.3 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.2...v4.1.3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-release-binaries.yml | 2 +- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/create-release.yml | 2 +- .github/workflows/draft-new-release.yml | 2 +- .github/workflows/preview-release.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index c18c3124..44754dbb 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -45,7 +45,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout tagged commit - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 with: ref: ${{ github.event.release.target_commitish }} token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2f869f3..e9f2ca70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 - uses: dtolnay/rust-toolchain@master with: @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 - uses: Swatinem/rust-cache@v2.7.3 @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 - uses: Swatinem/rust-cache@v2.7.3 @@ -76,7 +76,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout sources - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 - uses: Swatinem/rust-cache@v2.7.3 @@ -129,7 +129,7 @@ jobs: tool-cache: false - name: Checkout sources - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 - uses: Swatinem/rust-cache@v2.7.3 @@ -166,7 +166,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 - uses: Swatinem/rust-cache@v2.7.3 @@ -177,7 +177,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.3 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index c62d47eb..a793b43e 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -11,7 +11,7 @@ jobs: if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.2 + - uses: actions/checkout@v4.1.3 - name: Extract version from branch name id: extract-version diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index fe857dc9..0d066fcf 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -12,7 +12,7 @@ jobs: name: "Draft a new release" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.2 + - uses: actions/checkout@v4.1.3 with: token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml index f45ff1b4..1e03123a 100644 --- a/.github/workflows/preview-release.yml +++ b/.github/workflows/preview-release.yml @@ -10,7 +10,7 @@ jobs: name: Create preview release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.2 + - uses: actions/checkout@v4.1.3 - name: Delete 'preview' release uses: larryjoelane/delete-release-action@v1.0.24 From 8a88bde4f5f04bab8b199a835435fc51fdbcc036 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:42:46 +0000 Subject: [PATCH 14/41] build(deps): bump actions/checkout from 4.1.3 to 4.1.4 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.3 to 4.1.4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.3...v4.1.4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-release-binaries.yml | 2 +- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/create-release.yml | 2 +- .github/workflows/draft-new-release.yml | 2 +- .github/workflows/preview-release.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 44754dbb..3ee02709 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -45,7 +45,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout tagged commit - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 with: ref: ${{ github.event.release.target_commitish }} token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9f2ca70..1c740eb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 - uses: dtolnay/rust-toolchain@master with: @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 - uses: Swatinem/rust-cache@v2.7.3 @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 - uses: Swatinem/rust-cache@v2.7.3 @@ -76,7 +76,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout sources - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 - uses: Swatinem/rust-cache@v2.7.3 @@ -129,7 +129,7 @@ jobs: tool-cache: false - name: Checkout sources - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 - uses: Swatinem/rust-cache@v2.7.3 @@ -166,7 +166,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 - uses: Swatinem/rust-cache@v2.7.3 @@ -177,7 +177,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.3 + uses: actions/checkout@v4.1.4 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index a793b43e..28640153 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -11,7 +11,7 @@ jobs: if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.3 + - uses: actions/checkout@v4.1.4 - name: Extract version from branch name id: extract-version diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 0d066fcf..8ff8b6e4 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -12,7 +12,7 @@ jobs: name: "Draft a new release" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.3 + - uses: actions/checkout@v4.1.4 with: token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml index 1e03123a..7d7be25c 100644 --- a/.github/workflows/preview-release.yml +++ b/.github/workflows/preview-release.yml @@ -10,7 +10,7 @@ jobs: name: Create preview release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.3 + - uses: actions/checkout@v4.1.4 - name: Delete 'preview' release uses: larryjoelane/delete-release-action@v1.0.24 From 24466c0234f02fef1e32a06018dbb3e3d97f66f3 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Tue, 30 Apr 2024 10:17:15 +0200 Subject: [PATCH 15/41] ci: fix macos-latest and add macos-12 macos-latest is now aarch64, add macos-12 for x64_64 --- .github/workflows/build-release-binaries.yml | 7 +++++++ .github/workflows/ci.yml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 3ee02709..91c2788f 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -20,6 +20,9 @@ jobs: archive_ext: tar - bin: swap target: x86_64-apple-darwin + os: macos-12 + - bin: swap + target: aarch64-apple-darwin os: macos-latest archive_ext: tar - bin: swap @@ -36,6 +39,10 @@ jobs: archive_ext: tar - bin: asb target: x86_64-apple-darwin + os: macos-12 + archive_ext: tar + - bin: asb + target: aarch64-apple-darwin os: macos-latest archive_ext: tar - bin: asb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c740eb1..8b4f9964 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,8 @@ jobs: - target: armv7-unknown-linux-gnueabihf os: ubuntu-latest - target: x86_64-apple-darwin + os: macos-12 + - target: aarch64-apple-darwin os: macos-latest - target: x86_64-pc-windows-msvc os: windows-latest From 79b4d8efbcdbb160e6e609ec150c8b2f54d7f213 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:28:44 +0000 Subject: [PATCH 16/41] build(deps): bump data-encoding from 2.5.0 to 2.6.0 Bumps [data-encoding](https://github.com/ia0/data-encoding) from 2.5.0 to 2.6.0. - [Commits](https://github.com/ia0/data-encoding/commits) --- updated-dependencies: - dependency-name: data-encoding dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- swap/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1f3d9f9..a205210a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -990,9 +990,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deranged" diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 68c3dfdb..4cf0e05f 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -23,7 +23,7 @@ comfy-table = "7.1" config = { version = "0.14", default-features = false, features = [ "toml" ] } conquer-once = "0.4" curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" } -data-encoding = "2.5" +data-encoding = "2.6" dialoguer = "0.11" digest = "0.10.7" directories-next = "2" From 1aca4462e7347b2cc036c44a792ad2156a2221e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:29:01 +0000 Subject: [PATCH 17/41] build(deps): bump serde from 1.0.198 to 1.0.199 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.198 to 1.0.199. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.198...v1.0.199) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1f3d9f9..019d8825 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3725,9 +3725,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] @@ -3754,9 +3754,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", From ad6b00beecb1d22db8d81d1c7e047c2c0fab1709 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 11:36:56 +0000 Subject: [PATCH 18/41] build(deps): bump base64 from 0.22.0 to 0.22.1 Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.22.0 to 0.22.1. - [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md) - [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.22.0...v0.22.1) --- updated-dependencies: - dependency-name: base64 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bc6c8aa..2f274631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,9 +266,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bdk" @@ -2808,7 +2808,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "serde", ] @@ -4259,7 +4259,7 @@ dependencies = [ "async-trait", "atty", "backoff", - "base64 0.22.0", + "base64 0.22.1", "bdk", "big-bytes", "bitcoin", From cc3abf647c9296a1a8bccfe6bbea64b36bfcb86e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 11:31:44 +0000 Subject: [PATCH 19/41] build(deps): bump serde from 1.0.199 to 1.0.200 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.199 to 1.0.200. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.199...v1.0.200) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f274631..3a22e80e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3725,9 +3725,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -3754,9 +3754,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", From ca5bfed3923aee4b0dc65d71b3a4f759b2a82a9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 11:05:47 +0000 Subject: [PATCH 20/41] build(deps): bump thomaseizinger/create-pull-request from 1.3.1 to 1.4.0 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.3.1 to 1.4.0. - [Release notes](https://github.com/thomaseizinger/create-pull-request/releases) - [Changelog](https://github.com/thomaseizinger/create-pull-request/blob/master/CHANGELOG.md) - [Commits](https://github.com/thomaseizinger/create-pull-request/compare/1.3.1...1.4.0) --- updated-dependencies: - dependency-name: thomaseizinger/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/draft-new-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 8ff8b6e4..1dd94d10 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -58,7 +58,7 @@ jobs: run: git push origin release/${{ github.event.inputs.version }} --force - name: Create pull request - uses: thomaseizinger/create-pull-request@1.3.1 + uses: thomaseizinger/create-pull-request@1.4.0 with: GITHUB_TOKEN: ${{ secrets.BOTTY_GITHUB_TOKEN }} head: release/${{ github.event.inputs.version }} From 7661b19300230d795621960ef951d4113752350e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 11:47:38 +0000 Subject: [PATCH 21/41] build(deps): bump tokio-util from 0.7.10 to 0.7.11 Bumps [tokio-util](https://github.com/tokio-rs/tokio) from 0.7.10 to 0.7.11. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.10...tokio-util-0.7.11) --- updated-dependencies: - dependency-name: tokio-util dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a22e80e..c22abe2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4651,16 +4651,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite 0.2.13", "tokio", - "tracing", ] [[package]] From ed8deeff57828cbf0e9ae2f8df51e769d66f3754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 11:26:23 +0000 Subject: [PATCH 22/41] build(deps): bump actions/checkout from 4.1.4 to 4.1.5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.4...v4.1.5) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-release-binaries.yml | 2 +- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/create-release.yml | 2 +- .github/workflows/draft-new-release.yml | 2 +- .github/workflows/preview-release.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 91c2788f..a0812e98 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -52,7 +52,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout tagged commit - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 with: ref: ${{ github.event.release.target_commitish }} token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b4f9964..5dd8fd3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - uses: dtolnay/rust-toolchain@master with: @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - uses: Swatinem/rust-cache@v2.7.3 @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - uses: Swatinem/rust-cache@v2.7.3 @@ -78,7 +78,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout sources - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - uses: Swatinem/rust-cache@v2.7.3 @@ -131,7 +131,7 @@ jobs: tool-cache: false - name: Checkout sources - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - uses: Swatinem/rust-cache@v2.7.3 @@ -168,7 +168,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - uses: Swatinem/rust-cache@v2.7.3 @@ -179,7 +179,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 28640153..fc79af38 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -11,7 +11,7 @@ jobs: if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.5 - name: Extract version from branch name id: extract-version diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 1dd94d10..9982f130 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -12,7 +12,7 @@ jobs: name: "Draft a new release" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.5 with: token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml index 7d7be25c..d654bfc6 100644 --- a/.github/workflows/preview-release.yml +++ b/.github/workflows/preview-release.yml @@ -10,7 +10,7 @@ jobs: name: Create preview release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.5 - name: Delete 'preview' release uses: larryjoelane/delete-release-action@v1.0.24 From d40059192a2bc1c837f7cf1787041cd881210393 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 11:59:43 +0000 Subject: [PATCH 23/41] build(deps): bump anyhow from 1.0.82 to 1.0.83 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.82 to 1.0.83. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.82...1.0.83) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c22abe2b..e7473808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,9 +98,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "arrayref" From 252c394ef616305fd6b3480160fdd124d7bf4b1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 12:01:20 +0000 Subject: [PATCH 24/41] build(deps): bump thiserror from 1.0.59 to 1.0.60 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.59 to 1.0.60. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.59...1.0.60) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c22abe2b..7812407d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4438,18 +4438,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", From f1d78593342d70e8a975a6d36183747220a3369b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 11:58:50 +0000 Subject: [PATCH 25/41] build(deps): bump serde_json from 1.0.116 to 1.0.117 Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.116 to 1.0.117. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.116...v1.0.117) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7644ba9c..2cd36698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3765,9 +3765,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", From 081bb965eec98b062268ddd1a32e457f2c1fc81b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 11:59:51 +0000 Subject: [PATCH 26/41] build(deps): bump serde from 1.0.200 to 1.0.201 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.200 to 1.0.201. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.200...v1.0.201) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7644ba9c..3161afd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3725,9 +3725,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.200" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] @@ -3754,9 +3754,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", From d9de1b6cf36fe449feddb44ab5420cde9f9b28fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 11:33:15 +0000 Subject: [PATCH 27/41] build(deps): bump serde from 1.0.201 to 1.0.202 Bumps [serde](https://github.com/serde-rs/serde) from 1.0.201 to 1.0.202. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.201...v1.0.202) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e85e364..34fdc50b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3725,9 +3725,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] @@ -3754,9 +3754,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", From 6edcc28b8def128b952644a33f1ded66690fcea4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 11:18:16 +0000 Subject: [PATCH 28/41] build(deps): bump toml from 0.8.12 to 0.8.13 Bumps [toml](https://github.com/toml-rs/toml) from 0.8.12 to 0.8.13. - [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.12...toml-v0.8.13) --- updated-dependencies: - dependency-name: toml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34fdc50b..12c5b916 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -703,7 +703,7 @@ dependencies = [ "nom", "pathdiff", "serde", - "toml 0.8.12", + "toml 0.8.13", ] [[package]] @@ -3776,9 +3776,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -4314,7 +4314,7 @@ dependencies = [ "tokio-tar", "tokio-tungstenite", "tokio-util", - "toml 0.8.12", + "toml 0.8.13", "torut", "tracing", "tracing-appender", @@ -4673,9 +4673,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", @@ -4685,18 +4685,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.8" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12219811e0c1ba077867254e5ad62ee2c9c190b0d957110750ac0cda1ae96cd" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap 2.1.0", "serde", From c8e5768955645dfe275c70745b0854ac4caf103b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 11:12:37 +0000 Subject: [PATCH 29/41] build(deps): bump actions/checkout from 4.1.5 to 4.1.6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.5...v4.1.6) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-release-binaries.yml | 2 +- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/create-release.yml | 2 +- .github/workflows/draft-new-release.yml | 2 +- .github/workflows/preview-release.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index a0812e98..ecfde0c8 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -52,7 +52,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout tagged commit - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 with: ref: ${{ github.event.release.target_commitish }} token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dd8fd3d..2a41dd6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - uses: dtolnay/rust-toolchain@master with: @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - uses: Swatinem/rust-cache@v2.7.3 @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - uses: Swatinem/rust-cache@v2.7.3 @@ -78,7 +78,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout sources - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - uses: Swatinem/rust-cache@v2.7.3 @@ -131,7 +131,7 @@ jobs: tool-cache: false - name: Checkout sources - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - uses: Swatinem/rust-cache@v2.7.3 @@ -168,7 +168,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - uses: Swatinem/rust-cache@v2.7.3 @@ -179,7 +179,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index fc79af38..2e52519a 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -11,7 +11,7 @@ jobs: if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.5 + - uses: actions/checkout@v4.1.6 - name: Extract version from branch name id: extract-version diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 9982f130..48fb2d3a 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -12,7 +12,7 @@ jobs: name: "Draft a new release" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.5 + - uses: actions/checkout@v4.1.6 with: token: ${{ secrets.BOTTY_GITHUB_TOKEN }} diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml index d654bfc6..63ecfe6f 100644 --- a/.github/workflows/preview-release.yml +++ b/.github/workflows/preview-release.yml @@ -10,7 +10,7 @@ jobs: name: Create preview release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.5 + - uses: actions/checkout@v4.1.6 - name: Delete 'preview' release uses: larryjoelane/delete-release-action@v1.0.24 From 0e5241787fbac75ac92bc3ab48617c6ab4109efa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 11:58:52 +0000 Subject: [PATCH 30/41] build(deps): bump itertools from 0.12.1 to 0.13.0 Bumps [itertools](https://github.com/rust-itertools/itertools) from 0.12.1 to 0.13.0. - [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-itertools/itertools/compare/v0.12.1...v0.13.0) --- updated-dependencies: - dependency-name: itertools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 6 +++--- swap/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34fdc50b..ed8efa66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1838,9 +1838,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -4279,7 +4279,7 @@ dependencies = [ "get-port", "hex", "hyper 1.3.1", - "itertools 0.12.1", + "itertools 0.13.0", "libp2p", "mockito", "monero", diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 4cf0e05f..22705715 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -31,7 +31,7 @@ ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = ed25519-dalek = "1" futures = { version = "0.3", default-features = false } hex = "0.4" -itertools = "0.12" +itertools = "0.13" libp2p = { version = "0.42.2", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous", "identify" ] } monero = { version = "0.12", features = [ "serde_support" ] } monero-rpc = { path = "../monero-rpc" } From 8d8c938a940df44daed83bef64a75ec5cca0cd65 Mon Sep 17 00:00:00 2001 From: icy-ux <63157298+icy-ux@users.noreply.github.com> Date: Mon, 20 May 2024 08:28:44 +0000 Subject: [PATCH 31/41] document use of asb export-bitcoin-wallet (#1280) * document use of asb export-bitcoin-wallet Add to the `asb` document @delta1's text on exporting the Bitcoin wallet descriptor and importing the descriptor into Sparrow Wallet. TODO: maybe it would make sense to add the images to Git, instead of linking to files on Github? * bring asb/README.md into compliance with dprint * update bullet point formatting in asb/README.md * run * add images to git --------- Co-authored-by: icyfestive <> Co-authored-by: Byron Hambly --- docs/asb/README.md | 35 ++++++++++++++++++++++++++ docs/asb/enter-master-private-key.png | Bin 0 -> 35936 bytes docs/asb/import-keystore.png | Bin 0 -> 186436 bytes docs/asb/transactions-tab.png | Bin 0 -> 111456 bytes 4 files changed, 35 insertions(+) create mode 100644 docs/asb/enter-master-private-key.png create mode 100644 docs/asb/import-keystore.png create mode 100644 docs/asb/transactions-tab.png diff --git a/docs/asb/README.md b/docs/asb/README.md index 16e0a602..b1acc877 100644 --- a/docs/asb/README.md +++ b/docs/asb/README.md @@ -164,3 +164,38 @@ May 01 01:32:05.018 INFO Tor found. Setting up hidden service. May 01 01:32:07.475 INFO /onion3/z4findrdwtfbpoq64ayjtmxvr52vvxnsynerlenlfkmm52dqxsl4deyd:9939 May 01 01:32:07.476 INFO /onion3/z4findrdwtfbpoq64ayjtmxvr52vvxnsynerlenlfkmm52dqxsl4deyd:9940 ``` + +### Exporting the Bitcoin wallet descriptor + +First use `swap` or `asb` with the `export-bitcoin-wallet` subcommand. + +Output example: + +```json +{"descriptor":"wpkh(tprv8Zgredacted.../84'/1'/0'/0/*)","blockheight":2415616,"label":"asb-testnet"} +``` + +The wallet can theoretically be directly imported into +[bdk-cli](https://bitcoindevkit.org/bdk-cli/installation/) but it is easier to +use Sparrow Wallet. + +Sparrow wallet import works as follows: + +- File -> New wallet -> Give it a name +- Select "New or Imported Software Wallet" +- Click "Enter Private Key" for "Master Private Key (BIP32)" +- Enter the `xprv...` or `tprv...` part of the descriptor (example above is `tprv8Zgredacted...`: + +![image](enter-master-private-key.png) + +- Click "Import" +- Leave the derivation path as `m/84'/0'/0'` and click "Import Keystore" button +- Click "Apply" and then supply password + +![image](import-keystore.png) + +- Click Transactions tab +- ??? +- Profit! + +![image](transactions-tab.png) diff --git a/docs/asb/enter-master-private-key.png b/docs/asb/enter-master-private-key.png new file mode 100644 index 0000000000000000000000000000000000000000..7b69b5502d87a47c220458e3f71cbe4c3d46519b GIT binary patch literal 35936 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

juV7!m>J$0I2f#*5zabva!qa3& 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

+YjgTPXg*kerFDhTDlE%r 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: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%0A;Zu{5ku- zM~?cpzv#pNq#6DHl^Oa!biuzR@PDlie+>HHV~XyNz5aWE3$ye8y(~obG5iP#nTaw> z+1c9z2iS?LtK)#tXU;MIF-;B*n0i)YI_U?FJM#1QuUurX83_ZQy|WXYnksE(Mh#{g z`bsacfrUji>MSK~ZJ9DNGZVd@bM*hIF3@0IPOxHQW=ToO|0<8saI=&@h5G$_*JQT% z;bb;UMn*>BH6b|S?qsbs6gtMQm0YO1?$4=r=g!~xhNJIkoU@6HTczN5k`T3NmOSRcn z%XOA34Hq*5_0FeD;A~n;{8LpM4ErM?YB8XiSJwt||5H(7_D+njolX`irQz$|H94RD z?B47+bo{QZ?bTfZVN<<$etq4uX*&THM+LJj3-yXo|GF}8n49ks@ph0to4wh8tgz#5 zTQyoJAXoNu1_GhX$c?&W2WWh2?o&}##y}3Ce-g=($l=Z{qbCX zj~rsHGkYvf_uo$0ARoodKSoXdU#%_T(5S*88=8;v$eWY>AH#$WpGyAgb-+zG8vR_t z@XvMsdtfU#($T8Q#a(jc( z+c#~at+m}c&eem?v52-e*}y;jklEu9N|pDT<@b?~c}@h9EEe$~@0r4kX?tKf6 z-I4h=7E1v&lv#?{uR1p+2b#0^^0Io;TRJTEVTDx>8$_Rk?!5gE=6i{v!s2rdKEqus zOUR99HVg&Hr{0R(g^}l}!me2#$0DiSN}DJ++oIv!V%psnc;gYad4GV0LR%tEi&lvY z0@+z$Q~|8`4l!;@>2U8w5RD!0-fiRU4X&z z-i1g~CV)}Jo5iBe=vyGt8+eb?;fZEHAgv7xhra0}ZxyXK^o)@eV0TRG$RXY@6`EWX zIh6KO;kxnKuK|715bMjUS07$}99JVqPA=y&;G5hbfwlK>s zXXO0(Kj4QE+Bf7JPg$d1C#@P6413jWe+1Bno^Z!$YGT2{5@%@&Lf?S=+EaD+RW7_a zdUC&;yM5%arStM%o%NMlm$ro9$7ZiV6R;lA2M5@&>m!7Euny3db?t4-ONh8UA{~h* zK5FeJf?Dxr+1Oyf8qz@>=H93F6rA`)_++%JyShOb~FCIUwD`4NNJ4VSIO5 zJ65_KSyto$i0$#zt$9utEADLfaGE7WEfaK`Y z<_Bn`<@Jr{jWnqJX>fl|Wgcd&5K|fGG}uqvz_<+)lx})m!*Dy({!2w0*E`ozIw$tgNctaTaP*%n<@Sh?9k_U-x>$G#g2I0v=dPUFU?Y74*cjmQZE#r?>11ce)p*9L7kI@uqV=nNWU)JafMy&4_ZVv1{jI4b>y)6$?{kSSF zw|ko^{&BJCtYd^maNsA(nz7thhOufI5xbIC6J%n4lk@ZUXa^^C7fZdBr@*T@m|`v^ z@4c}4YO|YqPhMNhC?6kuf^q43+`N;-{;cZ`+`Tb@ZvcOPeH)?RPQ_%Wec5Zv=^x7Q z)jxJ}9Uk@yiZd81(D>L>VpiXC9aA;HOFTZdKf7F5Uyciy^^`v(aC`rGqPxagpXIQi zUy0*co_Bl#S}~IGlYRxEcMj`2uK`bPdTQ)(1jA%e(X3_dxK(u{0Oyt`2X78u0_)?! z^J>4+Pq*bM!zcb9Ia*;z%kuTY?5F0&jBj<&+AmPCkm_je$~LwakEw}&Jf91A=J(f1 z)zQ&h-B!|D^B>RIkk&Sky1cZh5_BdY`Lxq~!Wh4fd)6=Q3A69dW&H zmN}T(Dmh-JS?eEayc)1wG>u>Txh}|a{ru%)*t2$6a`Oq-E#V!jsVSxB*n`JICx++w z8U82xbO2{df9YN-H!)($1rv(QLHM%ftZ_BfV*Ag=S`>2k@P-~ei*>2JvEdG53sLvt z^{a!4xqZRu5>IFPjNz(nXZIDskj!+?mDX9aGU1Z+7kWH74!b}f^6|?oe!BE# zKZCbW$H<7Rx&w+u%{^9t9fOG4^%;@#)?*p5!!*8$O+nu3HQ@~Ai&1M8G$M+-``ykV zxcJ)omGUZ1wiipvy{MRZJV)F*=Zxnw8C0;q=&}>0kU>_EkwJM!kX4IS$9P?l{X(TB zahQtr_BT7(Q?Dt)zU#7X49iA`K&RWhos@7jdl9CsTO35^ckG(n&xbfd%EFdDP;3Up zW`72j_QLitJa?Qeo>>q(xq!Y;3jwsUNWGJ8bS@Ey{nm{IG=uvR8lQM$qoS*}NN=s= zH^%yj^bN2$`ME8s)NjTtJY$rCHnBz<%+PS~aRQ>$zd8>=WBN2FkCf`3YvlUx1M#H~ z)DM4(?)sLyZm%limsO%p7(&*EgT$W_|JtL(Of7dn{3kFbP7fRwWjW@IJmy$RvqExmKP13m}) zeE4XtPI-_uLkqRmtWbI2rDz;J7KedD2TLwfRCFPPdPX+e02Je+jtPXg;ortJsbT^U zMGL@+SQfP3A?dtR4aMU1#n^g&yL4n;{S+J}n*DikS~R0*>+4}Nb*HEO%nr|DuT5lO zBfGk&f|t}{8Pf~8v5h~=w;~O32^}+7Vo|NeA5_%H(J|Ha7R)fFVt(^@LoF}WcG>9w zcDm`ju82k!Rgu?D4+P!O-;+SvDiM-8E#uCS?^is;wksO@WKfDhIp_rFI3(qQT81xc zXu=a>!dtCynPVWhhN>iWszb`_jjKJ@1l`(q%O55S>a5$xRd)%uM+|nKh(5ZE1a1n3 zfcRMAVaw5cs_OwOBNvv>$DgWL60ek>B{Fc`>EZa0{1OuE6dil;F&XRm&? zZY+I)5~Ej-BUI}z%7LU4hb4B3U$*@y;7IeHk&Qok^3pH6R;$qAVRRVmcR-yljQrgT zVf-VbhCMG&%CCvv!gp7Flgt!xCR+(gE#r6cDJ!;)<4+df=t0lL?Cp<-B1^NAfG*nr zR>z|+3|fwyq*Zbi=ExOZF2vd5K<25rqR+;|$Wlj~=x0B*F74izOs#sp4&jvyIs3_j z2R7kZx+1XN8M8b2o*8#;-RV?sNxf97#I6p=`Ec5GUjcXn_hcewQOc621~sLCOU zJ5Fss?!AFa=9eWMuV_hK8$nUQ!ix6d~7&YQ-9{UezEv-n-?z4|w8vzVGIPrBSA|(~F9M@QCJSQH zv^b_|Qua?V*JW!F0q@o;;W8GVMvwlyCt z$KJW~vFv~$vU{c5+uV|IYHK2LJ}E@ZfKUbc-4CfFCty9x=3$90{M{U z{Xp7OWrS^Y{4MPaiq+~8ZCrusMa%Y-+55mfU^nVu|S6K_s~F>9mkB<3r7-p zAloAfL0`v1`S}p%;mOkx>)g!~_4aB;4PLUO^&V;(<^u0+Zhx+lAMVdm`pT#lkrVc3 z3DOGAZJXqSS6pjz%hwt+!)^(7YC5{k9y~k>@hJR40yh(G?qBi2+)Vg*n7x6bN!a%v zK4`rt{KRrgSbkof=rf5n$Na8l;8%|{l8w*(o!4Z{o{*S!+(@qhsl)(2Ooqu57S?og zuRbBr!3R=OG#lIPF;nDnEj zfw#B@!hILdCi}#ptjpWIU0-drVf=}~ z+4F}nTq>p5z7%7R!XBZXo#b$M{(6k@4oY*HdX&3PicOw2|8yMH3j}j8t7lVanJ_!~VqpBihp~Mi z$_AteqOK25cxgDj6{um-!e2kfD6W=En9Z^uH^4lj>8^TUy&f}jbedjWp`pzx=!4XJ z2T{tH>8@Lw7(c97*cVn-zcwCl?l9NI;u+es3z6q+|HX|(dM*#hYV})k&i6%0Z6gGG z@SwnuW0@-9z02#kEE|V+KBZ?WUNkqD)Yxg04(l4Xhdm#MG8n-e$;kxnZVt?g9-*Q1is|*B>4T-bHT#N1U zO#7cyZBM1tfUm@akYb)|@Gs#GS>9WT&ZOYX)y`OHC z{aUxt&w!Y2I$tdA#h9)3s31=zrsl)m8rt7<(&RP)3kD%w}kplb>Am{ z<^-_lv$p?ox4!@KMrY^~@ig8(b6PH?o~uxRRuM9Q)HSl)LkYCzL2XitGfvclC<~8& zeyZ)M&X>1y1UzmlhVYRFRKnL^^ok!3aAYN2o`29d)#&1+t^Z}u1(ebRPY50jPE;y3 znCbD>PkSnXqhwpj%^Ex(ZJ)Z)4jgs%$*>NG*LI6@q%2X<9y+aBSMEmYiw2i#Bgttw zD`{V-dS^3g4j2484fB}HOvN>{#9k&$C(|_nh{ng7cIY;`y$R0{;k?YViL{0=%TbL? z`25%cIc1|CskQzS3t+a{a>(-=g?_#?cV~j|`*1&+qrodGV2(Jl!90KIXs%N>iY}YH z73QYLVp*l=7PK2}0n5+0dFYv01vF6pRcpn)UbZB2xyd&qqw7cFj>@W!i$>z0cD-rN z5&NV(pb2w|J_Oc~1c(Tl*DZdpD5fd^}j^$#g%42*N1dzA(a{QFfv#gFm#^@TA3nR}4qnT?IulXZvhWu@-_P!cnJ0zux zwBAur(AC07#Taur!DqN=bP#`xw)+~1Buf<$El>6{3jmdT!k_3fSUndv-=Be4r@hMKKsENe+)}z?}y0NJ`7(4Ah{p z+#~13zlHgf@Q7_iB3gr{7Dtt67%n-y`T!ShNp+tMK#jpkzpv`6hHlSq#HG2u!L>~) z-jCH-=X=odW;Jg2wtxC~;B`c3XBL41ZDjy5T3SLk${JO7#PkOyj~+c7bM3L5 zpL)^O-`MQlx~k`j)>?dWyWjT7Y>XOHsP(~HMvz4WwuOh<3Ce!Hy+y>EjSvm~HLzNh z9dCvKHyeMzTtx)jw$R&!~0yu?rb=JR3Rdezo z;$8qH0-^YdTr5@XEUhjfZm}?jQM+SqGYWMZ@T=8b;5f38$X)ss1iQ0R(lkhWZ)qLkKdYVaE?UkR5+$daQF~ zY+7t=M(hs%dLk#~U?LZTo#jeJC-i$r8F5Du1tBEK0t=-Wwrqv@lh$@YvELloK@zrE zPfdGGU#bD+weuPt{NwIt`D}Lj%l%phcfY^!9xjbCv#S1602+#dv?FQwrH# zNv>7|F|AtHrCWRrUmO@dCbcL&+`N#btlWm%uJnXJgG4LBDDbZz85S)g?;R;8Tl)+m___mR<-q9mLErUl1(B^R^C_PX>?4U2hO9lli~=PNIoCzs(?do^R{P zYk=6w%6BF5dB{TV0`2C?R$|Ug^e~yNDwb^=EPD(bBbnSFm;fSPGZgK1tZvaCs&8{; zdsb7534h+bKsn6o8Yq^~TW1i4to$ymMMC>b9WircBr^Z*d4}i6!ikOM3J!yp`M^T~ z_2T?sT#k4?y3L7lQ4IABLw6Gufu|9Nh5b--G1*GwTDMb`P!f0c0iac-<=8zf9Drw4 zQF1Hdhoo)+vBUnRKzO)S0*qh2AAHu5~J`0ABl;k^9SL~-kF1ue@C6M7^SeqmT z*jEfO^W+;fwhWaw?1+g{_2t45NS(~T8onW6UCGSMa%3@if)o$S#`hMm`Uj~BcEs|3 zWV?XDN-EXfi>Ej%;ue~xBijpb1QhrZt_SLK9~R|0APOQ*(m^q0Xd#Ut2l*H7A81?%tI}C!=vBw zQ~e3ahw8^bm?W#^p;}gPZaabCswT{E3Op^7o{CKi#_B1Xg>c#L0AL?mo<7cW&m3DG zbA;J*_3!-Ts%O3+82SQ&TJ+sn~bMj3sT(cf8Dr8Afr z#&@;DT9RJLAU+Pfd41~KxH{a+t4k(s54)&dFsR*~8a$2bTJp zc4y=-C`ja3i$45e!-0$!N7Yy7NlXeP8`$k3WQmAVNVz-cb(QwXc#t%YLo6D`AH7Im z@yOYuWKZ5dZ|qwf2!~yLrf+wBW(@x)wqE+FHhgT%Cf9N>oy%9&X=q)91K`}Cwo9M+V|cph)96cl9I-1{`UMa z6{f*jX@q*IMx*D}q{v=gA>Jk78Uf|vrhH9!L1e{M`MoMKa5pH6*m74YOT6MMq>gu+ z#8l##ue%77QOa-N4L(S39yYAI58GkLsQ4@D{5#q)X2lGj8Ov>4^O@(+ zf2p!wmuobWyQBx#XSEOYxBK{Cr>sFRx(d_Jk9o-{ICF&RpYH5(5+k2AF9}P#3Ap9b zAVtL2aB$pUO`PTMZL{i>bNg%MH!Jhz7apJ!wdQTTUX?Qeis*lP85`|me?g9GJ|2N1j=c??}Pu5AlXxXf(`s!6LL8QJ(G)(nppoFTX# z(kcimp2Nzyy04kEll!O$RHan6b4ys=>LACH+1ea~Kkj@v(wtBjO}GGF#U5hs-7!S4 zT(sEV(fk|?ATbjk9A{M2)%e;BnKGe9Qaa}ULsN+k!ax9nexBMw1-896B*b+r$u|Hs zS4BJ*ma)B5ov6-}hw;^#g zuUk(1l2H>-=BH0*R>q)A`^Sk-ZD;zJ@;T#q=8@i>Ax8obi95k=R2OnH9mkoCbikIF zuC%2rz91Ln#xx;`vQMO3oJadp(6p;7U6O}u2-nfV^q+qV@g z))68{)+*rlOP2>21qQGlsQFkuI=DLwR(=)bSCwGkpaMgfN{X2|dIZkjd$;AG)QtIu z`zq$NuTp&IU$j))9htLwKqq?kdyWGnoid&8WCvOnuFA&k?JEE>4K-3lsZ84Jsp2Sx zq?W&Zg!>tRYrMkN1HSe1*ia62>7wP+i)PwP@^0a0^GRoRJATrLWA(()bI|XSETo9( zpTtq0-6$)`PPp0^6~$g}2tEDUx>GX0!U^9xugNycR3DA`CANK4YX3I%Az%}XXAaj0 zgnu$hc$Ti()sQ!*RgEMhr)_3hFY zwmhfzC-Ez7idz@weP)b8)rhVP>WYz?_`Ir^o1dHZ-12=UNQ_1k&y0oklVbwleDRn) zE#`OviRx3@;{m(U9q=k%$gD-SG#-5iBixiogzuR{O`V2>A;r0D7ef*2TmRl-%uAnR z;A4w}(e4q|RiTlvmP|W@Y-S-@?1~lv1S$%1kSQ(^MX|?E2GgbT7*#FtFRLad;Hv7H633FZmasy^7SJC z<8xkXe;MTa%O6bnBv07!jf-(@_g*?$dWFbUewlhGHZ3<$%P;<8T$M`Gtw-Z%zj=>CR%O7eR6>;VLPY z1>~JTQTg^S6Q;pN%pZL++fC*~%7G*@;ufm}NfdQ|IwzTJ;D@X?+_tCX-506suZw58 zmrH{xJq5uJ<*EsYG;s*98YiWau#G`!#X4k`4-}dSBHPMwLoatPEquw)u_t^DcO^th=|1i89J211~#c zc#I~Kduqer@SRZ_&$xUZuDqq1L{_#ruXYTJlehOnOs5p>9uNdxcRm<50x$AQ8U8;2 z%Rn^0{P1fj<0d5Kn$sM~?E?F`tX^y>icZ99{#q6sD&=f*1!;eJ9pTxhxM!v(Hscst z(R&Fm{&1EBqX>XP%axCL`#>q(*b#p33Ze!RC4wW#FaC!3eq#)B2|IuB24~tNWgsK8wuKCz&(bf_3;dV5-O= zTdyFxB``ZggN-SHc>A+lsL$eR-A00kFH`{AP}aZj2-p8-8+k>a^WLFozI$JAe_mF_kJ zT?!yHaru)!@?q9BY79N7q8%O~vskcV9cveb4vUWLqpAJ|3L=g^Ge$KsiJm^pkOV4B z_0%`3Fyq!Y%t&5N{Jt`B8c(r1eF0xz6L>2EQSB<{NZJJqiiq3%<;XTX%(Y*_`hCZ$xvaA)~YbK0JWQ{b zW_sF;DY2F2#(r|whZ(oIcL-i7Y+8AoYX|E&zwZ=lX0F43Pz2@3##?B*Ap;U_?k=Ow zr^eYE3u??=0s*g-O{_eAjf3^)*>`FUGuQbu+N0luI3tB;z8OjHt-mN#9ebP4b33j7 zNNJ|gJZbpMn1oIVpfr&F*S9&T>jk?&mOlCn_osy8u9Y#?<#Tf9N2FbCv*O%hOX&I+ zc;x#3ur05M&)+*l^mp&OMW!mv7x?Id3p6VdzN?<&sriHYs0n3xYBa|_Dx@R(B$t}! zj*R37l!i;3De6TP97#-FXd!}ArY7eqUI|5It>(Sigm_9K)a6}8iYWv_mpd%|sL zx%2`5`)&r!`g*z+Jj*YhPGN?roCB}@FKMNEvNB6~a?b32c`e{H>jA#;owWl?LhT@b z&$}F{7|r4-;2Io?yHrT4t`4IbX?`YEPiav-G75&`>(nbT{c@;>dR-%m;6P}o&y?(w zlh!>2SAs*`kqTSU)frJE&F3#wUrJHE42pr`>-8ux{gG-t_2?QE1cy*U`-^~y?jCrE z(4cRnO##KWaVQ=LZ{Q1uZDDBbUGyf5uesCEv-geO4MA4a@e7N%pH7DQU z9seKlZ>hecMt$8Dlv)nI@j8Y!8-f?CXJK3*?sg)k?soL8c0`S+D>zx5{qDq_Ber~* zx9NW7jopKOVrzoePZ6ext-Ax$#MU&XF^y^bvp}@P!Re;CQO2R)!Y^p-aCCMaf2t+H zj_8NJ#mn~$q=aw_N@C+HKG^>5*X$@Vl5_AhHS;$SH04^PgwM*C`SuetZ$(RJyrWtE z^fz(n{%_vO(389G2-g=pM)X8&<$^t~?sfCzkMR>4V7Z>6-u)^<`JnYJ_h@Dicc zSj_c0nb;A7qSD*cK#Kwh%H)(Si+uP^{`g;xg7emlbiM~Q)dhH6~&a)8j&^L%zf-@Jio|u=)py8W}F(X6@oYK_XAh8b=))wUfM~C#aD{ycVaDXyqg+I4AJ< z%MYOc<1PwXuJZZs3pf@uhXo6hNls26!gG|A8CqBTf?X|s)mwdt?I+6URz*A(Ji}Ml zhcfiMEugV?x;0kF4!6iOjlC02&Q9Z|ElpJ!>`$w-VlWZ$Os_+T zE|U$2?u&S6ebgufs7kxRTo*%bxXj^(L0`$;G?!iGi}Gx)6h6qiV>I@lGT{2QE)MXYfXLF zhp*?wM`l|sG(V|5W(Ybc+Wj%x^Nc8h1M?nvYDj_k(IMbCdox?+Wbtlx8JEq2&Z39b z6Ey+WS!p`MCuzlWV@KTmkFsc7kzOXMGWL^JY2{kcm!!4x8Ty)w*g81fA`fMUequ$? zVCU%EpGI(;Fx{XyyBvce)7yT7@|<&=FKDFKHkj2fK23`IDEBf|k+DAw>s?P`?fgJ2 z?@%U8VmOHjuAE3OB&(o`&P&Dg2`(&}7mck_Y{j_S548{bgqL{rr3C{H5e}Y&FM6Dp zjFtTC(^A^9u2A$uB1yOA@OH;dz&YV@UVcE&A9oeda`kh5UvP||ISW{roJ?{;1fCX+ zuPS}r-Snw|jXe&xdHNOv8zR_GobSPhjKAo(RABC7+jBp>ZW&mOuA2CBJ@)jIw=f-51OE zw?tqk*OUI<$7Hps*oJN5#fRdD?X4o73)V0{t%x&*Vosi`Vfp6ZVTN53mFLe=X;P8A z7qcqCQkq)Jrk+ks@d~AQmuXhw-%sI&En)z%Rq}vJ_dz=Q8pj+n+ zH1;?-Ia`h%8~*5#i&z%vidF!hc?*dC@FM3G2WBr_h`(8pAZ30C2TDumF&OCV&-w|V z^$a6^a0Ip5hT-_wjY@uDglYwD)YMQfgPR7_t}==$Wb7U6>8UHG%&>t#cL9_pYHRyY zH4%hba5_=@+G%NOLbS3!Y8*XqcNn$?C#`!*_=Sa{6>d;dQ;&=r8dSQ7?$=~?c!R-6%2}q`+}v+vAPH*D$7OOygU%8 z!=P_NQ4G}SI#I~K&_LJA4BDyZ%*>8P? z`2&YMB9}bDw><>@&+m^>KM8Vc)V9@L8tZSMAYjvahClzGYgBfD*1>}ruD!H2x6_b+ zl=n&sse0vAo|tVddR?W@K7p5BeTfAF!W_@=MLhnJk;F=@~kD-E39 zet@!W0hhT?^KUOL;np&-^CoW1zwiykz<+;QLh*qk6fS;-S$E2dQ0YM55zIZ`{tge% z9Nl@uj7jS3)0oCItYVtjn#MGyF^#(%lPTnRM_%VzEEjNc$*vSK771@TE3XZ5S(8MvYRNO29sB9pC(M2*-DR z%Kl5$v{hwtv?`NhJMHj}O<~pAds!au&3J-3090cxJKp@9;!dy$UC&paS%Bvq^QKcV zRcDY^ZS8w4$-b#mOl-+iWM^SsGO28sJ2L80aEv7>)}E7%qpg@h*Gn2iK+tHAZZRPe zn*LlHvOL9!809K*vRaTuM`q0lH!ArA3)qx;nB24)WHf}Y-AKY1Mt50bhqH}H zn`0dJKD3FY@qxH$O|;crpA&>(0of|O z`g-cyhFeSpY~B6vakg+&7X$>i`D|K!gxs`hF7G?R+BusEwtA``@J_z#^vMZA5Cq)j zZ({Y4T+*s9v+u-O=4=kWGX@P+>5rrm7_R-Py?W~F+eVPDf~`A#KF}PPxSlWz zLP>ClXVnUS&hM?E_Vg)=);`V5!G_(Q63$fG2kqSTl%hs{r zUZvIK|*6zB+v{f5=b}YmMl+AN@v>u!U82OR@6|dSBdo!kSmKR z?y(^*C7x>+3MeY?X1TjPYIg&=CI!hmj9?4$S+3mAZ-2PoN<$K15C4oGtn(Y!n2ksG zv<(O&P`XG(O)Yvg7B4}jvbdNYn>bS9uW_NEi1O~`xZA6AH|S_mB)r3dZ#gvzj_!Ec z38>wA^zAZQT^+LMNr0~d&L%%x#C&wR2IO!(1_eYnPuvG@?t&{x%jdE)?;H&o8C>5O zM$Dk^)y{k_WEoK;KNc^EwxUN(MwSaWx_e?LsC4V~w98s_buyx70KN{+nEYH2^L6MN zWWx0Z6hz!S-7U%`S(dShNFveoBH@!^iiF8oMUxj z{|f5y761Ss07*naRINK`sjs3aCzq-=8EgVceg0AA4tC}CLC z)J(#pv52inw5_(GZ&grHFqU1TH!y`KpLl>}(H>|8g|50xcK>xN$18NCzrTz4pFK;0 zMIBJWGi43)EJQMk-qFO(5Gk$grJ+%e!a#-&x#}G0r9G%p2&*@zFp~R}@LiHhLfUmM zwdIgg{sgn)?vUwQ&=9olIUXDrsb$8bboM)p$?6ApEMiNkcrLtl!!QQtylB?E6o(p*RvH5u2 zDraG$qq&lT3un169mU`ROJ2kHuD)>PY|VJDjRFA-K-vsjqD&aw2uXCJX^>*fJhS39w=y7x507Vr#c* zKv!)(_R;M$6RXRX*cPKVzaUHOZ6J|Ilq&QD$dY!utHz!;Z#Hx+S zoP6T~)yMX6dHE}(I9sePHiZ~^ceVZ`um38I+F{0GP0W-0^3_yxEkxCTuEvOhhZ6`vW-R*Y>D-cI8b664f8MXwOT zTtrH1YcO}cmXcy4f^RShAqFmHmQYe{Vs5lRqplVcwuFTG4ai5)4G#|yYV_#!&6sqZ zs8S^U-XfwLK!BuBsH?-E?@#A9BI-%NA$}Pto*5i&zQl!VTZox`lSx%+%Q;VO7ic4v zvB)Y5KRH=0x|!1`*68)%;_B_%v(x}X_7(mjv&asjn-BRzgL{`_7b7Go!UA*N}Si7A&5jD5)&tq zwtBKU``w8frmi7-+i!XOR2hf<`YGW*e}NcI8GSxX*G|2*XaS22ZiV_rDXOa*ukzZU%c76-1eMULp z+J><1g-5vlKOd2+Hy~*INPXc+l1JppzY|bsJiUX1m3?66f(u64iqqJ2X)Z4>9KCL# zpmoO85|g0uW9j;Z94tzwwWW}vdWFb95d>T8?9A_LPjBz&hGDgjo*oqh5nH>#HHB0R zSGMuSzGB+#qPhP&-{8LRLHqvZ`^fp^cf66F#kSW!$LB|n5o>8bnD8Dm{NW|M7d_86 zpN*#S{a^Cuv&|HKzKv5ezs0I?gl#E}r`d6^l0NKkaRFmnF=@LlG56(#W7f%1jXCW4 z^fCrj!hiJ>+#5O(CQ8CTF_qPG{!T|Sn;;h67c}sHwVT>A@9~EZFOmQGTkHtP3lqB}|RBGCdSS;ll5wt~OO-X+-K6}^a!(Tlk6 zi55z;PH`yh2-yv_oPY1P^hrPDt0^9c2uND3xlcWvbju7mf!lb75FO*(Ph^|WSC`Y! zIkvGxa86*uJ)vCLSw_zOqm<6wg5`vK8V1yu$!Rfn7X+LWHgHeq6?T^8u>WW&Rv-O# zT9LG9&67sw$Vnq4ItJ%{BHW~}rrbPfL+Uyi`{k*JrzJ!BL|{wEI&;0Pk8<@G1Py+v zFYu(LD6qlL;GWFt1$)Uqr6)K2B8~Icm>ne4HnPrS(XL2%E?Q+ZrSqr|Y;pH+MxYtR zXrxo+%TH_}5)c}ShO6jmn^EGmlomChx~?E9K8h}PfultwG$>KH)$6#SXb2AvvY4h^ z3SP%Izkkmt4k_cnM08IvfY4A4T-8zAtPrm)rKmy0bww2MQC+wTM=2_4K#6jrUUvgU z6Ha*0pq>kEczTFXrKh#E4t<@B=;e>Eoq&KZ{%#U^wK}TwdO8#Vji={G9b6m1l4XG$ z+f_y8`8*z;m2A$ip|+5Hp^zSInX`O>g<5WdFbP>MxOrmkdMmAUdg^2mFMnU`1Oa^g zag*|>)m5R_cc2IwJUuO^#WP1z*%FyJ2j^3#%xM#o<`HQYO31^7(v;{>H}7Rf#ESb_ zaMR+hw32)1FnI<6@42g4Jc3vP)u^XU1w?n;Z|9IViMCM{6d6Fmf7!jva~nCSqIWV& zV?U!fr;(lw=BMn<>$0Li%QT(a%G*b4=o6fo_1Mci7<&h6Ed4#9sthcdFQJMk>$sZLfDD+5 zck`Q{|BRpgtpD?iKXIf+MNmzYW#!R2#=9)qIpch@E{NoSi-Y!WGUuw{61(;m(T$eS z4d3FM>qF40MzVLMk>5G_J=G>Cf)EK%nrYVGjP7*A-NOz61*6_LDsiuN7%-Srvsbt4 zAOo*b&-uewFenm!sgJTQe3&zXznHd(W)4;cafIgIfk*SlY+h5 zxzM5@d9UD!4H0)dRplLpV2A(GCwVZ@0YPcy(y6Po-w{!M(|g^PsH8bKi6D8*WnQ>; z;QKET1X~XxQ?~H+AHB-`QT7Omfy__$lGm>^Xk6WKl|WTmXf_NMbRxb>U*y&AeUI;b z?|XdjyRUF>e;Q@WBQW^wlJHx;iAC-ra(z1cE;L(C$0j=C;wl0v9i7(X+?N1D9^H@S zn-<|N%G9Ut<3jTkr?dsoxZ-Y}G+LSsL&jG0UCN8E_Rrb(zRN53_SX{{Jh(&T=@dA^ zh;}$T-!zCzIJh|U6K|tHuqQ5cC4mz3m7U~lrFoZEnl5vupa(QTtXN?!E!@};q<;NQ zxziDIe=7*s2ZiD<%G6cY)7x1_QKbpHsAz(1gNTW?N2x2Ov`wZ#SBoOL5f(hU!^z|^ zLETfZCn(e(QLdx9zL(CjA}URGL`4T<8x%vdy+U1aDQ$8Cx>^O%EsWs7UK@fP?jBBv zY9FoFi_vw1#yG-h(X zxDUa39?KG~)mnnd%5uTZ9SNI)JJ-b9IPT zYq^aNlW1EQXj7xL;@u)R;uq+Fh)QouD;-m~N!)bO5FNY;2@fZH)K4gWE?=&A4vRYb zT?#W%bcZE?CX&@(d5-6vdyeP6vih!yVEdaq|1`$Lm(Xf4jcH6{8q>IIA$MTxPyhi3 zd%K~Bm%-^8?u7hTJ24tnK*ZkOcESfuwe=dgRWgW9#6U;# zZOP;C=eyXoJDn1XmOvZ4;#TnBbKl_A|M&s_@$di2a}TT_&I>Aw%IH%iJfkD5M-5H5 z?_lp>HZrU2#wk5e42Iy~N065Ys)?p1{ms8(=j4befZEYUyVcX4N=I88vI?TT6Qk8< zS3C1LmR^I50|{#ujp_xfb~jLzokdo59@R!G4=&gfzV1Pmc}buc$e4rcGuc4Noi)KFz=E+r=`i|nhF7_2p{%EX1=UFn2bE2L;4;i&C7=ANezT)rD4|*b(@nNOE-lF4p}C@1uy*lv<*qvT7l+e7>`rEiLI7PI8 zL;QNyMcYDmAqNiSa~r(BWr%)+hMLE%rnGWY$EE>Cu0aIPAbHwmY`@K6lW92p9v@t2 zM$y`0BlJ?1_E&ZljxDF9fF-P%ZHFK?aQ0*YU8;=k;u*^Nz;4!R7Ed}SlT3%P0|gKq z>={u;^-BuTJA@ENfgYWXy0RildNqW_gkvYz5EdJW)>KY$Su=IDEhySBLjCWAIc+k! zr-*k*2#!JzI$a%QMJ4oV!iWjCL$C=WHd2eJyqL1)I%-=Kv|*tP?Ye-Qmj@yWjd^+K zWD#$FAM?Bl4*2+cAjvi4=Qo?vReM;D>K8ncS(fMsJq4V*(u{&a-IWWJn?yWQmJ&az zjGUw_7u@jj5J71qFHeUodgJe7K7$GlKKOe`$Tj)oH=EP;d5jg^WE;(bCE)}|FJu0w zLh1H(DC-dMU9yg3XA!0KJYSq^8lgK04o)}$sO=c1R6iZNZ3HJ=`yD0>2BTF+3{mP| zwNorQZ0*e{?nU2~{L6p-7ytENqkVqB*H#RxXLnb~H0!Xev)|pgN$^bFXVA?xAuQUs zna!Iwvw7npLNr4fo>&yt6!&u)cL%14t!YeS8q>JTAqc{qDfoT!o>QnX$#vzF_oyJb z;pgKp#0a6awL=ns-Y#=0o+*ZMqmpu?0-^&mW{l<$XaQv(s0ktES?2;a9H^6x91bYq~;ER)$bdTvqI{>-i90xP>D3U*ml7lCnBi(A>!QIav zdl8fy6;xTX#N96BcG`?8AYf}Z5JhF<7mU9IYF`tK|D^a*{ z;V>5s3X=B{Rws;+X0o@G!*BkX*Is*#&$0~G&n%)dZZ7?)6T5pxT&J{L;)A!oprRKX zVjtq=M`vN7M)*GtM0foB?GaJ9QBh^pczZ|CaGv*Ge~mxAzKa}d%$9tE`lkNWP9Cnq zzEr5ann|rJpo!SXzy0_p{NyLYK0o>zt9&I?QwiAx)*^>`s66-%J93TSoWN5*_;)sj zXpw7=^Uk)*wBA7@e^2=#K0DKbEIN^J{{~|11q6q9Hf)N=0h!vPJ4m-+IZ>()@xMR* zAwT^2cJi!jQHs8q7IR)w+-@ym0k_!M1ZhBS%qFMC+Q!^R+0|T(ihwq97Eyx@Q;O1W z)E(`BH(;z+C*#>~Wsk8A0oQqt@W@^<5gwT1^mmHnylL=P#vWU>l?#9Q2|xVd zkNCe|R9PiC6s7+gNixgrI|F zh>JiYsC49JV_l6ga_z((4jlM`la<|rqJi$b{x&Iy-YnU)9B)ac`RoZYr<&Ok?1@R3 ziLId0lb^v=>ytJxSA$a_4U(rVWmVQ4HpYRT%7eVKBNrpiBs}#4{(WNzTDg|v?`-4p zSVBo~V{U2+9->0ql{4ftmvHWEEwbp&!qlY6r>U0fC@=3pMZ(R`=N}*qVLvp1goQ{b zjWy(!l+!Nw6BFrzfPiCo48c+l<)v5A)hS2;VFcMu{ew?d_tXT25h5ux*5p!B-j3iO zL!^rUf+OKE!AL#jlwQ?Qr$_{Z1u?YiBCZ~uNPo+-G87EXAs~gqINdYXiyOBJjapp-IlnA{*#})dLQAV3Z*H~p+>vAQvz#O1tQVQ z^}|QF);)9+5j_G3a1cPbK}D4{a%plHt8D~Vft_NKd%;IUiHk9K^_aVIsy_wTm{Rg^KKIH^pZ&Xplj4olShuP>M$K^2J94 zRj#ITc*Si1Qv+2sqg4n66a&|=JNaxS17! z6Q#9A@4F^^7V!=Os;QV$XKO5^$&hKPsl}R|Yi!6gWgO+AK>^8=n23Qw_oB}n<_2mI zzf?fPIt&DELgN08&!;-*AHax$DXnU2`OCPc| zy?&Cl_Qrr*OZxVs)XJa@UB{OBH`5ylc%*D$O^^n9OWFU~g%Nv@;57r+wniFs8D!*J ziVTox$j-zZ)EP;{pi1|mPcqAA+9Sxdr0>6IK1dJ$b<;Txo^3`Jok&`i#L(l(}c@7UfJoonM7~Td1I|uB|!km_| zgt_2^i+u-84fT{{=37qfq_$Cdtri&p+rU7){!t9p!!V(7L%=C2o^XwTYAWLRu~ICH z6&3yUYna7~FTP&=t6gv=apfXBB$ck*G}7{|McSw+{avwUCmAsvw$a?P!AySl^~o0Q zE8v#6m^lstivA+UE?G`lDA!-$R6#GQ=t|<;$RV{*rbK_cZH!!qw$b;nA>IMxKKkUT zHY^D^C$40XhlJXdOIljK)yQ|%;-n3drzNL2TWv{i?!Y)ku1~e2fL(u0qpVxNW8u?0 zvm}tn4bQVB+8(v_5+80&r+y5fWEZ=Nl_47F&gJO)dpX}MBL%KtWsKeEKSZUg&B(}} zt8^EiCBv-nGdn(L;!at2D+DKkLcJ0Ds>#Z(1-EFT`inw|UQxt&LCf`QN?TQ&!-Dag zYHQkU=$?X85TV{8ebrf*yB^hF&q~BADh4lFxSm}~tLjWxu;&O_tabCi<>nv783gzY zIQiN81Po-95Iymbb zpzHSd1b7U5=jq8PU7s*+SBDxM5`oL_CBZ>JZaK#n=Wh(%yKN$wo8W?|nkYVX7KAtFGKF zoc`!T&gljzv1%99r?#^7ay@FHky}{mc z(-`$kx+(emO+LOAqx_|OII>$Jl=3hH~Olp9RKh){Q9rwX;L)! zEq#t>7I_X!q3FWAP3sA_h0d!xcb-m;niEtqN!@%n3f z$gb}ibSP8ks>|flH+PWNts*+lVfDg6X)06$Ss%a2-qLQci(vEDo*?P(WwrjBLcn2m zDl7aY=&NMk+uO*f>trt_eX)A7y*qqSkQ|)L1R`c8=vvH<_g3sP&e_8hwKfglr`Ony2){826v;4u83^~VYf>^U< z5$>W&NA7O+q4pDRF}fst6t| z+_H`^8x>>LNBsHY3~Fu-yi9x5Sw48p{GQrJZDeEOu&5*{n1xG2&~9xa3AnbJvB`Zjjwb)!mNELsw0MV@{6F|lz2 z(Q7eVRtKQPM9rbsd2LTN^<9Hjl+s0A=BK>5Bad!X#CgtY77j}DW*4`SN9TJW%8i`* z%OBa9-8AG~!KA13)E-WhnLx0?+1XO%Ba6wu@L0H!RihZc1ebYiS|5%rom}1V7N6&} zGE|MpMpE9smF!07*na zR8gk!)L;1H&TN{7P8#~sQ|viWh6w>1XXimtFoWc&sfq({f5gRxLDav}LD}JV*_CgB z@Va$PE0?#u%ZXYU(RU@!J>E~CYXVsP+#}3(hK_>Iczb`D6{58tq904v%)y>MDhi5e zSM7*TT};53) zJym7U-%Tb=@^CU(!Z$PwTcw`rS`)U>(FEOe`IiESiFHCQFQMF|AuKF#Y6-pD)jcI& zLc?rP>Z_?W*%BQc#6Z_$h;@=FFDb{Q2_wvcBc15xd5fqMw1c9a0C9#t0XM^U$4ciX z1+sW~BwD48lWFH@RvbuJJ`ayc%962UIf%G<-X!V-w0`~!bX{bIe*gnr_n2BIl-t#z z#=5=hT-Gi3MN)exK75p-!CZ@A&&<@71ZknKavyJR%b;%HkVj8rCOh8ROX(!Nu|{nh z(1fjKQ<4j!e1r6@TS>1Ph!|Eot2pugb}k#0|DV14j*IHb8$bTJcbH)S=?qOkK#Eup zu%Xzy#+GDFVv0#j-OZC^ce9%<$!>P@JX@aK=a*!&n`AdJn`BK)G}frG7wjMc3J6FQ zsnUC!x%c-6Mo|YuiWoWX*NfMS7tG8(_sl)#o_oHhAUj1e{bnm|+-lkeg|ld30D8$p z$)S&Ue`^wD*WAOKDNFg55B~ap{PmCj;&|D0S4=*+e8S0APAka44K7T|5!F;RuTW%?|5~m zH=a&fT5HQFE@>v>hkxOTP%{s!k`7&M9G|?I#IB%VyzNw~^3usHuE(UxSO+iV(OG65 z_B38hTQZR&9~`4RaSgx!V#(&1If4&w}BTaSnG#T5LAd<9Bj9UC8Pb>_< z-b{nX!fOssugvG|Ph!a0`8QsV@g-=eE7nRag_#-TSGR)JgQ!(cGQFd<)0Gkqyz@IY zP^cQ{=?MJY` z0bNrGS!oxjYz2+|C?0rr34t9A95xkjbnh7&RSm9DGYIQa^iM(`xQrK`YTzHA9HAg} zJMaB=4|eX}xZ6oIR+LawT1%@6vb8_+o_UPOc2C@<^WU=ecqt|oBvTOw-+P@y9UjYq zq4)lR=O*>`2I4AB$KK`nW1YQ5LLc=a?>rf5ra@<-aNC&Jp8mva8C&14iDX7wAV*yQbntKLZ2#XQQIEU~fH&|2TyYj;@SGw&%@d*b6}gQ>EB3zzCJsS;Mc^LctjMB7qdS16PwC6H$X&5$rgx_123V&NOgD7yn3 zs!k&@t%335E-$BJEaK27pA%cDqV-wGs{2B?x>}6}!{o>C<{ud|Nk(j3xr%4kx+dgwi@obPwTwW9Tz$I~kddHd5?au0sQ?~i_k zi<={s%@q`v)nQa6G=@l4t}@qwtif~E6FiXd7GIq$cw|>GPHxoHp>uZ&GjdCLnc-6pYgj_cjE2ogtoPovf>h&BYwzV zo(R2iU)1`}=ZX8XdHd@WvUmNB*G_m76ySlaris#=^W>B@qF{yZyvJEGtm7FSYSS^^ z?Yx3Q(ns;)J5SP4()-md<{Ofpz-2G+RKq{`sc-Ar%cK(AX2a_!%CL zbTE@>$$WrE>&MJR;T&EaN3Ge>`4)K1d772^Z}Uk^7CZm;Ix)UM40W|esVyWkBcHZr zOcbl0oPOgTmc7w_15Ab6c(e1H5*ovNUj3gRF!cJmro&U8$)fPXtd6U_<^B6^PQpIo z5q@-kE$?njA#>MzydLe0m$v~MO)IsR3Ms5;z=Q_-;Ke-ic;vOSF|>Xxczh9QZ+}Bp z;ZZ(%BZ*zXfw)_>QjwQIcBv7qR)Q9D-KR#14t>Jsv6ZM=9~Q240}sW$PVL~F{UeR773lMmbg#eBxzR=!QOVL zRe7Xm7E^Chk*$MS`sgffd9i^$rBZqLZ~S`AIWsfOpmP8JWGeOLiY5-+suds$<7&P3kh0PWY@vCwqLzM?}n#~OnjjUd5|-Xoo=co<2-H} zqqm$VuD$`;aXOPbyf(X{lY4nv>acTmrY-3*u7h3MJqJqY-8^xT<0&-B403kt_5I*_ z)`q(2r{gzcF2fK0horJ&?2MU1@U*J~ge<)0@Ys?x-rk%}!Qs`sacUFZUashkWn9cF zqsht%C#z=48@qL8ogN#>f$@*5BD?r~4&U!E^RAJay?^K90^Oefzt-9_>kB;9%_JCdw~nl5?p6lVr`%1+y4+{nW}{x~Fwd z&)V#Fn(t;S_4=%(E`HvXnOfJqUR6!xfAbq&`D!q)e)c?WJHPw=M?~~uri$5GcZNH? zoCty-2z^B}m^shCfbokj*mD;Uw#UR1 zb9oq~q;n>C$}(0gi^R+9=pqT*u!s4{Q+n2X9nJa5eA1KhL9)bU*i4>$a4A6!S#7DKvIQhrgHCUc`;Z|7jtFD? zq;UkhSa$NzrNJ;^6~8hBv3<)7q?qSl94%VE%k_8&HR)cUb zxZ^W4gwbOo8586}SC;7SZrUZiFAHA!Irf{sV(ZcKRFq#Jqr5F37Bq7O4+Ng)t$!V% zzRS-X5|T!XwY@9e0V5d`HI<3OUClozO4bA{`U$Uj?P2SVL&WE1bGAGikdXCG1WuU6 zk|i?(%g^2w-dOOu7I4`XL6V(Hv) zJg&dklroYME?`81SJA}ki|qL_L~0}HJ2nzqr6B17Sg>@g`O(C(3ls01#euXP z-`h?#XVO?V@rBJ7QFl#9=kX5&4C0iW+ZYc13*Hyb|-AcQtn?c=El4OzyjCE zr}&ji09!VG%ZbcNO7m;mCla;(61jy{L^wO$IA}rZ$&4TUhQZO>*}m@tX+>$o7hX=6 z#s-hkQ&_NUA(7tJUA$&)p^mv3*@ej~?&EaU=cLppvvKWFroKFd#`GO*jIBbEbObC| z%2@N6j?2V*nROtI9r-03{CYnVe{m1q780_2CrxA0FnG@MykPu-FLs`xq_%|YwwpPU z&H;aGi6#|vSCVXr&zu)|-SYt3w(sXu)`xnlTa@Yd1#CSirJ-CNae9{xiMMe(NieJrVcJ#yfEf9i4j5KHm!8c`x#tA&2;8 z>mFh<%eip%OT%{XXeQ2F$h@eb*x&G_N$C9-@$%0d*t~8#$Iq9Oml8)F5?aRqragW? zera#FEpSFkcQhkqO?;qa%Q6Y%Tji0tU5 zr1K`w-;#Z$&3I0k&hRcx#gsA3dX|R=#j<{T4Dm6SXE#YYoP(#ZY{fDn%{TbJ*CYoY zd>u5TRd^9Rw0%HE!rDI=UyC@-Tl+G^(7Qwbd*1exaPaZPL6b&>S1?1{55AGC@elUJ zBJMoazC$pZbd3YfPBIiIXgxizeU0pjr-ufq(1h%K^I1=lZ75U5I}m@U2KPx(+;r8; zn=x}Q4|#Fwl{9J`oM}tC7SAhtgMF8DPdqddg(ewiC;L9w3$ur9sGE?S#xrm7x18Ho zO8V{tWKMkG>Z&M`9^d)@#jB3r@YUwSq?MO*A)_2g*5eW~kH;Q};L^YUm(3TtcV?X) z8<1>A^3czJj_0QJY(Jh!QTiE*XnQtx9T~;K`xZ0Fr>kwhTO>OX{>U$RZTLR6?>*j6?zyKoKUNR~VZe%yj|X7Nlqr351eMm> zGD^x+y>(Pv!LuzKk^lh$1a}MWGJ{(nxZB_obZ~bKZiBnK4ek&uxVwAs;K3cf`Q3Zp z``%sa{BdU1OrKueRo%P#?AmqAHj=El%DZi17u3da|HftV;s8j;lCDEdq(eG1s~Z)3yvk{! z)~{(WQFO_tZ7SHLfRJgsTI(fY0WEH*h?fAx6qGaGCUeL;ylt1f@rjoUMc{3E#!m^Y zO3LK57H3{UJs~S1Jbe7daCqyv6S&(J#wV90v031PNBkkM|AonpAZ3WlZ(T?>lXiX5?Hf=s~EJQl4JApQAMm z4D#Hhii(O8Ws^`-8@QH6;ZI)gH%~?_-bDclQr==g_1=f>&p*QlMpQd6>+Q_;zx1l}R3=|qwfWp#bhS0FKXDhh5LMIPO zOkSKaPpqwT>;98_?x`bTQ`B^ViQ~dts!c?U%Ce-hQCyc#SjO5V=Ap+}w}DF=h^1#c za4q^&`9>i9cum{x)qrujbl`FPQ0rW|*-_cyU6vn>3yO@|sm$yWb#kFE0*(JLjfspD z^jscFi!s!F}8OI>AVlMN*~jDtI4cOGbb~gePF5`?$5Vp39{QGVR%zCe*Tv$k1sI1B&rh>|PY;3uPZv=_7gu-A)(s?6rqCR_ zjg`1Tj}Oi(OAsDayg$4=pJN;4Mv=p?+*+etV)i|^pZg9;rvg)lZ5JFR4ib^3 zUj?%G43+@<+Vc!m&u=4!y}quu-$du$@(${krpd@AUCJ6<%rp&mz}+*Ilf_cHcT^^ zew45G(5W8?0_`SF1(`|$Jtfs_5+H5AHtyt12 zzRO#nSZf%g+3#2qe22G557oH1{zaBgJ$FIcStEJI9(0~0@wM3z&)7yrfHI)8Cc44U zT5PuMs}oq|n1k1qCo2N_S%FwOPp$U1)mh-!HRif-fc9%tn^R z$b^NYv-&@&KYLOeF1%#rwsdky=;sSZ?TK5Gn0}d=JlQh_NgWWE`B;jbgw`+eiJM z5%4A(3b9WXG?CP@`M?|juYSwo4*waP+PqFOg^}Ow*B$eql^9^n-J&sDz!zL+Uq*6M z$DG~074*RG{BmU~IZ$y=zY)cd%z_{mY4GLa*4e`BD-FgUj7QbC=7Tc^h8yuT-BN4u zx(jmqupN@DZkbbYg&ZD{V1-G61Z1BLYtFcfoC6OBdD4)*w-`TR{!3i07UMT(~l*4df>QzAkk;+;y z5PyI9DW}2(-E=`PZ{52Yt%O1(47UsBKR=>_gW)9#;*yhb8?>3@sZd+~EVv!4t*kWB zHU{@+tv&gMTRVhKuCyq$OE)tR`dT} zn{HfOT<_-Q=izean{LjaP(E_sm7FU#kAHgEpPF+%(IYoUmHojUtb+`pVTFILJvra~ zU;l@-rT6FX5VqlS_V@QMt*qovn@=6zyku3ZPM8B#bag*B?1fJW!>Hmd^4JkO(GDAQ zhWv(KDG~LvX#aOZ|2lSI?fCi%H8wVyn3()IJByBr8vm&kJqc^az(CQ#fxWV_6841c z!^7}t^ON&JRH+X;&^FY$N6flN=uow}j)7_=BMmJe@RG4Qc8b??YbRgw{ags9BeE5n zbfEp!&2I&m@{Q|P2XH+dlq=uK9{l%jE#%gPt;@u~3g^M22nglNZ1=Hm0yf(v$f$ad zKHxA*sUvu|^W9gJlX+TMwBT6Mm&9cC(k-+3&d^n~)s3sk!h3k^0ge4TN?5D1I;3q1 zuSMZNl|Q!F@C^b%=evMPwY>x7mC}5t%Ynh=nG-ZEOI2Qv2b=~eb$su3`@g{6N4A{c zHWfwgE9{T|S&RPX3c9sz?2-n7U^V)}Vq!{~nvx^&SZ|d_&b*tN{!>NpbBo}>8P(UC zd0EJT&_Pj-P0{UTV+ED(m}2&0%Q_?Qv&VLh_b&!~)E{y>gNlB2{>bZ}eVcEQw1r*; z&rimR^L(^otv_l0`&!-6@H1}_-eqotvi)k!ARP4Gzi&e3;fRANgrdU|HOVAX(H--&wgJ0 zuXj!BJzX&W!+FW8ME-R4!`l^n7e~WON67<3dpOQ5d#Ab<*?-cvFEj>?6IPS$oM^-S z46AOhw!QB5O>;$A!i_s;-ZB0zi>g^me1~-6q&86ei`@S|M6j_6TJ166$eTS3gFtq- z`y;V&aFjJPLWfO`&I{LZ38O+p0ui>hw)~r$x!*A}GrwfQ$kZR3Ji|{2Cwv&MvHR?d z{j@*SfvF-(9t>Ufa4>Hv(6|wt6CLy4US-iFHFh{Hc&`SYw&><#iJC)6BQk8x@Fhncl%)Q9z$C{k%Jv7^EyJ12^=LhW3 zlKsml^8MT8^-}kwRd=QMeC>y>19? zwKX|MM-G_lzGNk?PMB~ggFxc2qM8p64-jHr%&ZAHgGPIuu;1!k3HZPZE#EbvDY&21 z8&?L~p|uz7tz8jIsBOdA|9JdKD*N3KVI315y;wp}i)70mXmPe7=otn2&ZcpnsB*-k zEO!Zq(@I2(_^n%M&!E*D1Rw!l$Fgt&9*%asvM1wFvwJD=cZ%{!wtt%aK@Iy~_VO_= zhc3dAy0mEV7zr7=<%3)*XrxTZ(n5j9~i(C0yvTi%6p$l@VL_1%ZpC?%kAp3Dd^)qO%ak?wLf2n z&i~unzeLR|n`$a5u>Nvd%uopk2uvM1m;PsccIG1?B_)+h2O@RoE85sFQ&Us>+@m68 zVE~#QGlGSCpDXw?`wQ<#B!0RQm1sK+(F1#8g;HhaN-WH`*M<|+%5gu6NA+GMe=p;( zO*mkGeVHXLgP5I{9fW1t`cYR}`n-QaOU#$z6FrbW=CN?|5kmRj(|sLv9>60aBBH6O zIkmDPBP)v%k$>$pVvDYoJbma4`}PYqcHeS6n4X@#ySKNXD}jqVoIvMAzG<+#X=kX# z#40B6b16&-gs{`k!d#kiUyL`x?3412_g{rl3C9Q%g*kGn#^@a{MHL6rnPi#MG9NJw z2B~D@RPO%z9h67gN$~$!CI8!Ff`nk(1NzUvzz2RmAR! zKFFd6d$Tn>+%Z_HN^MOuK|OLp=%=BuT{QuFJ_1K@Jiu$I(F)HCGm-#*r!Xj6NoJx3 zyvX8uDqVOV-TFg~khSl>K1-G;yS$9_{@)=K?cyMedZ($aoew?wnN~KRoljQ^^)xl8 zJ--xddFfLLdXc9usL+O^y%Hg7gl2NU$1*uMn>W|TGc}7-gWy#|UBYBkjX!Xo5*^tR z+K!01p0G#Aao8$jefoPlPh*HBl#v!Klq1)|FR4n}gI)HSavUa~0f0?QLfSMcgFN02PrOPr z5<#e8LfW6z3(WM6j7|hcAfFWZhI>d=EGoyg5+7?jBrSgi025_Hr&6(w zuSJjkIx`8&8B-pS62g}BjSOn^qf!kxsrIR?P0s|B@FpaGcK}gFxPD>T@P|O~zlUYK zqD{K}m;VK|g+e1R!bRUO{44dTDMlSvdlU251ad$A7@?-GM4q2q9{ZjuBmbzy5%p92 z3*5$OCrbzF0%oqd3aI>N>ls30CW;o0^S{mPl01|HTrVcF1Z#3jQTs_g<{Qd1UN5$c zu9l%z!Rg(NJGNhxUQ*`Daj1Y)EcVi?&?T4!lV&tQitC_Ff$eJaU9sZ*Fe9$M)M`0i zACbt;QaK)iZ1jc5!I$uUPvcuV3`wB_AyxR;ang=pGljD3fdRJQLER6_q1?3;dfmRo zcGU>5tlpBPI*O2Fv{zOOge^`pQsEXqG|Egn{nzxFyQ}_?g1K#});l^DS8 z%&JE2FKMc=HD-ansOhH*HEJYOsg>0G%_2;=@!gLG{KnaWocE($XD8uM2E%?xQC#5G zT+(fIXit-dbbT9C#+2{{G7V`E*+0SG+G{icFO!SEip0cEuvc3?dLLUBW;i> zxfPwcXYB#Sk+sC*>|gm6s5rzH_wLe4jF-k*?|pCBwbHzJQ~A(F*XbLegb0(_@}=|g z8yo9Ri536;U5I^0n@0*aX_olnZ7fl5rSqP9{4jgBe&w5mEd@vd$0L@glhk#6rCRO_ z-@Nvae~j>+SQ^r>;pZ5E`gaB}0Hbyuf9*f^OBgd|RD5hs_q&!pLkSi~3w~R;8^BTX z;p!mF@w6g{Ga=4C*%LlDy&hcWL0-t%?S*4(CNKUmIT9I5Hz9FA&X<09_|i z2ubL!qC!g3Jq9cy2Che>M!0cQlC>EzIrbuR_?M5d%pRl_X~yCCk6c?;HGHEpp)Y>c z`-U5*+(39z?7-EA0TG;OT3S_}U!2q=!eA*V4xg0zu8b#V8~5K_^GgSGBrF0_U*D^T z^K+pk3VgWs!@Bb+uq+L_EjN+L*W2&p^>9B)fZcJenhdW;CIu?1gi45u8Xt$iI{2;p z#J^G;uhXF@6U)HnYtbZQxPI{!HE=Ad5K#GZ&VVh0 z9jn8@_DSlGQs-uZmDp~s66|jgpZdo9CdY@5aM0GOW z?)|VURlzEZo}T}~``>~_A(zY&yd1dug#Ba7$~Pa3gJ|#3303$)TfZE18{d9KuvbJ= z-G7DgqGS&&H2|d#|9vO~3k}MFEl-GSQZhw+#$>C;C6|5fiq?Ey(D>hpuLOSXexLmA zx3C2|Bi|a5F|Ve{?*GbRK~DzOLd^_F>7Up0p7DjJ137sgcCGJ!y2QPW_x9Sby4BFl z!NeNR?OsFWfKpj8Ij6pad__vwZkcigLF;|t5Vog3o>olHB>>iYz=p7!f7W?dB-iXG zLcjg-&s)WY^{~Ynq3Z(cLNnpJ?*xL7Cf=~0umJJuXtlCs{nE`H_k$vhIThdJe+-^J z;ikH??-wTgC_$I;?75k~K7;fZ&v0$Uqhh~W_DZ-0{V`up>NplR!<1N8wmj7m_UCj} zow1G-qx;F#w|x_f_s5+AB15tX)fu391t*$t@G%QwJ~nhBlj07s`=jetG_=VqHdbXk zaTqOy-xowU(eKc7b}3cX)?rhqj@jl*2|ni3)Rt)gy75RB4r&9sOl_QDrjJ$keDJ)e zYggqO6T;!T?f(AtL8DVZM`TTd2npRI#K9GFXae$?ETI2^=}0wx*n%Xg70JWWg#qj& zPp$>vU<%uahGT4;pQ9shQ`jmq*pPHm;>g6<7}@zU#-Ey# zdfZ6Laq22~eIO>zs^}4YyuP9{W4^z4{}HDI#sANjOsx)CRX*AE78HAfV}3 z)rL8@MvbCJ2S#^+TEbH?5|i+2$((vgxgs0&%vtalIH@>k)d3So&bvmr4_i+jt@$Z^ zpWcZq>i$D+oadw)YEkfrFxWU)4Ez}&V|wDrm0c-mY!N;;vvwg@#~*bfx(j&wZsJ!` zpvu7MpmR@C30DzP`<#@L2DrC`KiJ`h#C38#U#tfvnw2##Nv1Xy6s*nuP>sXj0+-~yzt}~NzH8SedlI~3Q91_*HxF6H*RTBgu z;pgSIz}a#;O62_?uo3j>-=`O8Sood>S`uVJS?UK99BAD|hHA3vN$a4$hR$T1MNq%0_A7Z58;M4%JEy>a7%Z-q=39R)~ptERe-=Xn=c@hG3z+ zR~}RrxFMH;H*z%R4$BdR`+>m`bNIc@p5X2eu}kmy&Tv`VeQ!7Nwy>waxV~tj$Jc#vUGy=yiBZ{7aDLvVeu-Cq5GHH;Ey;#}-;RNsW<4Z! zdGZ25J-8>#OL9Lm;RjXy7FKLTfS_63v9hsLhj<`+iy3nkDDweTy>@LcaXuoPGB?jA2Wr1W~W@K-~eK z5rF~U*xwg0jv`6kRms+D5_lmyv*2XpMB`1S;8pS2NrMKoT*5PzJgxaOzF%a}-iH|} zzXmLMBF}-(;|3m_?G~^qs1{kWi3tuv39`-X&}6he8&nC3Sl}&;B{=tLU9PQd{h zukza(?8CSm9W-X{vu>`ic)J98! zf-37EBPzFZ%cf#4j=RJDAYCo1^0cc}VIwf^{iIiVf3Iz3arpvOjiC=QXFQ@4slL>= zlW+ycQL)3}>U)M_Y;mqH4^!Xe*ix6bYJ{RltXvb8KQ8!Rd6*dfOA^#cCtSJJj7M13 z+W;I&)E(1(mCPz^#h!b#R9iv79kMOJKW$ArX-AG%L2hCFG1RzOxgzB`jzx$D>jgS!x|$<7hwV*QZG{nJJSBmIP3!9YI%bb!Yi_*h=~D;2ZxF z#hwU!>1{{)E51Ejis7_I#q<;*(yS)cIPlv8a19}ab- zNEBI)<2F!GDKdhdsU?VG0a$Uo{y_C@PG%Y2$AswZ?}T8h)JMJ_bgXr%X1 z_@i^YvjnFbFYhW&_#j(V99qFq*tqJsL7uuVXmfl}E9}??9-cLm$=QigPAY3nMct^N z!OT!C{4cVhjX~P|hc{L{Dix-EJzfs7>wVmkD)hFsJ;uA%q@L~^zxu|#JC3on_K;nD zjZ?dx5#??=wD;3hx(sj5tmV`TG;gn&Le^r7XdLf+`xX*uOg4E18lA=3s6{}QleXKl zAMnP<=o0X;U4_ewy^+nQKMhX-OOf=0e%PZ^c@SHFHig|m!{bP)mC2>CQZ`K7G z!QfbM{RQJv&&p2GP1^wXiGr{6!2wV2t47_D2Ck_lnoFgUDS}Yj zewAcsYe}>gikFZV5regs`mw0o@2@)Hm~t6JSV0s;Qz2U{RqtGo zP=*EN{wf=O-5Zu&LqL`BE&17!(Ot) zWq#?OwvG-NY2j!1^Rnn?UsJk;IucwL3#uGeBl~Gg33y#0_M+6?9V99olO0z>3X1NO z3=A21f(vOXoX=)|U(9erK~}u=79{K-%qoCJ4c%14+EVl-^0z<=0pzX^ex_Eg$Z6xY z?r-i~*{3?pmYm_oLWVlJywDx@Y<&WE>QZ=27Sc2GLP7{Nm^VR~%#I8|l2?Za(7$H} zj6Dki*8=S>FX^TEB!{;t7FjS>Go>9ZVcX+yO^xAc^s0gt`=Ia(zm<`!IY?y2ymn6X zyA}%=ZO`#m6KG848wXMq7 zlHXX>x}P@Pq%=jVRT5+LK7&S4x|7b^a40{ZRP|eY<6kDVqY)=wqAb~&;RHFaM`}_u zx1)Us13_dH3dY`JC{JVZ5d)41Kk?}*av2n#oq@1HZ&|hclw>Y@x$jph1Jz<$g(h|w z2%HqOA}6kZJgmh)ox-BzQHE`-123sCh9EQ~98p&Kb7d4y$>Pzl(_OLu;*JriF?nU8 zfF>t#*96kUGImHVe$oPR3og=|V^Lo{Fjmg2n4|ekvmk07448rI^Zjap* zDu*f)_U~1dy2Ek`MaoGv#JB@Bu1V!5^WIa6CLpw= zhMB5QJ0mTSjM4jbEn>z=(^X-iZ=TN>`=8w;7K^;%AqS;wYfT5LsX1bn4*~+K<1&`x z(qCKk#b*#Ogw}2cmZDdrR*LWLnVLR|A4~6EO18SGyqieIWKLS{vwaGvj^4$%>lb!# z^@}3=sL$P@WRqX~3j;n@X6OFsJSQPGS<$6FqQcE+Xy`&NLpUY8v&oFbE%RSNTqaKc3$(fMyxvJU2w?bKogqBI}_rp%vkz7XdME)7Yh#dj~O0z9(TF{ zO3Xue_(VFk!lrl`7|uB;4}8bu9iGk+)In4?%!eU)0f9HWrLv)+yv5$5K{{qv#h>*x z=2-qVr&`TE#nz`11n*};-^YKu%!zZK|ivT=h_-^b?g3oF(H%)=UYDTjPdo=1uutz{te)uJtP4=J z+SpD=b%aI`5xm`J2oN4dupj7bcv%r?fsp#Zf^vW9uIhbkqxy2=?k)|4v%NU&3l*f9>BfqI^d91# zMGgcc5RY>Bo3R=9AZ%*JCt{-cw5R_Kz@749fAAmFBs`2MSP-*OMxG|>`mws4j@Bi- z?1XJ-yE@D|MoZtA>5+ZaP>#eZ{5 zK7TYCSoke)&L4#zMsp2^MG7$3YFzXioK4W{Qy~qomxR*#jD}XoR*A<|AQ`-VieuqE z{5`$0v$Yc#lSbx7pVfg_Z=qtEIUqPtkK%nuCEc0cI$!PBYY_d}(JLQ78*e$woLx-4?<@e0RIr3i?sD-hKG&P?-?9)Jc2~+2{3LN>RK6dQKDKy(u@_DWEdt{M^T`p2MWwr zQ&pS#$>7Ddv>w=THk3G)_RM(g;lrqoEs!6Ht+_dQdhUmzovSgF*2c!`sMp^V_ zR!PK>5D4>L|JtK&T=Gh-*6~@Rp1C8XYLgasB^)%KWchvfYXn5k+w;q=-Xi|wlixM1 zfFSM1K>cEyZwjC4a~@dFP^Uz8JUZ7iWK;)v?et|aBsb1#bXltyy zqGrg1tvba5JU~E3hEkgJk^1n}2YSEP*{*Q*)MDk z$Z)sXKu_)g2)ZbvzlmVPFqS~=g659{vf-8{3H=A8?(*`u zRMJgF&K5do)j$~|1( zaI@`p74+$|ceTgfSWxT9)Ioi-y}3M|BdS=Ynj7Nr`~w4{a06OnTF#B2XjhTI@XSJ> z<%$o*0Ln1cB7+pJe-LVOTM_t^u>rr!@?agLu6wVeUm?amWad&*N`?8}4X6K^B6lvCjLnJdEAq&0adSSS!J=7L>ghcR z7fKw=FT7u)5)NjDEz%|MFe&+-9gIW%W9rquQ=Wa%&cS(7VA(pn<@?(88pc>Is{%*N|rj)~Fd zF@h+kk8#~*0ZF%8V;M)%YupcNnesGmh$0$yB=LgyLNnoZD6;fC_skB;lWg6!dJ~MC zh1S}Wwa_LXHzuYPa7$2B0l-tbs0jfA;Rz7IgI3A0i6^~jgtc+IJJfxBCUrxsE$eR}LKTI0rSja1R53LrJnnaDR?^yU zZ_900Y-P|7=Q~|q<bIWsgoXdjNpI)bdK&5ryl>B1w`GLm~2K~`O ztIMQd^U?DeFSrxe6Wsr6|`y6?cM(y0*hlqlB1LXuh_JnOUtTOLmP+Iqh31QAaEG&?1a4!30!apoUt9Jfqhhtl zDr*=jd4xd$Quh}LS?a8Z1T+; zz%6XaL*h~UR#O>{wX*_lK75u|bj5J|&)E|Kjo?XoJ;iZ%J^QDQbk%1@W)@!q)=qYb z&FoG^)@Reu^eFSFx1*O|wte(WTo%ox|0=;ZDOuwj+r{P^?I_>n`lnAdJddr}IUP z+gCNa6RrFbzjk$I7aI;tmW8alF_jhR&fylUfukeDSFMzB=DCnb^`lvwV>QiX==ar> zBSn6p*-MM&!!3R=-Aj;NyDjQuPT_rjB&fqF5?P0JW>@OF`m(DHJ-SSl_1*z>P-;kx zviQ>oc4A(ta_h)Zu{Q?R*)(fTEA4fH*)KD{<1WtnJpI?wmR>7f8I?K-xek#S6R~3r z;fDg(hLeTuJugnHjUh^dzm_t$<-R{BafV~TeR4134G`NEELD0fK{>=l7u0Iw79bU^ zq_SB!Ar$@ZkNQdsNtG`#j%Ehc7mWHd`@dW~5AlkRY&%u3Lht8PY85kte03tZNch^( z63YJmEj8Zgg(#}!(K${qHKa4l&QqCqBr6Ze}44Ixz`3Jf{E6WZwz3AA_me71C zWp=;{sxwF8Q0=EQ*9nqJq|V;S<(riT%Si2A<%zLF!j*EnO*j(W$INU4_2QoxCG>6r zg+vk(+4rrQ^DC-KdAYhW!^1H4Ta=}!^>~E5avePmck$1VOy-D1~ zCIpkEQ}u34hCi6h6E3ORUH@wHNz~Wvm)WLUN^z*n+zB5Eokc9QDw6R4-3~AV|L&i+ ze1t(o|2%(VQ4I)BK047b$>J&55q?j_)mQ(#gabX%s{iWhW&_W+bryWw*;X8i_TlND zF5J6!Je$EbHCRhk8l-Pk`qjjRC}(fnFPp8DF3&z5J`~n>Zvx+IIKQpImO3(DHi=V- zh=Iq69kPoT$Rxo*fUnlrtUbNyV~6MUneM6I;o#$G1jDIj%eT=1A61QO*QMn3LXzsq z#qw1`6E6;z`9Qbh_{3LfSV=D;6KM`COwl!w4X+SZ$$Hy%sqbias#=^n*WLM*P=E^1 zbb?yYc@7cOZV|{35aMl2OW!Q(Wz-inTWFoqgmbbuU^AZ%;NiU%=z{rcd^_b8C%bD} z^1K!Y7R_-$9H}%Me4sy;r1df?w6d8xO-AaI3!fH!hznQp079d_BS zZ;Om8Dmt{if@1aOZF;I$o~v8tLn)c%hXxb#*T;6MH`%D#x7|9A_QtI;8ZQdMS8dlX zU0JR@e86ms1R%R@xKPz<6|>h0C+}pz$Z3WPIbEw04I`_`pk2Yy5#NG4A-3>eV9E0_ zT|F(ALf$snU97CjdjGlA;tV0^e5r`Ts*o?m&8qY?mCNDJL=sxnwfA^7pmws${1-w$ z{X{I`i3BIt@)2MApj>ud8yQb7ZMJ>|tBl6+Jgxfid;x!9UVYVUY-MD_F9y-@@WJR$ zfbE1ta~bhQPAIQtd7y~u@S(*eK}ce9mHVQ45-FXEjZFemZ)IU2R*Cf0?%34WSj@HO zv}fhYBWO?l)E&Levp{Hdn#P0ns4h~)iyghCvY4dW>a~1z?uS>R$9&+gr})Gvm*FZ9 zsF8YDr9a-_xJ_2!7LKc|tSF-dbHMy@j!E3r`zwboMntr%`r|*{s0nG1nj$>6KtvRZ zmJa9c%(3&uQ(Hqru{OVJ(lDWiyNytz#im!axkFjy((o0~?nujar)QWzSFxkF>QvM2 zIEfir3D_m&(79U|0(IDunwGilycfK2=H>$a=wvonE`_$Pkbt`mf3=ON1h|!h#lg*+RxEz{2v?T6w`PR6|hTh01F*rGd=0)y$j>20tZ8M>`Vc8(^@_R3Nf!0+O{J3{otuC1#&bT=_-cbc z6jhb!wPYd%rFU$oER?u6U&kBD9!Hqh8w|i-zsmCV>S}9aDS<$hOfDaFP7SUg>;H` z1~%m$cNveT^`juOe)F|BCM^yK&?# zBK6|G;q))R90aSF##dhU?QTvkIkeb!falX*oPv!dIWVyQKY)&93b`LTe=zx`^j6e~ z-b`Hkb^zB|_2IY2pwjifI2mDqmH~XAmO;mj=&8GBqh&MeT)^JW&G@LQ7rup2x#?X- z`rC-a{IYG4o^RI~|8SzDKU5GVgoq;`K(u*5p+Nu^O;YI0`4RR6jJc;b{*^>gv>`zRBapTj~&5wFDo+ z(#(`D8Se54MmFApX>?Ob9K|LuBod8`s6b1W*5>i+ZeANG#VR4Fstw8&9}v+j?d0=T z)+vE}tF)9xK?9I$b5jxjTlH2oZJS|$N6QRfPCQ>wMLyL=-(rt%?4KN z>4=K&#-FL~xGhz+273C^;(`4sk39mw?nMhfUnlc%>Rs4TDcc9vs%!4HRuR-DDviRg z-&vw*#HnKDD>4Qw7aaUHn_hfQZ&n?Pb{P)tw>%`;bI%lplW>41$R{ zH4npCZ0MJZZAf7#I|;uI1CYq$QhbSLyO@5;c;hkeJgy*6Iw`zb@-wZ_A|+b*FowR( zw0R@*ErM%d*|9{g>gk++**QaBT!K&~E3aXcY;I?S5suZxs@us)gme+REx~29}=bilq z0NbrwU_7Hvb+WR2_IA-=x@4!bOsg!ffVteW()aD!>oH$1g3zm;w3AwKef-%;?1ACs z%r6DH!KrWXHt9EInui;Vt`4o({)a6Y?l35j#73nTw{Od-?wgm7pwp{psQi#k5RvBG zST&?uz#ZvoH66;&xpV8l=en;VTa>X_4m2Oia_S)M@EHkzvP!agsGmIAxfHco)2~hg zcxJ1N^52>XzOr`EWv7)wAan#zgrEoUIKq>yqk6q_c(?ses7}nPhQqixq?U}Z&h znOtd>)$HqC&SJR^Vd0YZ`o*ag=w*BIW&uz3`Z<8u=lm5#G1IZ!CkA)iPbA~LZB*B> z@C|xORtt-gB(E`!@K7uRQ)5F@@X6|J^oWN39C9UetzuqwL~Y7;G%2nc>2TdSoX}xp z2$IxVpp7ct53Q46RC_h%PBdT5Mus1wGF8N1F@0f|?I(LyQDiNT_u97cd|qh(V$jJv zuMQ&1&IY`uKBlI|<OMZ%c7^cXxLyr3H!? zch`hKp}~TCaSsyQf(Li`=aT!mpOg3OJ$&}VQP|(?&g|^WcV~BJIn{cj(Q&Zn(_3$L|Iob|a|G*ZRiv50c7Dg_>XWAI#Ai~O+4liqb4z023qdlih1)}x zej%MT+hQiJ37Ud(1zI?^x}Qh-tC3BPs))08wj5YBQytyndA;Kh_L17yPUhob^-ul? z03m zK#$vDPG>FcTR6B74P4YK6!h!bp5{X`Y#=0Tx`DC|hRvTXF!rkrNc(f*0^{2lOMxD2 z+dsq{G(wmaxB%Xt-Q4^*IvjFG2+`2KxJl>4YF9@B_UAl%A;(ywfHV7k*W zuRuxr?^urN=xEZCO{~xjP=73s1)tNj5s^ReIdfO5-OVSY$Xz^46J@UkN1O5W%LWrp zr)1;pX0)TDISW4oYx-*f+nzmiXfA{*)wGPo>x*-n>{WRMH~v217VVFtWEia`q@^|l zs7uI{&G|OA_7bqFr5JF@s7NYLF)vL3``0|@nI@8Pu?Q07Xjq#*n}y*{jpp@=ger|e zZr?T7w?5~!N0O+F)4%0*?SF^M>45P(*+!!8hhK$pQolG=IhR$Jxs7)OW6%+%!~3LF zHz`Uy8+qDTGO)RZ-9?WGu*!_bbZd-91!sxG;Ng(Qcm3x9RZs~zy}La9W`Bb$zW>@w zk>hAA9;?eEn-FmEe&udM-8pbdda~PVv4}K{=B&sM!NfPNIW(7YwmM$@dMD&1m+ac_ z1Wg|Ig^r!d>yzuQ$V*zKRLf*K~6grY-OE)HrC|xC;k*i8x#%=x{mxgtdVE%RrutlzUx4w6I< zl8ztEl8Z(++^;}v6bDMpjy)uUMR zj`Mk~!dFeApv2XZ`7$Y|5oDgC*M_F&qPGNT?c?tQko*}ywLWehQ^yfbj_XM;^Z3)9 zwsHKLvZ-Wdk9fLs@BA6t`6FVHt6M9edQF>^?S`I#UK5(l*)E+bs0wUi<<)A|DPR97_@l_zI6Nwl`2)h?~T+NQiK zY*DQc3ns^%6c3%vsuX$}+pm@*vMF+b`$0E4EZPCeQq|8JC9t$w^RI#yXdY|=D%lD6 zHgpY=H{%yHb0s3Z%Ivg?HClAYWxvMzl^&mb3IoK;bqYbUC2BUateS50~@oB)dB*imhpF7)T>2pB}C=9B=ePk7rg$F%&#ceQ6-M&Og*5 zESDXJkj=mY2Wcp^Hqd`wo+1~?VGihaWSQ)^3L2fOX7y-3%N6z8z+e%4k+>o|QIvx4 z5CZ2+Ef!wS$x_~$hQR_(t?o9L?lqJG)eAknpcVB9qKz<$S7uD2Wn7s&>!k6E7PvfW zW!t2#l!0k!sU*IFQ=R&0DO=S)sdTW!#`Ks$<`N3y7R2w)+4H+l##PMvKErR4DaX~( zTe?o+z-JPn5^72pq+>+3o$&Jg^te?ZsMWDdeY(Eg5j0ur^`Iqomy@$Yf1(9cI+3PP zU9&VOre@W{wYx7(5nZe_1MMm{pN%)}lvbqdj90C2U4^CbeoWxfr&P;o`TfblK$IJF zgw1lZpyumllzXG|d_VVV@R!F~vBn?Thtr6_tU-XM3@>n~8nd|Sv#D6~Y+)>;*nku! z>*DQWzm{S~m(X{0oSX9nKCexSiuZl@XT2a7^~Ot%%;Q)}B1PR2k0fFnWMUV+eqdf) z4{M04ytcH{gvvc9*j}(Q8cRPuXYIBfUzA|mI&+|YT=@bXL6K1DA}a35UYfhF>U#gq z!UJAEXUly3K}n=WTO!yxx?x*o!K-gsjl`O-4e=Ze?E{G*>aH!lMX@c+D|^b$simxL z(RcS0fE`o{@;9i<$i;p*m%8q?3u7{E^TfORUiS16T3DKF`aRDAq(ZrMX}r*(l5MnP zjcUpw%+D(X18Ljd{~W`i?&Iu6w?RUvqx; z-lF)Lp!B__iUp`?OvUvoWAd&`Q{*selu#>nqLYwtF87O=7M>oI7L#PIoQ-T%xi&%~3EL2ENYr!ML^WHSilTy3bDyUww(#nEq zidfmgqC zG(~g4x68-&ema=aX}2#(r|_eVjx4!(X%SQ(igf{h~3>1}N7cF0&me>h(0d7I%r3pmB-Wq;5d%if?Q zH|AYnqkHGDk@ZT&C_>iUH2*Ceh;pXIaJM36Juq`1I85slLM`4x8oQHnp(wmEP|8eg z(C0C5dWtVL0TSm-By%W()=MW1eVh%|>a4tiwj@q0jJ%TDkP9pe3UDw=scnlBa%rl# zyB4|bzf5%;mJ<(&k6-6~*XqE;LsK(=4-ju@-+P zY^nAv#b~a`G4#dz(MD`dU_L4s)*hQPiJ$EGFtQ*JRDcw9?;Ep`G1!J4x8HbdO^<9R z#2fhDOpA@uZx?VgnZ*slmAuA&yE}%7BhD}kn_#g8yyC^4`C0Z z!`%Y?l-Y&hwa@qAHA+d~WP+rE-PS;`Vrd0>Us9P4pZxUkgFzlJx^+61$|!#?Pv#h+ zS?2xli0AXV?Hqv~NQ@2p$W)JKAg~B|7ufhof}aO)Cm-uSxxpsv1HBOgObm8n?GILz zs4g$7HXk07P0di@-i-%9ZR}HaqnRaA7Cjn0@7GDevkw*7ek^*iOB^eQ1YyZ|?*mn^ zy?FXK<*y=%cYgrhAFPOTCV8HjUBfgCim^_kMm@ZObcBMAiq8`RfmVU_63AGpB8YS= z>O5sl-P<>@y>W9&fgxcpc_gpQny&rDLH1f-=@cmD?iA2rk5~d-@;rHTIOSU-h{f`< zBeRP^fScrs9bZHnS`ITRsECLd9+NfUv+b*mT~xND+v>o2(0K4(aQ3kPu|<-3-UMVn zFUd(k`x0zPw>u7Z97p&W!fK1b*8^Xbc+STL5=~`2+k(tLC+<6`Iw+{&lOLCs-PMQp zaeYn^ISnXc^$`y;fJipl$*F78$b`;UiSwLAuML$G#h4sd0B6%m z*y;u~M&C8!S7g)=_TcUVrH}9Y?_GJaG+Rs=j`Ho?w;SO#^`$=7}%x>7QtWAmgw>>9on5;JS67wJ=7}?cxsl} z2BDs(RBKtUf^Jsmac_*CTnAxaTy$q-9WEY;hcPeKs7<96Bu&(~ErzR}Z>_jiGAmCW znixpdojz*R@LLL0tlYPb_isq|TRQ6(Z81!JmhKCmt}$D3f;9Q38T$Jawp@%tSSf!x z7RfiuEe)iX(jr-4CA-)jrS7pivO?zrgaX=YtS$P}hF%Rlv}eK(DKEJ+uL@`&E(u0Q z33N8zY%L;b&k{VQy#UjHyoh$PHsJ2YaKR+$_!U+89s+}ayJ)DVZRjUNaHu~`#e~@s zARnKorHqz5LZkjD?m2BAJ7H3CC9`9b&a?shJM%Wv%h27q)(3jprYk8H{aQ5*jT;SZ zi$ABdc6&Tt+sTY!r?(G!r4t~Ig-qs>8t~{~Ip}mti{|twhiE5Z-w!fy;BBO}=d94U zg16v$6`qi(7pk8v-Q88f;9wooqnHk~Azo*ig`Ygzj!;s~XQcckEUP5O>&&e-!P8|? zikk9_N9F}(INgewWs$4Xb~XX&tZ5v&E!p=ClyMK%Yrx_;va=Z|Z%xjN<%}N*9=&Tz z)Aij>yCD|jHI?pRFaWI>&*lyesu8%##$ z`G!4bSdD29GIX>KC&v zJ%Itb|KA}{)Fq^uW036yo<`TE?dh`GC8dcJ6CEsZZq0oK{Q-zP6)&ZIY5yI&psH$Q zQ!f5p^U1JKeBo|oYYQ76HW~u#0I!vaXN7L%KVp>uRNtX)cquCs7+IQU#;dJv2z@v1 zvIVP2SVZgE=e$x?BSi2_K^d3CKv6dEn%SUr?d>#e5#qT!-Q8>Jf)0!XDNQIX>fXxS*lVd zB1+BGHPhenLq_&ZaklnUMYv7umKMIe=8Rzs*6CldH4<+-SozO)(Eza)t%v&rCDEVn z6mmoj;wL93CvbI3`W&Z-k$3W9zLwFp37b1&*-))DUlHzJO}Q)9eMkAJ)Xc8H8!9qN zN){R28r!6IHn4J?yHmwsc4eOzG_`1GRyivw_O4LV1f>-o&kS3;kO;Z(`0dDv<-vMZ zrCQsI;f3cj@ys-8AtSTHPHKzw?ShumV?xDBMI;$*slbI3{>|GWEiE}& z(|E$-FGEz51gvuhR@g(OIQqm1+?e+5a$Ky*Eqn?2_~jIt&)eL#jt;nU|H6Y`+|J

Pxz=OTpZO@Z97~lj!>024MKVsW&N*vliZK=Hb?SztH!E=eCi5k5 z(rZPrF=FxI>PMdDuIb&47IDs4GHxfRovSr3$ZwC+@vCU>3;RcC6m12Av$0z@;-X`4 zr>4r0^YSsN##m81Z#8a{Vc~`IBu3ede$2m#jAR2? zuFiM?h7dpoiZJ3ADi*8X0Np%~Je$3p0zN-?ur?<#EHX?jdA~LZ+xc|4R)>k!W+HzT zA8KPi{wtXEDQf3CG;iiqKUxN`<%%Ma+j@G3y5VDCc$IoTdtG#Yw><^ip=j91C;&8_ zh8KU+`Pv>1K5X0HM|-iU=*6M*H#=x=5-0YZsvQ~uR4@CXI~;e*!PrTt+sXxjzLhOL z#>OvE()p)+d9*$?3VQg6N}&OKrO-z~P#}G9BFWcb{|4}H4Ya3HSp`YgR+I5s;+0pm z_ipQo8a+|!6{LZ7W*oZA!hcEnUs~+TDyk?Xh3n4o0`?R&w*QninKLA+i(V}UUiH*7 zkH4^c31a&^{^x%S0t@%WCn7TfOBZ7L|BI3#&2syPw=@6NLPMkPR{w@T1csqrmHrP| zXH|sXFSizCQ2m?#)@zn*3kf#Ib4PVagbQa4LVWtywb$xv@W)>m|M>u#l$~7xg$gDmB{A~yYO2@dddPKyXwrBUTyw1|l>)cQJ%P#+e{r@1c5cap^X z?^eII=c1C-aXZX$qiXIhw}<@2AUi5qZhjUas?Qu89Aup4;nmgE?_~D(_tEu%C&d3o zXw2cY_HfHnqJJ13mPSYysO?)F-`pL03zPBMr-tH^!vGR)PX?;jFK)1)BnTe2Tx>M7 zEgi(COvC^s`#;}piDd=IX9XxWTfl4Q;P8!$8w(xHAOeyAz=rAMzpX0DS@F|)oC;*c7_@OM=3h?!cG&+) zfBC7TnOT9z==c8&2O64U=*#~b#Q)R$|51qlo5%lWmHxl2QoG3LzjLLnp;ExOjMjR< zNrn>>%KZWGi#C=~0*WKiKq`h>AYlK;0bDD{wW(_O{h}FtJj}uJv22meXD~PZt$xJ2 z<;PzFrT+B>UjoNSere2F_SFjAw+>a;4u=|vFG9!UhAi4zM!tO`Qs$B;gB3} z!O$}l_Ze+V>5j;Lwib-aUR(o#{6Ss}RM)d$Nu?Dt#`YHc`6DeMDcR4S>Cs*ihUn3|dG=P$o`s>HfjwR4^ZR- zC57}$$CUUrQ*JsM4Igo=0m99*K5Kh>$c=A7vPBsb`WJu*Xtv1=K_Q&P)YPrio|J3c zOc6!9zhNwe#`s>!QvBE2(;<%}U6nDxQHjUbLs0}dfg(IQtOaHZ;6a9w55#49C@OPM z4-gp{877ori>9ios()ZW?epiZ55z@U>2q^uJv$D9>T;AgQe{U~>3&Dv5`LX^qGvw8 zP>==)1WuyRWE~t_U(fH;Y}lY*(c(|eug^}RuBVrQLJ%bJz(59D>jdb4?@{xh^)5#bh?&gR!hQ7Jn`jfGA!uj70v3ItyHi*9UgGBGmB7*=}aMuw_Q!eGTf zAQK@0L1|5m#$OzbjNsp7goGp)i3LJs!feS2Hco6mImX1lUfnqP?iB7SYVF{lWuXAB zCD>DOv&&FjGdru1qyN7D2P}$TtE;Q4uBJwV;<7yiE9~yaw_sG;C?TQ1R$^RS?w>z@ zf>~8#OPAVa*C6MA!KF484Tt2NyN@-P4hB{A>MS4iY<@a6a|p?~yu3~BX(*^z7@w!@ zX0m(6FmAE6#yqj%MoPBs!q`V1=y8X_s7wqDaU+Nc+7$h_vGo2@$$4J4fv7!P{W2I# z6UC_QpEzoCgF-5Y>($(3dsg+r5s<({2~)>}6Nq+&8*+nwZ9OH3I-+57-ldYxEiAmN zmNT8cxoIhb%6FZKLU9&4gJThKC=JBvR7U;s*BGMiXP#3|PdY8{Ih)@6H9X4<4=XjL ze6U&&?Tm}knb&bV0kH|wi>JQ~c_=E~0=DYIV^qSIbo45cbHULy`{~K+BK~;ajDBet z8nVjt4-F;3_mfe0-p@}6QGlLSToxYwv|+*Vug;%r4l%K@;olhBS@luNs4kY;`^-D? zT(M|yV-Y~E{kz{~K2EV=pzQ}U)am&YRM&V8;O)N`aKC?|7uaePA2Wy`#lf_ItIK9G zkl^(1djiUsvpncCRa%38cmDWiZWahO{u#hm8l#g@OYn~ugk5mh>&d(<7N%2+>GZix z2B&-O{!t~&^3dBUKQa7nCy$C`yput_{gE=skwux8%;mwlvFzCxOEEIMMn23$F4&)% z_<~JORU*OY(I@aD;-_;!>tj3XOf8vblxJlp+TROMDk4bUG0YRTbt>g0xGQO8;L;(H zh?F3}aM5Z>UBnTt)&6~_2YUw%y!Kg5hjd%D(2m;YUV@^mUM32TVA{V1 z40Y_442AlsC%$(2M7g0zx|sl$<+C(m1xXAuUHSimwMUUL$fiQB*NDd@Ftd_ma(dUD zD#*s5YtC+_tNu01%#LtgrmyMBp0n*;F3Q}+p8SQk`Rm;8VwR4qLLJ&0G8g&QPmk9a zN~pw9=R@dADDWizT?{Xm7jV5s8#dB^)RCYT^IxlNF#YmvtwUceX%z-yr?j`$3|&{! zK1(Rm7l&oXSi73SRro)hLrol=wYb~$rnSViaipF7BM}!{)L+9R`W4~7tnM&NZ_G?J z<%AFBw~la@Z`i0l=uNgen+ut9b_4>dB)YpAqOSkOs~?aQukCT2b-r*}$UV_}imfOT zipO#;Fv8f2+RzJj%LXTh2tk@55|Zl0J<*i!ml&P$C9u5gwQM5uN(WuYgf0{7{t_u# z8%IYY7fkhv{^V>jo|BYRYnL2C|0?mwM(_IQ&VZ`JjfzsBQq$D|kA&|DXbVx`+@_>?Nj zN~?}0D+Uxi;+fV2n!Rr4VpA47V2-iC(w!Wgp&M{!=H!($B>UUeqOYgE)JtY*xL(%E zH7JOE*d@O{+aa2DWxg4H7*lE$)UV)Ls@CoIvR+94M7l-~+J^+nt(`(j{d)z$jNIIT zuJ8}U#d@_KElLvBAm5|dv%`el4Y5rmE-7oB|JXCxp?+BX?Kaf^+;v1svB47z)5va+P*$Oo!5&N@$o znk(2$eCUx=EK@R}u$QTIU?jmg=bYiR5Ln|vV%3sG!0gTziAc*{dt4>r_*_+Q@S_fF z;;BQJz4`fo%#3PxzqGr*2MqY_43`8@T6>EBgjFtrh-frkv*qOD{38mJd^W+6^5djy zXOAphxV?-6)1`Bo=MuksgVp?+1z@7`Pr9+y7zd!)Eu`Qi*y3tx5mtQuVx)$b4_KS8 zjj<~>mQfiqH5B{s!AnWz>BE=2r6*_ILSitnW=+-$h|_L&PhO++I)q6 z(GdCWx)ZXVjzYgy9;>oNZrdd5>(=3XvDAZb+)%b!`h1{JVF0I+9YSu1-$IGBlA3-y zj)x|{@WdnA@@D42MHa(-Cne!NHLSjD?+Y*_{^d$i?^5x3F**~t50=~2(z2f8bH_h% zC+c?-v4%W!9v46qR{s@5+G;K+&>x0qO{;E4bkB`j4n)Z*+lzyDWRtD8WAqY*+Tp|! z7|@ONFz-J=4Ks@Xra@&pa)6uoXZx= zoH{pO&;8inH}2q%B`|TJuAJehqMe#Zjd{m`D22GnGR+|iISAM}d%QRFhNoThU<94t z)UIvuYLdT@cozI8^-NYx14b6Uy*qw0~%IR;E%zU7!oq~(45h(;BKbSR=Ye!8RzvFi-EaJ#>xPwC3PW<=WenDm39J#uIMnR}$ zt9^>p&#E7?vf(P9*^aq|MuFFJwo`nyKoRRdlq+iT{`sqB%?l$p6xJHzZjJVxC|NQ1 z;OGT!>S}0AgUW{3;DjZagCsoMb)d5SLksHB{LpJ(kE|M4RJdkTC7Wf6WKfkY{?3O2 zmNXYxB{|sgA9F(WWDvMXJ*k0FHgc<5@+%k!0;)QwfgacDEpGAC(xQ$xdEACmdiXJ6 z=ZwE4e2U!;w{gr^C`_K$Ufa zRjyI46#L23C(>T_4PCC4@$dDjeUTR2>Au5vX6lj!q&ms#+H1l~{+qAiTnG`Q&&W*+ z7b0NzCi}AR$*TNs?tULfz8v0ipUmKDb(XH#w*kYkaE=H)HTIq^lPp-?;9l5aEXNGI zce#|j)$|=5Yw&vFXH%Qs(i~DeXOkny|th`~% zrgTMDoOuBqPd^a7>e==>QIe`|qNIJ{HxGIdg{&y+=P-yc=ROk@l#zZyTo0Rzm5Zja zn3aV&Xv_Ex&i60-o)PZoLP;nls%8?TWrm7#A>Sx&-iwE&M=pli0`2s+Ny)^2&&UiI z^riEBFI;eL6|BhCWi@P)wOeA)wSO+qz1v`?A;~_MDx&*rclDXQL~w-Sdf56|gWmq!w?v{fmg3 z0E*F9FoV+?$ZPAE+4o96(q=soDkUCP_zGDx))7gNCiy*J=HPsNEO`vT>Zl635?6318 z#kA|Sw}Z3H3$6O-tnO!W)n-i#XXY(Ld`p4XLV_d{9~Fk_I)SDfRzDqg@)zLZezB2* z7Qa0{3m)A2GO-zzSlM3(u-VE#$FQ&{Tw=h6X53%PvKk>xd|xqfFk&PoEwFzV(A}!S zJQf`FPh$3<#PRFQ#F~=-)Gk>|M5~w+FSxW%G#V;mfXcRX_UTr()`_i+s^aBN0Ytwv zcQ`Mu+LG7{(WBT*Jdf`%(`oC*oRqS&{@J==$^qk;5^d*x{P&l_2L*@+4{Xn z`a;?^i{f%8>?*J*_Hx=k0x1R!5Pn9?qZAN=e7MK)2slU+hepWyj|DU#QCU)Ju1p?V z-3w;l4whU^d`Zg!1Z7IaXCvT74;WUy{u42siGh5`>zRTkpCl1~flI7X-)on^!vWVF zDG2G6a?~>dky|DI4N6eS!UtTkPOY4^BjjCX%1)>_=0`%DzM`fTr0AbUFT@MaBQ}RG za;;ZHOSe-x(2&XzLK?V~?lfU0 z2Zr21^PbL`&XDsLPs;5t|V8tF(3^l_+z3uwDII-O3u%&T*Lwnl@x#yY!DR%QT6CS*W-|1=}}s^@5PGsFuz$`O~IL{)#&l`5+l`aMRIui0UN|98sk z`{LJ>$Pbi=VFRl_Shn(uhCKSZeifVg21o=pd(CH z-j#P?{B=0bB`Nt{KDZ#}gjiDMJX+~Y_rp@{FEb1XhahOgxy*wA7b8I=t31lwoMyiQE7rU1|TZ#w(sVD!$nhSbVG78 zF5VkiTtO%W3hXAvzfUXf^4JTqBNg~T*CWX4b~;L`+o*Ju5Onv5bjdrBQihs%?w%?p z2*KsQ|AE|4eeViB6ELECth%NwBJMtAwAnR)u-PW*s@(mE9!?fe64+*D6c8#&+-2@) ze32bZU@@bdwJ@pPB<$Ia599^$Sg7C;kxVgrGP<6i7}fn(LgyrU{b zC&D91IQjN0M|$3J+A|FwkTDs)FM;kbww(9jWg1;Sxj;lsGr2u$JU2f<(Ce0oxkp{M z>MLGumX5jJzTq*Xq>F(IimjRBet_plq2DB}?Q5rVMPrZ(; zaojykWY{crq569XKdUy@H)>t3+-b8a9BpmqWM)&j9MbE9N`ryWz^2l!jdC+J8#-9u z?M8gcL`M;q)|RK2n%b27_`=n6VsxKU&S zYdxm%L$7lNXxZ2@=6)l}ka+SuI9e>X;?e3;_$!^#rtOlvMD$!qpQ(IiYcfTQl-~UK zu3nBo#Vj~n%fzH7HAcYI_nlnZC~ojnYL`&CslP8*{8|6Df1O?zZ<8@u^AHP)B}DdA znl^oi+SNK62(eeDi>aT*T}vES)xm36!CM_6_lKljRtv!{+AXQMBAYCO_}4JhD-f|g z&E@jmd_gbeo;ahhw~#3uNNHYVf1#*~cl#roneE7RcNgWP*okcp>~8PH0_FBex{{ls zNBqyT61$cB4<1qZI|IaSQrHd~UkA5oT0bw&&)OUCi_e*=bH21@abm{F8hf%_u(Bbp ze-WJCkg;I*6?w>~b{HD8+8A45md3+<#&>+Er*u*fbj2CuSiWqfWM~e@IxyY5MMJ|! zC?_X7801M_sJln=X<5Hz-R$n_XQO z^fgvQUvd>v_SR{|AP6p_01|XKRNfWs@@hT9iAKF1av{=hMKwzZF{*!P$7;ApFqZUV_uuB$OhU( zz?Nam2~Ge?scgh#DxNG(k%h#deu-4U&8XKEj4Qf6x?%oe%gWnaOLH{3{^Ddgp2QY}4U5BbhLu8~Mzq$dy{He4l0AKJ8Vd$!aR+9g4j|6@^*|Ly@> z`o~ygSiLB)pv&I$47%P{PGA0hsXC#)Ajq`vfVKkC8sjj~k=fXH=Vv2i@NwP%u$&d! z6a(qwfV3J@`!&jWye*;nHyD z!+Via-a7XD;8z))JMlQCN!z_vzcy5%oVc%-0rvS8ZG0?uWkLXg_c|bkp&B!sA*&go zOveXwZm08)8DqBX7P--5Q;U)K>?Kz5rrEM;3WGRd0Fr4fbU*$Q8Q zuw^(EMqU@2KbJcx34DjiNqI`jhMJd8;{RBAn7k^sIm43g`<}SjTfll+PwZU|t<34U z7WHRHozPOzjQggOb|rvQ^^UG9_q}@~M%_wWpZ3pwTJwI*!a(UNR;wW(j_FjKa2K1E zL^0_o$pN4%AcWVm5q=9Q3;K7JEl+%{7qmEyeebA9A>5lF3+(zZm^J#=PhbC8aWl^Dr^1{$o4UeevV zQwe;U(oPljt#E-fVTfV6UL2SfF<$D9d^&)hnfEf8#E!&O|L|(@1N;C=E*&N4dxSBE zeHg~k!9QMlaKuV0-bQv4G-dN4e-$hj+>aAtLUJ#9SxJ;p0;yYQnK;q0+##GEO23(>y6^(`;G<9)>ksm2Fl?d=Z z4E}x+aZfFJsAX5B)Htje&gLida9B(z&vp$D3J`?z*g*_CSrrB33pcyRmZY4VoR+61 zWCm(}H(jo=9i{;?d&`)$pD+F~+Ly;8O&K3s;1Ry-A`SJR@o~CmM=quPV1%&F$@yHO zJLQd*t~H(FgpuDRAj<}jCPqJ>-DXh;sCH|o`ukQ;7BzL_hw7gn=#nPhKi)kx&%jic znL7!*Kk~rPrimKDOuAD3kQQ&RK3LdWs-sAFb(@{GPKmTa zZaz_8keLWh**YJYfB4+@q&2Y+T)spQn~&?dKVu_#HhO1U&t$YJ+Zt+W*a>xLD8*(N zDLbi#Nc4j78A^eVLQIlVfS*(4OsSjAS*|<@7Z$pc`US~snQTa4%7feGx*4b(uzJ){ zHnZlRz4u}T97wn6K6s78k(q)UbL`E{R3F;YvnV-h*crUG1XyX<*i!He!JBFR6 z<(~z&*Eln;>`T9h@`n0t2O<9sIz6TC`if~%bG}B=v?r&zQ`~dd`@E>L#KOP^E8JmuEvfY)Df-2$gxg{FnMxoVDN-~1uWG4 z!w`3p`&`5uzs#+9@nXtt$D2M-o}kr}O~M3_i$QWSsx^44kX)=Kt<$vM z<(}1&qD<;XQlmS@7_aMg^+CG}qVGYh;W4iO*8!y4Cy;*>rnuw~U9kKHc@vVh(g z=UIUQ9@mYnmyd^B*o3gZGEINlb!X=Z-oN2xokgUwNkCip*}y0KDV3NluOn(JdS7MM zR;9)}2$Wuumhykf5ONjFH8#A^jd+#Bj%3!JZVr8$@%Wy12PAYjGDD?YPOvu<$})fS zQ@TCIp^G8cBfF?dE~7z2esN2*CM)YU%P(BZLzviT-FD=3-yNJ|C#AGJPB^%G{+m=5*hAr7-*?d5_MfNnAZf7mcJ% z(>bgofRSS=fL!Bmp;jT|FO)2CU(JMRxS0*K^P^t@kXC8mMsV8If@uvZ;@~NB`oviVp&-Nx# z@+*8zSS_`4x$Y~q1FqizT-+v~td{bW&yZdjERQoqN$*Cz^ZKUcjp|a$JlIO2XNG4M z)o;oAU9(rT-FQ+Id8+Qut`crwjWI4|{ogC-RI2S>!=$fr3anGX7%C@G;O|^BC)y27 z(Q0P}TMXLC;No|yZZ%&SQ-@ZNi_e-II)tfyj5izJ3X%}CA#ibundt`e@VlSSF1~^^ z6I%`ss>+iX@mTF>d?wHTy$~Gwm4`W`kbO*zmNjIrOEC0$!QJCjH8Wk{?3UTz8$Agp za+X`pK$AUfs4WUU`E{pkBlL_LWqqrc%`hsbL+}_R2Ow743x7~+j!D#-<2jH_;3VNh zgZN15=f4Q#eeLK5TzS)FEvqiMAT0gJs@{@F7IoNqyTGUgN6jX#7uzKuvMc7)@Fb= zCZ)x$xlA0dww`nYD2mK(p^GmvKFGQXYDRLz(JwuvWD$wV%acg|_5Eufflr%sT7Ar9 zO|G3p*4T;(_lGLlmtq5PS_}^6?7uo94pzsVT8E1S>uHZk^w0K>kEeECwccH4+7hzXdCj#l{K%Mb^P(t;iE_O@)WJl~N{wGC^u}O^m641q zzqi<1NEZMPu|X7-Mm|%(4)Hr1H}dQ76dxX%yzLrXn@GhlUm+Ji*4kM5%5HkX2yRg; z63jA@!0ZJ(FLsBDfpvsNIWPSE1rQ!y6FcqF`_$1v81IEYj_u%PY^gwAl`CCrtmA%=%-Xvk@t9|AQivbu?$_>1o%fRcQu8RZ-}hN z^laeK{to)8`0DcC7dC z7f*O>;^v%3c$Cy4Adh~Z*n1#@j=QcbCpXUzC{9Y@&-AkXNp zt}kN8S0eM4?=Oy9_#6w~F=sFPF`eC~T&`l>vsvAmSYjxUqeXL5y@+30z6IGX3+hbt z&^VRzmP`z^ z(l=wX?$I2L%2@UVrmjje&ke)Hl7DQt<*b)zMBg4Q1@%BH#X2P6r{Nu4ytW;ZRKC6E z?7_>*&x!znNi?K$VC4vPI@|iob}qr5#U1?L3Dm_&ww9-F|fbeRBJU z>Qh{EpkxMEVS8@fh50qJD29{R6=IZ|;1w^j7tkDbyz!y@KsM24XJB0aqhxtFYOhd#<_;2Ka-P9@7$FDfCe>9v3f;jPTX zySR=oTpL6_XNRn`i`*-dPuXYdS1WU*q5oFkW@D|(`6IF4zCngO9kO&>l}r=rhzHX1 znXFu$k4=w#-?fZCx)@s&k@P&c+y^B5#^9NER8oFpDd~7rzhJVI11xcz&;G-ht2B9X z_1>)RY47t?79!&(3%ecrAq~9ASB`Zg4&JMqoqb<)XI3W;Z#I)lv@)#!#|85hk64wt zFSK`evP}@XV5+4m%o7W^S7$U=4m^FLp)U072WH#AhX?k+@WabzV&ZopPc}WjmuL^n zG}EMoC|#&(1b&UV!;c4_qRaq>KJ?QqOvFA4Uqlj6BW0A|2Qt$=^AtV4xTtzBkLlj~ zB81N+)2-x1PU<&V^gt>607Xj6bq--3(I4jM+&&^RS_hU7u#AlC{JGbUM`F(LM#U%^ z){pSV#Q634*l@=Jhpr>mQR)2onvm2nfwx+dUVvrdk-d_=m5$u1*GZR!<~ZCdXEQnt zeqww&;2uWp0ngtwS8tRqG*trgL+5?LuSQ@ZyRdk2{z#t;qXTm;I5F2qG1$>JW^9$EHQPz z;}7+4_xq+YxAN%!wfB{OZ8TBaP^_g$i&N~zHMGH<;*=nT;_e!(!HQFg6)hCk;!rFl zXrVxHg1ftGAz08Sz4dwDf8hP{e#vh$J3Dh`_w1QHXRf&p^|PE5=&VH%K#h`m4cCCB zR;6qqUi#pRLeH`BW?8a(?2DfhI#v!6_??YPK5f^ zGvIb7^=+y#3Tg^@xFW&QA7V0iQO?da-70W}9@(lHmFOIWR}(&oh|}Sb19VNINP%i@ zpW2dO|0@TzH!~|{RppNrRPQc>T?eN3$v*!8Hemz{!AMrywRi^FmQ7!;Gu5-JOul)J z5ljI4d|7p-3BltLT3UwZQgfyp9CvUuN7-YX;$QVBryE@Gqn7yc@YN))`wrTuN*+T* zDI!QTn@UtoZ+Lm*H43e>{*B7%K45vTFDOd!l`yN|X8oZEi_#+UokZhv_oPn@_7Z0Q#hYDE5rQ=p@vwBOoEb6&!h!oIQjW}rruB-@{CN$4v=n*3t3F3Qdnyr%&Kfc z)JBaB29GzVj^mJ+8;P`T{HZ=3iDsEVNS~C>^^<2D?983{UspyaDC@Q5BC}tDZMve$ zhbb98S%X6c3m@G8=v%_w+J*I-Ck)Fdt+p0iAylrQ{vx)4R-}Mn;+BJp(@d*M2s#m@ zS532F_+VK{g?I5R#0-bUPleymRdwiEqvkV0KqvA%s{En*Nc}20?IM!Pgq<$`POT+x zbusoEr(J0m^CtNxYazAt@9_wJ)mm_hjZ1ISN2Bk&?+$xC?BTIvH;rL0ve}A8^YU1h z+ADbW`V(Eq^ot%j##9dlYIfOxcz^~cEEn!YBP@?i{h7bSW)xgWFN@Oesbp`p(UesC z&dMR2B|8zUVLM((tE+P{+1>S*OC5K&r&C7lC0pi?r`TgAM<#bMqVhMa%^oH@2XH## zV{F6Y+fToWR215K-vpXr*-_dL|8VBBc>G7`JWVC@PEX}di*KMqb3sas7Rra=+B+%` z5_Th-xv^E(04AT86Gpa<8Gx+XU!<9{G+W}o^S}tb^qnkIZu^k^Tct0;j40jXTPhRJ zKe>D{i5?jw^Y#r)d+DvzuNk6skYCuHle2aR{sJfEwu=5#yV-iKL`_)&xuJg=>gtO| z6N|G1P8>S2cC-BO>C@AUESK)}Zt-Vc0UbN^`>gYMK^F=cA5^60yc}%z8Q;9e@W+9#4jy=eqJ*w@$8F6@o~bL8>~6;*DJ3b z2S-*7qRM{gdvo|7@WS_pU#h=^RH^Vp{b@RE`a>~EzZO3~P2V~EI^0vn52cvh`3W+?9*ch0FQ`f^Q!L|E~ zO7C%hgvwBVKcrFXe%aPlo?p7`EZp;$T%jv@E&WI33StOS1#n~knflyfMSOn{7+CqK z{rS|}TU2xzu}d-P33aGp?iaw0>S|rigSb4b6W~=Dc$a8%=L`HbzxD9in#1N3oU496 zSfXgt*=p)7qiuWB%{qdwe#?tsoD@8l8Kyq6{rnyrOlhe3`HAB@275V`(uNA-JPnQn z&2c63cW6-J5cQ$;_>>4u^qoVS}4*6yaLilxf4R#i=5T9_!Cj*dSTN!+HiC2*B zvxAo{DVqmU047!0gMhUipY@F4Y_pAtx}-ONuwuIt6_B%-!ewc8(YH=2>i~%>RWXHN zRPFM77x&%u{!%-;ZKpIi;m8_(0xBVtR@mU`1ZfnOfo@j5YCwSg%!h!I1|K%g7ePg7 zkpY*&tM5hZbRuEm`kCSH$7I!%T<1S~Dt_>b&lDI%Uq@}9Q zZbnf4yl;GT4l2!PNt0@esBMnU#oB(^%E_4&A4xwYXHTty2+gNl7%_?YzDHD`!00Q7 zN@9}_Kr2UBVk9d^ak0=V_aX=D(pk20a<$j$>TSKdgH`bE=vw1-Svf3H)~`EFC1)F$ zXjg1T2N;*={OrN7X{s>Z=!hLlU6Nsio$ZHWn#3CC#Qg&Ig7VXMFD>t&3SrJ9%kL}B7cx;<|n&@!}xs0}=5OpS4T8cRAMuc}T4#bpR2 zNJT><%lM52xWuW1th^--J{N~ARVURGjg!(5p4!5 z%$7D79;NI?*s#y_G{4;L>~m9!qVdUr+b2w?7-*s-X;4nN;P zm$YMk%+?PCewO_tEBTh5Td42h^noosi^JL~@mgA`v%>LCV_ zuqpCZ58Fix5gBlc9Z?Ep24uuqqOB>lvZ27Hr#V^hNbzN-+8A$3QLbe1KHj_e`-)r4(upDD=>5Bo zaPh-bbens&Or4mMxud&})iE!m)Q!61>N3ScoGvhYO!+2%exdmQ1C?b6#p!dKG1N!E92#T7c4EK^M=47>jK;AFX zl_0mCfCm^;uyg}*SerfO@NInm#S15dy$`<*SUIn%?z=3y(%1XUcWe>o^~>*ry>z!J zv&NpoKD6@O_Zc?#ucLKzppJfwU&o{`1QS0j-cV)vt#(`rEnmW?Kt5xLja0s@ z|1e@I`SK1Akcxuc1;;N?0of(I)sn_`#?;3yz7;ZA`)^Qey5}i&Xh8Fm88Y(mH9W5< z+yyUF(rS~8sJ=nj0tJ*nC}spBky1cx4RcT`AmOtvk4`eri?>1AHaHVH@m1vpq3&Hr z42k1S8)?cxv8B8L>9&jxARU^c+>JIgof(f`y_&X&V)G{){6vpU zje13_&ftSU8B0~ui_G&rZn0bU4DW`7^>r_-o@~v_#{8(2AU7pASQqQ0_o!ZJzoMmcrl=NUCF|lTUebxXKB(E)e8wr z!>sf=Q7G=FUv7lrs7NldQo91RZ6>{0Ueob%ZI@RX1=Nc;2zf%E zDx9b;Hnk*5iY-0i)1`4@#nm&osM<}g(L6UQz;4y;&pLQl?7cluu$H}aOLi`P?M%I0 zx<+_^LOdT;tTk(^`4h2`5uTpxWC-!Vlct&i6>Zk|D+CC0_V0v5>o(Nz|CpUbEZtpPQ{GJ7 zcSlqt++1c49j|{Q(k8e1WIN(2dbML)T=2sJv(m}zI`1eYNs@UF#dDq(uJ8DX>Nm{T6(zx zZ^`lSot&(#BafDJ0JZ&CS@7LI!a^(jltQz6aP6JZyi z4|gq1Z_4S2gIrjH`>02Om4#jwxQRGEE*TO^2$Q654DyfCOe`3VYAU-S`-C@0Xtiaz zk(&O4s2Zc(V|`Xyqc+%$)OpK0Vjw*->!$jcTcBxDZUbSmQk1Nd(Bh5d+V`YcXK-|q zH?*Q7?tJ#Xt%66C-e>{_W1x$C@4n!#-IZPVRuT2-do(Z8Xoj*4s<6|rI<6}IW9E}S zg-8tb)0I#qd!J@g#e7p0GgEmbTVv%ClFD9-Z7?I_f=-E3%vOcioEMrchlWG=kj+6C zMTK#Ls*MgO2JsxHJUBI^%s3U4%RUd~=*X|MMzDxZIZ)ZsQCn2`M72fK;X|3WNWQ(5 zBRhAQ3)4K*gKMbdELW)Lrlsv{Q+2GIE+cSgt&5`TLQ5Wcz5Zr4nsES^=*J<`oXIqIIQ&3FW=(Tb=Dg@hdQFmwJSRWD%4tLBjvXECk|SO(a*rJlUa;41 zi3<@s?3C1gVQTUt3s9?FVd9(*86I$P-nu--uH~Q6=v9X1D$h-!Vx*>2*j%Xv;8V)t zQb(JPwVcYar(W&0j1VpMuSq3*7s`GTr#P1|Nd=A;y}5iP={6(qH@=bik?x%Ki0&-~ z3=$POlNbg=9)#Vs;Gv0=JnTRC=KIP5cbZKJ=3dYNv>kT&GD2@CCVC~yh$to`{$TEv z&v~pHe%5kNi09iW4^dpx{@HFEx=2PunB?kEvUjnhUz|5ur?UM_`sFsGGjrZ*^^F;U zkNQ|$RndNtW~O^yo~dz7m?NBFNvfC$yxM$!O5eYP1P+dh~OfK3fG@ghR( z^U{^@@(@7BvHHGMs}nFD;}5XzLgQBVWFt7kldY>)Xbj2D~M?0)D=yhK#D6s`V< z#T0jdD<3w;o@{J{d7VTQ33)+-C;JkVE42l_O&ytE*NT7cknO9Jo=p<}d<*+n?LsqQRHH@qTd^}9;Xr`S*Qb&Rr`<)u-gg`WMlwR;PHqc< z-IW7)KL^!eQ*G(lbn68OH{n}}*IawLdPyI6iun31oo8d3Z({NAbzBN|Y09Q831?g0 zv)@OHRSAcjWZ!;t*fP%Xy8*~WGj1G)y*p?RXyY-c7gO_n5!!z(+0mEZE&OX-eK$rs zwk0n|Ke_q@KVWEas*FWD70@D>!b|!+bi!b?*dO|ML8EJSV5T@ysGCE7H(}L9h>X7o zwfPh+zn}np84;pt6+@&isS}%3Fk|sY9+=!Jm4##>77mPR-;mQ$>FDURhy?Z@cYls? z?r#epi&n#!Ad$%skD+KUPt&2LLHSWbd0APlJ^mgZ=Lh|R_<)0}@-=q?-qq9PF)c3F zUz0)a}!)N)uM!c z3iqx)!MZRy1sja$0}LVe$GtP0JwxAGctF%Uw4t+9w)+%6qbUvgcutaw%=|iwz_w9m zwZro>L*ZkQ=Fo(UR2kJ!nlb#^aTGw1uWpooxiycfN|#u%w}*jqz+;W)RCF6`oA1{e z;q;2dM@~ZTd|THLw>3|u2-I$0^PuYXH62N zG;4u+`jca9ii0;Nxv{Bf-GZ?kq;VMXiYM`jWpk6H;P z0q8cwkLJ3lv+j6oBFH}o;(>pA9}^sI*6rm! z;o}1@OyOMSj+>6tHRdd4dXM&!7ef8i@`%#Y6kgdcsyLUV5chB^nHty8KLcBmm2Y^E z+?v<>eboZQ6r?+2L8Jxb9@TAaPGU{$%vtVa!Dj$FzF&dUZ&7(YQUi!SGKaucNk++@ z>!8_Yb{CgQ%|z8+OYD?(7Y|T#()0NnL&7g>TL2~GI}P> zWG6Dox_zAV=CUyGjIk9DTSug#7TZnO$2pEIGC;|rhkCMrjgF{CMq692hPVO6zu8Y4hDJe_`O{`X@_uH)uo zBxBGYgafjnq~-Xa@P@c6z(2#Cr#6~8wl(GH`3!j4EH<0@G$RoTaQ)7eNol~K3(JU+ zBLt%o^%dILriGT4vV9c8)BI4x7DQK-M&|V^vy}VRCiT@V*=pj`=-c4G^u(OS;`M%M zcljrK?bjqBQk4YTieQi|V+%pBF-v!dxA+?inJ#AIqG)+WiEP5y?%-a_cxXABpzB?e z@$)A_EuhR9%Z|0Pl{0VPvuF*R{)_EOt>*_Ydf0F8+~maG?yyA3Ga6SWOT^+? ze%vl#!!11i<0n#TX}v{l99iJ)V{K?iEh8f%L`@JMAO9j%*-MP}5tl5zoH|Q4h?)TB zw|qvW;xXrU^j*{h;E0G6E*3HFlHj`ZTsG99n<=*> zEKeHQ`SyD?TSqR%i9M%gsC_sfB{E1?3G?Vhwehq=#KCs7muw`GuqPmlMNlRJb^qF)^{rnvI_mgkh+Ry+|KmVrB*)dz!|b>7APYTit1MllK#m z*J>-`%-Hce@Rqubb1Wv7cDCph+q% zTgR;MJO{2)w*&dY+iay1MZD%vbu(g~HtIh+e%GAOI@@qm5zj12TO>psBR4R!KKre? z*-I>|Q(1rhOTRU5NlA%1G^-Rf24lUEaml}5$wCKr?_>L*t*3t5%Fmx))H``5eW2ONEDh<1e{2m}y~Yx$a@3z(w?ZVX!NjigoBn!})** z0Q%UCP>IIy<0$b#6N;tWT&0WIAe&6Q3M;6uWKSe7FHa~_Rj){^KZgz1x|;Iqgc3^I zKIU@G$P+2;);+1y=DYZ3qY=A>L^O9@XvN;8`=!~!L&*|4v;G#C4e>))UUPhoBlar9 z9fK$~vx`c)nR*02_reUTHN>=`1H~31A|m1d|1cIKsDes%m z`=M~pu-G}Tu^?fQ9)~u2ylt)*%omqtn#U)YfR6=#O^Sjm_9RWN7qo2&vyS~Xi zfWJY>R+(YD_YbP(4Yfq3ab3Vk=Qpw`wawdu3TU*1)Ys%T{QC-1BJ57z-FR8$Ce;V0YAOP$l1`P&UtM))Q|B1!|W`wLOzVN7V`U z=$gw!|F5p=R`oNjG9Dsk)P><#@dl6nv6O%7i>zi`Ytmn)?oV_3`zIWiXqxEXe}A1+ zmwI6Ee-$xsD4K%)Zswm>Ww9QCAN|++@1yYIKmHHle^-4<%2f4RqW^t$?t9GtHMRbE h^8X9>|K!fy1SE8RhXr)N5`GgjypmIut&lc-|35WHLlXc1 literal 0 HcmV?d00001 diff --git a/docs/asb/transactions-tab.png b/docs/asb/transactions-tab.png new file mode 100644 index 0000000000000000000000000000000000000000..7b0532aff6b4580ca2b1952931f293e8f5019103 GIT binary patch literal 111456 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 literal 0 HcmV?d00001 From 3bb6f060a049520b8d7d6fad48f54a9f5eb0fc85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 11:30:15 +0000 Subject: [PATCH 32/41] build(deps): bump thiserror from 1.0.60 to 1.0.61 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.60 to 1.0.61. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.60...1.0.61) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed8efa66..e46ceb7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4438,18 +4438,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", From 928fe33532aa93b740e942663a827ca39871665c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 11:31:28 +0000 Subject: [PATCH 33/41] build(deps): bump anyhow from 1.0.83 to 1.0.86 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.83 to 1.0.86. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.83...1.0.86) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed8efa66..85f1523c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,9 +98,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arrayref" From 5ff46be2793bc62278ded2a4c02a4502ea6e99ba Mon Sep 17 00:00:00 2001 From: yamabiiko Date: Wed, 22 May 2024 16:12:58 +0300 Subject: [PATCH 34/41] RPC server for API Interface (#1276) * saving: implementing internal api shared by cli and rpc server * writing async rpc methods and using arc for shared struct references * cleaning up, renamed Init to Context * saving: cleaning up and initial work for tests * Respond with bitcoin withdraw txid * Print RPC server address * Cleanup, formatting, add `get_seller`, `get_swap_start_date` RPC endpoints * fixing tests in cli module * uncommenting and fixing more tests * split api module and propagate errors with rpc server * moving methods to api and validating addresses for rpc * add broadcast channel to handle shutdowns gracefully and prepare for RPC server test * added files * Update rpc.rs * adding new unfinished RPC tests * updating rpc-server tests * fixing warnings * fixing formatting and cargo clippy warnings * fix missing import in test * fix: add data_dir to config to make config command work * set server listen address manually and return file locations in JSON on Config * Add called api method and swap_id to tracing for context, reduced boilerplate * Pass server_address properly to RpcServer * Update Cargo.lock * dprint fmt * Add cancel_refund RPC endpoint * Combine Cmd and Params * Disallow concurrent swaps * Use RwLock instead of Mutex to allow for parallel reads and add get_current_swap endpoint * Return wallet descriptor to RPC API caller * Append all cli logs to single log file After careful consideration, I've concluded that it's not practical/possible to ensure that the previous behaviour (one log file per swap) is preserved due to limitations of the tracing-subscriber crate and a big in the built in JSON formatter * Add get_swap_expired_timelock timelock, other small refactoring - Add get_swap_expired_timelock endpoint to return expired timelock if one exists. Fails if bitcoin lock tx has not yet published or if swap is already finished. - Rename current_epoch to expired_timelock to enforce consistent method names - Add blocks left until current expired timelock expires (next timelock expires) to ExpiredTimelock struct - Change .expect() to .unwrap() in rpc server method register because those will only fail if we register the same method twice which will never happen * initiating swaps in a separate task and handling shutdown signals with broadcast queues * Replace get_swap_start_date, get_seller, get_expired_timelock with one get_swap_info rpc method * WIP: Struct for concurrent swaps manager * Ensure correct tracing spans * Add note regarding Request, Method structs * Update request.rs * Add tracing span attribute log_reference_id to logs caused by rpc call * Sync bitcoin wallet before initial max_giveable call * use Span::current() to pass down to tracing span to spawned tasks * Remove unused shutdown channel * Add `get_monero_recovery_info` RPC endpoint - Add `get_monero_recovery_info` RPC endpoint - format PrivateViewKey using Display * Rename `Method::RawHistory` to `Method::GetRawStates` * Wait for swap to be suspended after sending signal * Remove notes * Add tracing span attribute log_reference_id to logs caused by rpc call * Sync bitcoin wallet before initial max_giveable call * use Span::current() to pass down to tracing span to spawned tasks * Remove unused shutdown channel * Add `get_monero_recovery_info` RPC endpoint - Add `get_monero_recovery_info` RPC endpoint - format PrivateViewKey using Display * Rename `Method::RawHistory` to `Method::GetRawStates` * Wait for swap to be suspended after sending signal * Return additonal info on GetSwapInfo * Update wallet.rs * fix compile issues for tests and use serial_test crate * fix rpc tests, only check for RPC errors and not returned values * Rename `get_raw_history` tp `get_raw_states` * Fix typo in rpc server stopped tracing log * Remove unnecessary success property on suspend_current_swap response * fixing test_cli_arguments and other tests * WIP: RPC server integration tests * WIP: Integration tests for RPC server * Update rpc tests * fix compile and warnings in tests/rpc.rs * test: fix assert * clippy --fix * remove otp file * cargo clippy fixes * move resume swap initialization code out of spawned task * Use `in_current_span` to pass down tracing span to spawned tasks * moving buy_xmr initialization code out of spawned tasks * cargo fmt * Moving swap initialization code inside tokio select block to handle swap lock release logic * Remove unnecessary swap suspension listener from determine_btc_to_swap call in BuyXmr * Spawn event loop before requesting quote * Release swap lock after receiving shutdown signal * Remove inner tokio::select in BuyXmr and Resume * Improve debug text for swap resume * Return error to API caller if bid quote request fails * Print error if one occurs during process invoked by API call * Return bid quote to API caller * Use type safe query! macro for database retrieval of states * Return tx_lock_fee to API caller on GetSwapInfo call Update request.rs * Allow API caller to retrieve last synced bitcoin balane and avoid costly sync * Return restore height on MoneroRecovery command to API Caller * Include entire error cause-chain in API response * Add span to bitcoin wallet logs * Log event loop connection properties as tracing fields * Wait for background tasks to complete before exiting CLI * clippy * specify sqlx patch version explicitly * remove mem::forget and replace with _guard * ci: add rpc test job * test: wrap rpc test in #[cfg(test)] * add missing tokio::test attribute * fix and merge rpc tests, parse uuuid and multiaddr from serde_json value * default Tor socks port to 9050, Cargo fmt * Update swap/sqlite_dev_setup.sh: add version Co-authored-by: Byron Hambly * ci: free up space on ubuntu test job * Update swap/src/bitcoin/wallet.rs Co-authored-by: Byron Hambly * Update swap/src/bitcoin/wallet.rs Co-authored-by: Byron Hambly * fmt --------- Co-authored-by: binarybaron <86064887+binarybaron@users.noreply.github.com> Co-authored-by: Byron Hambly --- .github/workflows/ci.yml | 11 + Cargo.lock | 230 ++++- swap/Cargo.toml | 8 +- swap/sqlite_dev_setup.sh | 3 +- swap/sqlx-data.json | 96 +- swap/src/api.rs | 460 ++++++++++ swap/src/api/request.rs | 930 ++++++++++++++++++++ swap/src/bin/swap.rs | 706 +-------------- swap/src/bitcoin.rs | 66 +- swap/src/bitcoin/cancel.rs | 12 + swap/src/bitcoin/lock.rs | 12 +- swap/src/bitcoin/timelocks.rs | 6 +- swap/src/bitcoin/wallet.rs | 64 +- swap/src/cli/cancel_and_refund.rs | 6 +- swap/src/cli/command.rs | 1347 ++++++++++++++--------------- swap/src/cli/event_loop.rs | 13 +- swap/src/cli/tracing.rs | 82 +- swap/src/database/sqlite.rs | 87 +- swap/src/lib.rs | 2 + swap/src/monero.rs | 53 ++ swap/src/protocol.rs | 4 + swap/src/protocol/alice/swap.rs | 6 +- swap/src/protocol/bob/state.rs | 19 +- swap/src/protocol/bob/swap.rs | 12 +- swap/src/rpc.rs | 31 + swap/src/rpc/methods.rs | 236 +++++ swap/src/seed.rs | 2 +- swap/tests/harness/mod.rs | 36 +- swap/tests/rpc.rs | 436 ++++++++++ 29 files changed, 3471 insertions(+), 1505 deletions(-) create mode 100644 swap/src/api.rs create mode 100644 swap/src/api/request.rs create mode 100644 swap/src/rpc.rs create mode 100644 swap/src/rpc/methods.rs create mode 100644 swap/tests/rpc.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a41dd6d..da82b266 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -175,6 +175,17 @@ jobs: - name: Run test ${{ matrix.test_name }} run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture + rpc_tests: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4.1.1 + + - uses: Swatinem/rust-cache@v2.7.1 + + - name: Run RPC server tests + run: cargo test --package swap --all-features --test rpc -- --nocapture + check_stable: runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index 0767d24f..1c9b370c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + [[package]] name = "async-trait" version = "0.1.80" @@ -308,6 +317,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +dependencies = [ + "serde", +] + [[package]] name = "big-bytes" version = "1.0.0" @@ -519,6 +537,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bstr" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.6.1" @@ -1479,6 +1507,19 @@ dependencies = [ "url", ] +[[package]] +name = "globset" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + [[package]] name = "h2" version = "0.3.18" @@ -1566,9 +1607,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" [[package]] name = "hex" @@ -1818,13 +1859,13 @@ checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.1", - "rustix", - "windows-sys 0.48.0", + "hermit-abi 0.3.8", + "libc", + "windows-sys 0.52.0", ] [[package]] @@ -1893,6 +1934,115 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "jsonrpsee" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d291e3a5818a2384645fd9756362e6d89cf0541b0b916fa7702ea4a9833608e" +dependencies = [ + "jsonrpsee-core", + "jsonrpsee-server", + "jsonrpsee-types", + "jsonrpsee-ws-client", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965de52763f2004bc91ac5bcec504192440f0b568a5d621c59d9dbd6f886c3fb" +dependencies = [ + "futures-util", + "http 0.2.11", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project 1.0.5", + "rustls-native-certs 0.6.3", + "soketto", + "thiserror", + "tokio", + "tokio-rustls 0.23.1", + "tokio-util", + "tracing", + "webpki-roots 0.22.2", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e70b4439a751a5de7dd5ed55eacff78ebf4ffe0fc009cb1ebb11417f5b536b" +dependencies = [ + "anyhow", + "arrayvec", + "async-lock", + "async-trait", + "beef", + "futures-channel", + "futures-timer", + "futures-util", + "globset", + "hyper 0.14.28", + "jsonrpsee-types", + "parking_lot 0.12.0", + "rand 0.8.3", + "rustc-hash", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb69dad85df79527c019659a992498d03f8495390496da2f07e6c24c2b356fc" +dependencies = [ + "futures-channel", + "futures-util", + "http 0.2.11", + "hyper 0.14.28", + "jsonrpsee-core", + "jsonrpsee-types", + "serde", + "serde_json", + "soketto", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd522fe1ce3702fd94812965d7bb7a3364b1c9aba743944c5a00529aae80f8c" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b83daeecfc6517cfe210df24e570fb06213533dfb990318fae781f4c7119dd9" +dependencies = [ + "http 0.2.11", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", +] + [[package]] name = "keccak" version = "0.1.0" @@ -3470,6 +3620,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -3553,6 +3709,18 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.0" @@ -3723,6 +3891,21 @@ dependencies = [ "pest", ] +[[package]] +name = "sequential-macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5facc5f409a55d25bf271c853402a00e1187097d326757043f5dd711944d07" + +[[package]] +name = "sequential-test" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d9c0d773bc7e7733264f460e5dfa00b2510421ddd6284db0749eef8dfb79e9" +dependencies = [ + "sequential-macro", +] + [[package]] name = "serde" version = "1.0.202" @@ -3936,9 +4119,9 @@ checksum = "0f0242b8e50dd9accdd56170e94ca1ebd223b098eb9c83539a6e367d0f36ae68" [[package]] name = "similar" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" [[package]] name = "slab" @@ -4019,14 +4202,15 @@ dependencies = [ [[package]] name = "soketto" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "083624472e8817d44d02c0e55df043737ff11f279af924abdf93845717c2b75c" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" dependencies = [ "base64 0.13.1", "bytes", "flate2", "futures", + "http 0.2.11", "httparse", "log", "rand 0.8.3", @@ -4280,6 +4464,8 @@ dependencies = [ "hex", "hyper 1.3.1", "itertools 0.13.0", + "jsonrpsee", + "jsonrpsee-core", "libp2p", "mockito", "monero", @@ -4294,6 +4480,7 @@ dependencies = [ "reqwest", "rust_decimal", "rust_decimal_macros", + "sequential-test", "serde", "serde_cbor", "serde_json", @@ -4657,6 +4844,7 @@ checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite 0.2.13", "tokio", @@ -4723,6 +4911,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.1" @@ -4735,6 +4940,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite 0.2.13", "tracing-attributes", "tracing-core", @@ -4921,7 +5127,7 @@ dependencies = [ "log", "rand 0.8.3", "rustls 0.19.0", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "sha-1", "thiserror", "url", diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 22705715..b73d61bb 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -32,6 +32,8 @@ ed25519-dalek = "1" futures = { version = "0.3", default-features = false } hex = "0.4" itertools = "0.13" +jsonrpsee = { version = "0.16.2", features = [ "server" ] } +jsonrpsee-core = "0.16.2" libp2p = { version = "0.42.2", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous", "identify" ] } monero = { version = "0.12", features = [ "serde_support" ] } monero-rpc = { path = "../monero-rpc" } @@ -49,12 +51,12 @@ serde_json = "1" serde_with = { version = "1", features = [ "macros" ] } sha2 = "0.10" sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde", "secp256k1", "alloc" ] } -sqlx = { version = "0.6", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] } +sqlx = { version = "0.6.3", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] } structopt = "0.3" strum = { version = "0.26", features = [ "derive" ] } thiserror = "1" time = "0.3" -tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] } +tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net", "parking_lot" ] } tokio-socks = "0.5" tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] } tokio-util = { version = "0.7", features = [ "io", "codec" ] } @@ -78,10 +80,12 @@ zip = "0.5" bitcoin-harness = "0.2.2" get-port = "3" hyper = "1.3" +jsonrpsee = { version = "0.16.2", features = [ "ws-client" ] } mockito = "1.3.0" monero-harness = { path = "../monero-harness" } port_check = "0.2" proptest = "1" +sequential-test = "0.2.4" serde_cbor = "0.11" serial_test = "3.0" spectral = "0.6" diff --git a/swap/sqlite_dev_setup.sh b/swap/sqlite_dev_setup.sh index 67d2c9da..a30adaff 100755 --- a/swap/sqlite_dev_setup.sh +++ b/swap/sqlite_dev_setup.sh @@ -1,7 +1,8 @@ #!/bin/bash # run this script from the swap dir -# make sure you have sqlx-cli installed: cargo install sqlx-cli +# make sure you have sqlx-cli installed: cargo install --version 0.6.3 sqlx-cli +# it's advised for the sqlx-cli to be the same version as specified in cargo.toml # this script creates a temporary sqlite database # then runs the migration scripts to create the tables (migrations folder) diff --git a/swap/sqlx-data.json b/swap/sqlx-data.json index fd50cd87..f24a50e6 100644 --- a/swap/sqlx-data.json +++ b/swap/sqlx-data.json @@ -28,6 +28,24 @@ }, "query": "\n insert into peer_addresses (\n peer_id,\n address\n ) values (?, ?);\n " }, + "0d465a17ebbb5761421def759c73cad023c30705d5b41a1399ef79d8d2571d7c": { + "describe": { + "columns": [ + { + "name": "start_date", + "ordinal": 0, + "type_info": "Text" + } + ], + "nullable": [ + true + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT min(entered_at) as start_date\n FROM swap_states\n WHERE swap_id = ?\n " + }, "1ec38c85e7679b2eb42b3df75d9098772ce44fdb8db3012d3c2410d828b74157": { "describe": { "columns": [ @@ -62,6 +80,30 @@ }, "query": "\n insert into peers (\n swap_id,\n peer_id\n ) values (?, ?);\n " }, + "3f2bfdd2d134586ccad22171cd85a465800fc5c4fdaf191d206974e530240c87": { + "describe": { + "columns": [ + { + "name": "swap_id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "state", + "ordinal": 1, + "type_info": "Text" + } + ], + "nullable": [ + false, + false + ], + "parameters": { + "Right": 0 + } + }, + "query": "\n SELECT swap_id, state\n FROM swap_states\n " + }, "50a5764546f69c118fa0b64120da50f51073d36257d49768de99ff863e3511e0": { "describe": { "columns": [], @@ -90,24 +132,6 @@ }, "query": "\n SELECT state\n FROM swap_states\n WHERE swap_id = ?\n ORDER BY id desc\n LIMIT 1;\n\n " }, - "a0eb85d04ee3842c52291dad4d225941d1141af735922fcbc665868997fce304": { - "describe": { - "columns": [ - { - "name": "address", - "ordinal": 0, - "type_info": "Text" - } - ], - "nullable": [ - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "\n SELECT address\n FROM peer_addresses\n WHERE peer_id = ?\n " - }, "b703032b4ddc627a1124817477e7a8e5014bdc694c36a14053ef3bb2fc0c69b0": { "describe": { "columns": [], @@ -135,5 +159,41 @@ } }, "query": "\n SELECT address\n FROM monero_addresses\n WHERE swap_id = ?\n " + }, + "d78acba5eb8563826dd190e0886aa665aae3c6f1e312ee444e65df1c95afe8b2": { + "describe": { + "columns": [ + { + "name": "address", + "ordinal": 0, + "type_info": "Text" + } + ], + "nullable": [ + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT DISTINCT address\n FROM peer_addresses\n WHERE peer_id = ?\n " + }, + "e05620f420f8c1022971eeb66a803323a8cf258cbebb2834e3f7cf8f812fa646": { + "describe": { + "columns": [ + { + "name": "state", + "ordinal": 0, + "type_info": "Text" + } + ], + "nullable": [ + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "\n SELECT state\n FROM swap_states\n WHERE swap_id = ?\n " } } \ No newline at end of file diff --git a/swap/src/api.rs b/swap/src/api.rs new file mode 100644 index 00000000..ca3ae21d --- /dev/null +++ b/swap/src/api.rs @@ -0,0 +1,460 @@ +pub mod request; +use crate::cli::command::{Bitcoin, Monero, Tor}; +use crate::database::open_db; +use crate::env::{Config as EnvConfig, GetConfig, Mainnet, Testnet}; +use crate::fs::system_data_dir; +use crate::network::rendezvous::XmrBtcNamespace; +use crate::protocol::Database; +use crate::seed::Seed; +use crate::{bitcoin, cli, monero}; +use anyhow::{bail, Context as AnyContext, Error, Result}; +use futures::future::try_join_all; +use std::fmt; +use std::future::Future; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::sync::{Arc, Once}; +use tokio::sync::{broadcast, broadcast::Sender, Mutex, RwLock}; +use tokio::task::JoinHandle; +use url::Url; + +static START: Once = Once::new(); + +#[derive(Clone, PartialEq, Debug)] +pub struct Config { + tor_socks5_port: u16, + namespace: XmrBtcNamespace, + server_address: Option, + pub env_config: EnvConfig, + seed: Option, + debug: bool, + json: bool, + data_dir: PathBuf, + is_testnet: bool, +} + +use uuid::Uuid; + +#[derive(Default)] +pub struct PendingTaskList(Mutex>>); + +impl PendingTaskList { + pub async fn spawn(&self, future: F) + where + F: Future + Send + 'static, + T: Send + 'static, + { + let handle = tokio::spawn(async move { + let _ = future.await; + }); + + self.0.lock().await.push(handle); + } + + pub async fn wait_for_tasks(&self) -> Result<()> { + let tasks = { + // Scope for the lock, to avoid holding it for the entire duration of the async block + let mut guard = self.0.lock().await; + guard.drain(..).collect::>() + }; + + try_join_all(tasks).await?; + + Ok(()) + } +} + +pub struct SwapLock { + current_swap: RwLock>, + suspension_trigger: Sender<()>, +} + +impl SwapLock { + pub fn new() -> Self { + let (suspension_trigger, _) = broadcast::channel(10); + SwapLock { + current_swap: RwLock::new(None), + suspension_trigger, + } + } + + pub async fn listen_for_swap_force_suspension(&self) -> Result<(), Error> { + let mut listener = self.suspension_trigger.subscribe(); + let event = listener.recv().await; + match event { + Ok(_) => Ok(()), + Err(e) => { + tracing::error!("Error receiving swap suspension signal: {}", e); + bail!(e) + } + } + } + + pub async fn acquire_swap_lock(&self, swap_id: Uuid) -> Result<(), Error> { + let mut current_swap = self.current_swap.write().await; + if current_swap.is_some() { + bail!("There already exists an active swap lock"); + } + + tracing::debug!(swap_id = %swap_id, "Acquiring swap lock"); + *current_swap = Some(swap_id); + Ok(()) + } + + pub async fn get_current_swap_id(&self) -> Option { + *self.current_swap.read().await + } + + /// Sends a signal to suspend all ongoing swap processes. + /// + /// This function performs the following steps: + /// 1. Triggers the suspension by sending a unit `()` signal to all listeners via `self.suspension_trigger`. + /// 2. Polls the `current_swap` state every 50 milliseconds to check if it has been set to `None`, indicating that the swap processes have been suspended and the lock released. + /// 3. If the lock is not released within 10 seconds, the function returns an error. + /// + /// If we send a suspend signal while no swap is in progress, the function will not fail, but will return immediately. + /// + /// # Returns + /// - `Ok(())` if the swap lock is successfully released. + /// - `Err(Error)` if the function times out waiting for the swap lock to be released. + /// + /// # Notes + /// The 50ms polling interval is considered negligible overhead compared to the typical time required to suspend ongoing swap processes. + pub async fn send_suspend_signal(&self) -> Result<(), Error> { + const TIMEOUT: u64 = 10_000; + const INTERVAL: u64 = 50; + + let _ = self.suspension_trigger.send(())?; + + for _ in 0..(TIMEOUT / INTERVAL) { + if self.get_current_swap_id().await.is_none() { + return Ok(()); + } + tokio::time::sleep(tokio::time::Duration::from_millis(INTERVAL)).await; + } + + bail!("Timed out waiting for swap lock to be released"); + } + + pub async fn release_swap_lock(&self) -> Result { + let mut current_swap = self.current_swap.write().await; + if let Some(swap_id) = current_swap.as_ref() { + tracing::debug!(swap_id = %swap_id, "Releasing swap lock"); + + let prev_swap_id = *swap_id; + *current_swap = None; + drop(current_swap); + Ok(prev_swap_id) + } else { + bail!("There is no current swap lock to release"); + } + } +} + +impl Default for SwapLock { + fn default() -> Self { + Self::new() + } +} + +// workaround for warning over monero_rpc_process which we must own but not read +#[allow(dead_code)] +pub struct Context { + pub db: Arc, + bitcoin_wallet: Option>, + monero_wallet: Option>, + monero_rpc_process: Option, + pub swap_lock: Arc, + pub config: Config, + pub tasks: Arc, +} + +#[allow(clippy::too_many_arguments)] +impl Context { + pub async fn build( + bitcoin: Option, + monero: Option, + tor: Option, + data: Option, + is_testnet: bool, + debug: bool, + json: bool, + server_address: Option, + ) -> Result { + let data_dir = data::data_dir_from(data, is_testnet)?; + let env_config = env_config_from(is_testnet); + + let seed = Seed::from_file_or_generate(data_dir.as_path()) + .context("Failed to read seed in file")?; + + let bitcoin_wallet = { + if let Some(bitcoin) = bitcoin { + let (bitcoin_electrum_rpc_url, bitcoin_target_block) = + bitcoin.apply_defaults(is_testnet)?; + Some(Arc::new( + init_bitcoin_wallet( + bitcoin_electrum_rpc_url, + &seed, + data_dir.clone(), + env_config, + bitcoin_target_block, + ) + .await?, + )) + } else { + None + } + }; + + let (monero_wallet, monero_rpc_process) = { + if let Some(monero) = monero { + let monero_daemon_address = monero.apply_defaults(is_testnet); + let (wlt, prc) = + init_monero_wallet(data_dir.clone(), monero_daemon_address, env_config).await?; + (Some(Arc::new(wlt)), Some(prc)) + } else { + (None, None) + } + }; + + let tor_socks5_port = tor.map_or(9050, |tor| tor.tor_socks5_port); + + START.call_once(|| { + let _ = cli::tracing::init(debug, json, data_dir.join("logs")); + }); + + let context = Context { + db: open_db(data_dir.join("sqlite")).await?, + bitcoin_wallet, + monero_wallet, + monero_rpc_process, + config: Config { + tor_socks5_port, + namespace: XmrBtcNamespace::from_is_testnet(is_testnet), + env_config, + seed: Some(seed), + server_address, + debug, + json, + is_testnet, + data_dir, + }, + swap_lock: Arc::new(SwapLock::new()), + tasks: Arc::new(PendingTaskList::default()), + }; + + Ok(context) + } + + pub async fn for_harness( + seed: Seed, + env_config: EnvConfig, + db_path: PathBuf, + bob_bitcoin_wallet: Arc, + bob_monero_wallet: Arc, + ) -> Self { + let config = Config::for_harness(seed, env_config); + + Self { + bitcoin_wallet: Some(bob_bitcoin_wallet), + monero_wallet: Some(bob_monero_wallet), + config, + db: open_db(db_path) + .await + .expect("Could not open sqlite database"), + monero_rpc_process: None, + swap_lock: Arc::new(SwapLock::new()), + tasks: Arc::new(PendingTaskList::default()), + } + } +} + +impl fmt::Debug for Context { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} + +async fn init_bitcoin_wallet( + electrum_rpc_url: Url, + seed: &Seed, + data_dir: PathBuf, + env_config: EnvConfig, + bitcoin_target_block: usize, +) -> Result { + let wallet_dir = data_dir.join("wallet"); + + let wallet = bitcoin::Wallet::new( + electrum_rpc_url.clone(), + &wallet_dir, + seed.derive_extended_private_key(env_config.bitcoin_network)?, + env_config, + bitcoin_target_block, + ) + .await + .context("Failed to initialize Bitcoin wallet")?; + + wallet.sync().await?; + + Ok(wallet) +} + +async fn init_monero_wallet( + data_dir: PathBuf, + monero_daemon_address: String, + env_config: EnvConfig, +) -> Result<(monero::Wallet, monero::WalletRpcProcess)> { + let network = env_config.monero_network; + + const MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME: &str = "swap-tool-blockchain-monitoring-wallet"; + + let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?; + + let monero_wallet_rpc_process = monero_wallet_rpc + .run(network, Some(monero_daemon_address)) + .await?; + + let monero_wallet = monero::Wallet::open_or_create( + monero_wallet_rpc_process.endpoint(), + MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME.to_string(), + env_config, + ) + .await?; + + Ok((monero_wallet, monero_wallet_rpc_process)) +} + +mod data { + use super::*; + + pub fn data_dir_from(arg_dir: Option, testnet: bool) -> Result { + let base_dir = match arg_dir { + Some(custom_base_dir) => custom_base_dir, + None => os_default()?, + }; + + let sub_directory = if testnet { "testnet" } else { "mainnet" }; + + Ok(base_dir.join(sub_directory)) + } + + fn os_default() -> Result { + Ok(system_data_dir()?.join("cli")) + } +} + +fn env_config_from(testnet: bool) -> EnvConfig { + if testnet { + Testnet::get_config() + } else { + Mainnet::get_config() + } +} + +impl Config { + pub fn for_harness(seed: Seed, env_config: EnvConfig) -> Self { + let data_dir = data::data_dir_from(None, false).expect("Could not find data directory"); + + Self { + tor_socks5_port: 9050, + namespace: XmrBtcNamespace::from_is_testnet(false), + server_address: None, + env_config, + seed: Some(seed), + debug: false, + json: false, + is_testnet: false, + data_dir, + } + } +} + +#[cfg(test)] +pub mod api_test { + use super::*; + use crate::api::request::{Method, Request}; + + use libp2p::Multiaddr; + use std::str::FromStr; + use uuid::Uuid; + + pub const MULTI_ADDRESS: &str = + "/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; + pub const MONERO_STAGENET_ADDRESS: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"; + pub const BITCOIN_TESTNET_ADDRESS: &str = "tb1qr3em6k3gfnyl8r7q0v7t4tlnyxzgxma3lressv"; + pub const MONERO_MAINNET_ADDRESS: &str = "44Ato7HveWidJYUAVw5QffEcEtSH1DwzSP3FPPkHxNAS4LX9CqgucphTisH978FLHE34YNEx7FcbBfQLQUU8m3NUC4VqsRa"; + pub const BITCOIN_MAINNET_ADDRESS: &str = "bc1qe4epnfklcaa0mun26yz5g8k24em5u9f92hy325"; + pub const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b"; + + impl Config { + pub fn default( + is_testnet: bool, + data_dir: Option, + debug: bool, + json: bool, + ) -> Self { + let data_dir = data::data_dir_from(data_dir, is_testnet).unwrap(); + + let seed = Seed::from_file_or_generate(data_dir.as_path()).unwrap(); + + let env_config = env_config_from(is_testnet); + Self { + tor_socks5_port: 9050, + namespace: XmrBtcNamespace::from_is_testnet(is_testnet), + server_address: None, + env_config, + seed: Some(seed), + debug, + json, + is_testnet, + data_dir, + } + } + } + + impl Request { + pub fn buy_xmr(is_testnet: bool) -> Request { + let seller = Multiaddr::from_str(MULTI_ADDRESS).unwrap(); + let bitcoin_change_address = { + if is_testnet { + bitcoin::Address::from_str(BITCOIN_TESTNET_ADDRESS).unwrap() + } else { + bitcoin::Address::from_str(BITCOIN_MAINNET_ADDRESS).unwrap() + } + }; + + let monero_receive_address = { + if is_testnet { + monero::Address::from_str(MONERO_STAGENET_ADDRESS).unwrap() + } else { + monero::Address::from_str(MONERO_MAINNET_ADDRESS).unwrap() + } + }; + + Request::new(Method::BuyXmr { + seller, + bitcoin_change_address, + monero_receive_address, + swap_id: Uuid::new_v4(), + }) + } + + pub fn resume() -> Request { + Request::new(Method::Resume { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + }) + } + + pub fn cancel() -> Request { + Request::new(Method::CancelAndRefund { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + }) + } + + pub fn refund() -> Request { + Request::new(Method::CancelAndRefund { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + }) + } + } +} diff --git a/swap/src/api/request.rs b/swap/src/api/request.rs new file mode 100644 index 00000000..02bf27e1 --- /dev/null +++ b/swap/src/api/request.rs @@ -0,0 +1,930 @@ +use crate::api::Context; +use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock}; +use crate::cli::{list_sellers, EventLoop, SellerStatus}; +use crate::libp2p_ext::MultiAddrExt; +use crate::network::quote::{BidQuote, ZeroQuoteReceived}; +use crate::network::swarm; +use crate::protocol::bob::{BobState, Swap}; +use crate::protocol::{bob, State}; +use crate::{bitcoin, cli, monero, rpc}; +use anyhow::{bail, Context as AnyContext, Result}; +use libp2p::core::Multiaddr; +use qrcode::render::unicode; +use qrcode::QrCode; +use serde_json::json; +use std::cmp::min; +use std::convert::TryInto; +use std::future::Future; +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; +use tracing::{debug_span, field, Instrument, Span}; +use uuid::Uuid; + +#[derive(PartialEq, Debug)] +pub struct Request { + pub cmd: Method, + pub log_reference: Option, +} + +#[derive(Debug, PartialEq)] +pub enum Method { + BuyXmr { + seller: Multiaddr, + bitcoin_change_address: bitcoin::Address, + monero_receive_address: monero::Address, + swap_id: Uuid, + }, + Resume { + swap_id: Uuid, + }, + CancelAndRefund { + swap_id: Uuid, + }, + MoneroRecovery { + swap_id: Uuid, + }, + History, + Config, + WithdrawBtc { + amount: Option, + address: bitcoin::Address, + }, + Balance { + force_refresh: bool, + }, + ListSellers { + rendezvous_point: Multiaddr, + }, + ExportBitcoinWallet, + SuspendCurrentSwap, + StartDaemon { + server_address: Option, + }, + GetCurrentSwap, + GetSwapInfo { + swap_id: Uuid, + }, + GetRawStates, +} + +impl Method { + fn get_tracing_span(&self, log_reference_id: Option) -> Span { + let span = match self { + Method::Balance { .. } => { + debug_span!( + "method", + method_name = "Balance", + log_reference_id = field::Empty + ) + } + Method::BuyXmr { swap_id, .. } => { + debug_span!("method", method_name="BuyXmr", swap_id=%swap_id, log_reference_id=field::Empty) + } + Method::CancelAndRefund { swap_id } => { + debug_span!("method", method_name="CancelAndRefund", swap_id=%swap_id, log_reference_id=field::Empty) + } + Method::Resume { swap_id } => { + debug_span!("method", method_name="Resume", swap_id=%swap_id, log_reference_id=field::Empty) + } + Method::Config => { + debug_span!( + "method", + method_name = "Config", + log_reference_id = field::Empty + ) + } + Method::ExportBitcoinWallet => { + debug_span!( + "method", + method_name = "ExportBitcoinWallet", + log_reference_id = field::Empty + ) + } + Method::GetCurrentSwap => { + debug_span!( + "method", + method_name = "GetCurrentSwap", + log_reference_id = field::Empty + ) + } + Method::GetSwapInfo { .. } => { + debug_span!( + "method", + method_name = "GetSwapInfo", + log_reference_id = field::Empty + ) + } + Method::History => { + debug_span!( + "method", + method_name = "History", + log_reference_id = field::Empty + ) + } + Method::ListSellers { .. } => { + debug_span!( + "method", + method_name = "ListSellers", + log_reference_id = field::Empty + ) + } + Method::MoneroRecovery { .. } => { + debug_span!( + "method", + method_name = "MoneroRecovery", + log_reference_id = field::Empty + ) + } + Method::GetRawStates => debug_span!( + "method", + method_name = "RawHistory", + log_reference_id = field::Empty + ), + Method::StartDaemon { .. } => { + debug_span!( + "method", + method_name = "StartDaemon", + log_reference_id = field::Empty + ) + } + Method::SuspendCurrentSwap => { + debug_span!( + "method", + method_name = "SuspendCurrentSwap", + log_reference_id = field::Empty + ) + } + Method::WithdrawBtc { .. } => { + debug_span!( + "method", + method_name = "WithdrawBtc", + log_reference_id = field::Empty + ) + } + }; + if let Some(log_reference_id) = log_reference_id { + span.record("log_reference_id", log_reference_id.as_str()); + } + span + } +} + +impl Request { + pub fn new(cmd: Method) -> Request { + Request { + cmd, + log_reference: None, + } + } + + pub fn with_id(cmd: Method, id: Option) -> Request { + Request { + cmd, + log_reference: id, + } + } + + async fn handle_cmd(self, context: Arc) -> Result { + match self.cmd { + Method::SuspendCurrentSwap => { + let swap_id = context.swap_lock.get_current_swap_id().await; + + if let Some(id_value) = swap_id { + context.swap_lock.send_suspend_signal().await?; + + Ok(json!({ "swapId": id_value })) + } else { + bail!("No swap is currently running") + } + } + Method::GetSwapInfo { swap_id } => { + let bitcoin_wallet = context + .bitcoin_wallet + .as_ref() + .context("Could not get Bitcoin wallet")?; + + let state = context.db.get_state(swap_id).await?; + let is_completed = state.swap_finished(); + + let peerId = context + .db + .get_peer_id(swap_id) + .await + .with_context(|| "Could not get PeerID")?; + + let addresses = context + .db + .get_addresses(peerId) + .await + .with_context(|| "Could not get addressess")?; + + let start_date = context.db.get_swap_start_date(swap_id).await?; + + let swap_state: BobState = state.try_into()?; + let state_name = format!("{}", swap_state); + + let ( + xmr_amount, + btc_amount, + tx_lock_id, + tx_cancel_fee, + tx_refund_fee, + tx_lock_fee, + btc_refund_address, + cancel_timelock, + punish_timelock, + ) = context + .db + .get_states(swap_id) + .await? + .iter() + .find_map(|state| { + if let State::Bob(BobState::SwapSetupCompleted(state2)) = state { + let xmr_amount = state2.xmr; + let btc_amount = state2.tx_lock.lock_amount().to_sat(); + let tx_cancel_fee = state2.tx_cancel_fee.to_sat(); + let tx_refund_fee = state2.tx_refund_fee.to_sat(); + let tx_lock_id = state2.tx_lock.txid(); + let btc_refund_address = state2.refund_address.to_string(); + + if let Ok(tx_lock_fee) = state2.tx_lock.fee() { + let tx_lock_fee = tx_lock_fee.to_sat(); + + Some(( + xmr_amount, + btc_amount, + tx_lock_id, + tx_cancel_fee, + tx_refund_fee, + tx_lock_fee, + btc_refund_address, + state2.cancel_timelock, + state2.punish_timelock, + )) + } else { + None + } + } else { + None + } + }) + .with_context(|| "Did not find SwapSetupCompleted state for swap")?; + + let timelock = match swap_state { + BobState::Started { .. } + | BobState::SafelyAborted + | BobState::SwapSetupCompleted(_) => None, + BobState::BtcLocked { state3: state, .. } + | BobState::XmrLockProofReceived { state, .. } => { + Some(state.expired_timelock(bitcoin_wallet).await) + } + BobState::XmrLocked(state) | BobState::EncSigSent(state) => { + Some(state.expired_timelock(bitcoin_wallet).await) + } + BobState::CancelTimelockExpired(state) | BobState::BtcCancelled(state) => { + Some(state.expired_timelock(bitcoin_wallet).await) + } + BobState::BtcPunished { .. } => Some(Ok(ExpiredTimelocks::Punish)), + BobState::BtcRefunded(_) + | BobState::BtcRedeemed(_) + | BobState::XmrRedeemed { .. } => None, + }; + + Ok(json!({ + "swapId": swap_id, + "seller": { + "peerId": peerId.to_string(), + "addresses": addresses + }, + "completed": is_completed, + "startDate": start_date, + "stateName": state_name, + "xmrAmount": xmr_amount, + "btcAmount": btc_amount, + "txLockId": tx_lock_id, + "txCancelFee": tx_cancel_fee, + "txRefundFee": tx_refund_fee, + "txLockFee": tx_lock_fee, + "btcRefundAddress": btc_refund_address.to_string(), + "cancelTimelock": cancel_timelock, + "punishTimelock": punish_timelock, + // If the timelock is None, it means that the swap is in a state where the timelock is not accessible to us. + // If that is the case, we return null. Otherwise, we return the timelock. + "timelock": timelock.map(|tl| tl.map(|tl| json!(tl)).unwrap_or(json!(null))).unwrap_or(json!(null)), + })) + } + Method::BuyXmr { + seller, + bitcoin_change_address, + monero_receive_address, + swap_id, + } => { + let bitcoin_wallet = Arc::clone( + context + .bitcoin_wallet + .as_ref() + .expect("Could not find Bitcoin wallet"), + ); + let monero_wallet = Arc::clone( + context + .monero_wallet + .as_ref() + .context("Could not get Monero wallet")?, + ); + let env_config = context.config.env_config; + let seed = context.config.seed.clone().context("Could not get seed")?; + + let seller_peer_id = seller + .extract_peer_id() + .context("Seller address must contain peer ID")?; + context + .db + .insert_address(seller_peer_id, seller.clone()) + .await?; + + let behaviour = cli::Behaviour::new( + seller_peer_id, + env_config, + bitcoin_wallet.clone(), + (seed.derive_libp2p_identity(), context.config.namespace), + ); + let mut swarm = swarm::cli( + seed.derive_libp2p_identity(), + context.config.tor_socks5_port, + behaviour, + ) + .await?; + + swarm.behaviour_mut().add_address(seller_peer_id, seller); + + context + .db + .insert_monero_address(swap_id, monero_receive_address) + .await?; + + tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized"); + + context.swap_lock.acquire_swap_lock(swap_id).await?; + + let initialize_swap = tokio::select! { + biased; + _ = context.swap_lock.listen_for_swap_force_suspension() => { + tracing::debug!("Shutdown signal received, exiting"); + context.swap_lock.release_swap_lock().await.expect("Shutdown signal received but failed to release swap lock. The swap process has been terminated but the swap lock is still active."); + bail!("Shutdown signal received"); + }, + result = async { + let (event_loop, mut event_loop_handle) = + EventLoop::new(swap_id, swarm, seller_peer_id)?; + let event_loop = tokio::spawn(event_loop.run().in_current_span()); + + let bid_quote = event_loop_handle.request_quote().await?; + + Ok::<_, anyhow::Error>((event_loop, event_loop_handle, bid_quote)) + } => { + result + }, + }; + + let (event_loop, event_loop_handle, bid_quote) = match initialize_swap { + Ok(result) => result, + Err(error) => { + tracing::error!(%swap_id, "Swap initialization failed: {:#}", error); + context + .swap_lock + .release_swap_lock() + .await + .expect("Could not release swap lock"); + bail!(error); + } + }; + + context.tasks.clone().spawn(async move { + tokio::select! { + biased; + _ = context.swap_lock.listen_for_swap_force_suspension() => { + tracing::debug!("Shutdown signal received, exiting"); + context.swap_lock.release_swap_lock().await.expect("Shutdown signal received but failed to release swap lock. The swap process has been terminated but the swap lock is still active."); + bail!("Shutdown signal received"); + }, + event_loop_result = event_loop => { + match event_loop_result { + Ok(_) => { + tracing::debug!(%swap_id, "EventLoop completed") + } + Err(error) => { + tracing::error!(%swap_id, "EventLoop failed: {:#}", error) + } + } + }, + swap_result = async { + let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); + let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount); + + let determine_amount = determine_btc_to_swap( + context.config.json, + bid_quote, + bitcoin_wallet.new_address(), + || bitcoin_wallet.balance(), + max_givable, + || bitcoin_wallet.sync(), + estimate_fee, + ); + + let (amount, fees) = match determine_amount.await { + Ok(val) => val, + Err(error) => match error.downcast::() { + Ok(_) => { + bail!("Seller's XMR balance is currently too low to initiate a swap, please try again later") + } + Err(other) => bail!(other), + }, + }; + + tracing::info!(%amount, %fees, "Determined swap amount"); + + context.db.insert_peer_id(swap_id, seller_peer_id).await?; + + let swap = Swap::new( + Arc::clone(&context.db), + swap_id, + Arc::clone(&bitcoin_wallet), + monero_wallet, + env_config, + event_loop_handle, + monero_receive_address, + bitcoin_change_address, + amount, + ); + + bob::run(swap).await + } => { + match swap_result { + Ok(state) => { + tracing::debug!(%swap_id, state=%state, "Swap completed") + } + Err(error) => { + tracing::error!(%swap_id, "Failed to complete swap: {:#}", error) + } + } + }, + }; + tracing::debug!(%swap_id, "Swap completed"); + + context + .swap_lock + .release_swap_lock() + .await + .expect("Could not release swap lock"); + Ok::<_, anyhow::Error>(()) + }.in_current_span()).await; + + Ok(json!({ + "swapId": swap_id.to_string(), + "quote": bid_quote, + })) + } + Method::Resume { swap_id } => { + context.swap_lock.acquire_swap_lock(swap_id).await?; + + let seller_peer_id = context.db.get_peer_id(swap_id).await?; + let seller_addresses = context.db.get_addresses(seller_peer_id).await?; + + let seed = context + .config + .seed + .as_ref() + .context("Could not get seed")? + .derive_libp2p_identity(); + + let behaviour = cli::Behaviour::new( + seller_peer_id, + context.config.env_config, + Arc::clone( + context + .bitcoin_wallet + .as_ref() + .context("Could not get Bitcoin wallet")?, + ), + (seed.clone(), context.config.namespace), + ); + let mut swarm = + swarm::cli(seed.clone(), context.config.tor_socks5_port, behaviour).await?; + let our_peer_id = swarm.local_peer_id(); + + tracing::debug!(peer_id = %our_peer_id, "Network layer initialized"); + + for seller_address in seller_addresses { + swarm + .behaviour_mut() + .add_address(seller_peer_id, seller_address); + } + + let (event_loop, event_loop_handle) = + EventLoop::new(swap_id, swarm, seller_peer_id)?; + let monero_receive_address = context.db.get_monero_address(swap_id).await?; + let swap = Swap::from_db( + Arc::clone(&context.db), + swap_id, + Arc::clone( + context + .bitcoin_wallet + .as_ref() + .context("Could not get Bitcoin wallet")?, + ), + Arc::clone( + context + .monero_wallet + .as_ref() + .context("Could not get Monero wallet")?, + ), + context.config.env_config, + event_loop_handle, + monero_receive_address, + ) + .await?; + + context.tasks.clone().spawn( + async move { + let handle = tokio::spawn(event_loop.run().in_current_span()); + tokio::select! { + biased; + _ = context.swap_lock.listen_for_swap_force_suspension() => { + tracing::debug!("Shutdown signal received, exiting"); + context.swap_lock.release_swap_lock().await.expect("Shutdown signal received but failed to release swap lock. The swap process has been terminated but the swap lock is still active."); + bail!("Shutdown signal received"); + }, + + event_loop_result = handle => { + match event_loop_result { + Ok(_) => { + tracing::debug!(%swap_id, "EventLoop completed during swap resume") + } + Err(error) => { + tracing::error!(%swap_id, "EventLoop failed during swap resume: {:#}", error) + } + } + }, + swap_result = bob::run(swap) => { + match swap_result { + Ok(state) => { + tracing::debug!(%swap_id, state=%state, "Swap completed after resuming") + } + Err(error) => { + tracing::error!(%swap_id, "Failed to resume swap: {:#}", error) + } + } + + } + } + context + .swap_lock + .release_swap_lock() + .await + .expect("Could not release swap lock"); + Ok::<(), anyhow::Error>(()) + } + .in_current_span(), + ).await; + Ok(json!({ + "result": "ok", + })) + } + Method::CancelAndRefund { swap_id } => { + let bitcoin_wallet = context + .bitcoin_wallet + .as_ref() + .context("Could not get Bitcoin wallet")?; + + context.swap_lock.acquire_swap_lock(swap_id).await?; + + let state = cli::cancel_and_refund( + swap_id, + Arc::clone(bitcoin_wallet), + Arc::clone(&context.db), + ) + .await; + + context + .swap_lock + .release_swap_lock() + .await + .expect("Could not release swap lock"); + + state.map(|state| { + json!({ + "result": state, + }) + }) + } + Method::History => { + let swaps = context.db.all().await?; + let mut vec: Vec<(Uuid, String)> = Vec::new(); + for (swap_id, state) in swaps { + let state: BobState = state.try_into()?; + vec.push((swap_id, state.to_string())); + } + + Ok(json!({ "swaps": vec })) + } + Method::GetRawStates => { + let raw_history = context.db.raw_all().await?; + + Ok(json!({ "raw_states": raw_history })) + } + Method::Config => { + let data_dir_display = context.config.data_dir.display(); + tracing::info!(path=%data_dir_display, "Data directory"); + tracing::info!(path=%format!("{}/logs", data_dir_display), "Log files directory"); + tracing::info!(path=%format!("{}/sqlite", data_dir_display), "Sqlite file location"); + tracing::info!(path=%format!("{}/seed.pem", data_dir_display), "Seed file location"); + tracing::info!(path=%format!("{}/monero", data_dir_display), "Monero-wallet-rpc directory"); + tracing::info!(path=%format!("{}/wallet", data_dir_display), "Internal bitcoin wallet directory"); + + Ok(json!({ + "log_files": format!("{}/logs", data_dir_display), + "sqlite": format!("{}/sqlite", data_dir_display), + "seed": format!("{}/seed.pem", data_dir_display), + "monero-wallet-rpc": format!("{}/monero", data_dir_display), + "bitcoin_wallet": format!("{}/wallet", data_dir_display), + })) + } + Method::WithdrawBtc { address, amount } => { + let bitcoin_wallet = context + .bitcoin_wallet + .as_ref() + .context("Could not get Bitcoin wallet")?; + + let amount = match amount { + Some(amount) => amount, + None => { + bitcoin_wallet + .max_giveable(address.script_pubkey().len()) + .await? + } + }; + let psbt = bitcoin_wallet + .send_to_address(address, amount, None) + .await?; + let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?; + + bitcoin_wallet + .broadcast(signed_tx.clone(), "withdraw") + .await?; + + Ok(json!({ + "signed_tx": signed_tx, + "amount": amount.to_sat(), + "txid": signed_tx.txid(), + })) + } + Method::StartDaemon { server_address } => { + // Default to 127.0.0.1:1234 + let server_address = server_address.unwrap_or("127.0.0.1:1234".parse()?); + + let (addr, server_handle) = + rpc::run_server(server_address, Arc::clone(&context)).await?; + + tracing::info!(%addr, "Started RPC server"); + + server_handle.stopped().await; + + tracing::info!("Stopped RPC server"); + + Ok(json!({})) + } + Method::Balance { force_refresh } => { + let bitcoin_wallet = context + .bitcoin_wallet + .as_ref() + .context("Could not get Bitcoin wallet")?; + + if force_refresh { + bitcoin_wallet.sync().await?; + } + + let bitcoin_balance = bitcoin_wallet.balance().await?; + + if force_refresh { + tracing::info!( + balance = %bitcoin_balance, + "Checked Bitcoin balance", + ); + } else { + tracing::debug!( + balance = %bitcoin_balance, + "Current Bitcoin balance as of last sync", + ); + } + + Ok(json!({ + "balance": bitcoin_balance.to_sat() + })) + } + Method::ListSellers { rendezvous_point } => { + let rendezvous_node_peer_id = rendezvous_point + .extract_peer_id() + .context("Rendezvous node address must contain peer ID")?; + + let identity = context + .config + .seed + .as_ref() + .context("Cannot extract seed")? + .derive_libp2p_identity(); + + let sellers = list_sellers( + rendezvous_node_peer_id, + rendezvous_point, + context.config.namespace, + context.config.tor_socks5_port, + identity, + ) + .await?; + + for seller in &sellers { + match seller.status { + SellerStatus::Online(quote) => { + tracing::info!( + price = %quote.price.to_string(), + min_quantity = %quote.min_quantity.to_string(), + max_quantity = %quote.max_quantity.to_string(), + status = "Online", + address = %seller.multiaddr.to_string(), + "Fetched peer status" + ); + } + SellerStatus::Unreachable => { + tracing::info!( + status = "Unreachable", + address = %seller.multiaddr.to_string(), + "Fetched peer status" + ); + } + } + } + + Ok(json!({ "sellers": sellers })) + } + Method::ExportBitcoinWallet => { + let bitcoin_wallet = context + .bitcoin_wallet + .as_ref() + .context("Could not get Bitcoin wallet")?; + + let wallet_export = bitcoin_wallet.wallet_export("cli").await?; + tracing::info!(descriptor=%wallet_export.to_string(), "Exported bitcoin wallet"); + Ok(json!({ + "descriptor": wallet_export.to_string(), + })) + } + Method::MoneroRecovery { swap_id } => { + let swap_state: BobState = context.db.get_state(swap_id).await?.try_into()?; + + if let BobState::BtcRedeemed(state5) = swap_state { + let (spend_key, view_key) = state5.xmr_keys(); + let restore_height = state5.monero_wallet_restore_blockheight.height; + + let address = monero::Address::standard( + context.config.env_config.monero_network, + monero::PublicKey::from_private_key(&spend_key), + monero::PublicKey::from(view_key.public()), + ); + + tracing::info!(restore_height=%restore_height, address=%address, spend_key=%spend_key, view_key=%view_key, "Monero recovery information"); + + Ok(json!({ + "address": address, + "spend_key": spend_key.to_string(), + "view_key": view_key.to_string(), + "restore_height": state5.monero_wallet_restore_blockheight.height, + })) + } else { + bail!( + "Cannot print monero recovery information in state {}, only possible for BtcRedeemed", + swap_state + ) + } + } + Method::GetCurrentSwap => Ok(json!({ + "swap_id": context.swap_lock.get_current_swap_id().await + })), + } + } + + pub async fn call(self, context: Arc) -> Result { + let method_span = self.cmd.get_tracing_span(self.log_reference.clone()); + + self.handle_cmd(context) + .instrument(method_span.clone()) + .await + .map_err(|err| { + method_span.in_scope(|| { + tracing::debug!(err = format!("{:?}", err), "API call resulted in an error"); + }); + err + }) + } +} + +fn qr_code(value: &impl ToString) -> Result { + let code = QrCode::new(value.to_string())?; + let qr_code = code + .render::() + .dark_color(unicode::Dense1x2::Light) + .light_color(unicode::Dense1x2::Dark) + .build(); + Ok(qr_code) +} + +pub async fn determine_btc_to_swap( + json: bool, + bid_quote: BidQuote, + get_new_address: impl Future>, + balance: FB, + max_giveable_fn: FMG, + sync: FS, + estimate_fee: FFE, +) -> Result<(Amount, Amount)> +where + TB: Future>, + FB: Fn() -> TB, + TMG: Future>, + FMG: Fn() -> TMG, + TS: Future>, + FS: Fn() -> TS, + FFE: Fn(Amount) -> TFE, + TFE: Future>, +{ + if bid_quote.max_quantity == Amount::ZERO { + bail!(ZeroQuoteReceived) + } + + tracing::info!( + price = %bid_quote.price, + minimum_amount = %bid_quote.min_quantity, + maximum_amount = %bid_quote.max_quantity, + "Received quote", + ); + + sync().await?; + let mut max_giveable = max_giveable_fn().await?; + + if max_giveable == Amount::ZERO || max_giveable < bid_quote.min_quantity { + let deposit_address = get_new_address.await?; + let minimum_amount = bid_quote.min_quantity; + let maximum_amount = bid_quote.max_quantity; + + if !json { + eprintln!("{}", qr_code(&deposit_address)?); + } + + loop { + let min_outstanding = bid_quote.min_quantity - max_giveable; + let min_fee = estimate_fee(min_outstanding).await?; + let min_deposit = min_outstanding + min_fee; + + tracing::info!( + "Deposit at least {} to cover the min quantity with fee!", + min_deposit + ); + tracing::info!( + %deposit_address, + %min_deposit, + %max_giveable, + %minimum_amount, + %maximum_amount, + "Waiting for Bitcoin deposit", + ); + + max_giveable = loop { + sync().await?; + let new_max_givable = max_giveable_fn().await?; + + if new_max_givable > max_giveable { + break new_max_givable; + } + + tokio::time::sleep(Duration::from_secs(1)).await; + }; + + let new_balance = balance().await?; + tracing::info!(%new_balance, %max_giveable, "Received Bitcoin"); + + if max_giveable < bid_quote.min_quantity { + tracing::info!("Deposited amount is less than `min_quantity`"); + continue; + } + + break; + } + }; + + let balance = balance().await?; + let fees = balance - max_giveable; + let max_accepted = bid_quote.max_quantity; + let btc_swap_amount = min(max_giveable, max_accepted); + + Ok((btc_swap_amount, fees)) +} diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 292a4586..06be7b40 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -12,43 +12,15 @@ #![forbid(unsafe_code)] #![allow(non_snake_case)] -use anyhow::{bail, Context, Result}; -use comfy_table::Table; -use qrcode::render::unicode; -use qrcode::QrCode; -use std::cmp::min; -use std::convert::TryInto; +use anyhow::Result; use std::env; -use std::future::Future; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; -use swap::bitcoin::TxLock; -use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult}; -use swap::cli::{list_sellers, EventLoop, SellerStatus}; +use swap::cli::command::{parse_args_and_apply_defaults, ParseResult}; use swap::common::check_latest_version; -use swap::database::open_db; -use swap::env::Config; -use swap::libp2p_ext::MultiAddrExt; -use swap::network::quote::{BidQuote, ZeroQuoteReceived}; -use swap::network::swarm; -use swap::protocol::bob; -use swap::protocol::bob::{BobState, Swap}; -use swap::seed::Seed; -use swap::{bitcoin, cli, monero}; -use url::Url; -use uuid::Uuid; #[tokio::main] async fn main() -> Result<()> { - let Arguments { - env_config, - data_dir, - debug, - json, - cmd, - } = match parse_args_and_apply_defaults(env::args_os())? { - ParseResult::Arguments(args) => *args, + let (context, request) = match parse_args_and_apply_defaults(env::args_os()).await? { + ParseResult::Context(context, request) => (context, request), ParseResult::PrintAndExitZero { message } => { println!("{}", message); std::process::exit(0); @@ -58,601 +30,19 @@ async fn main() -> Result<()> { if let Err(e) = check_latest_version(env!("CARGO_PKG_VERSION")).await { eprintln!("{}", e); } - - match cmd { - Command::BuyXmr { - seller, - bitcoin_electrum_rpc_url, - bitcoin_target_block, - bitcoin_change_address, - monero_receive_address, - monero_daemon_address, - tor_socks5_port, - namespace, - } => { - let swap_id = Uuid::new_v4(); - - cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; - - let db = open_db(data_dir.join("sqlite")).await?; - let seed = Seed::from_file_or_generate(data_dir.as_path()) - .context("Failed to read in seed file")?; - - let bitcoin_wallet = init_bitcoin_wallet( - bitcoin_electrum_rpc_url, - &seed, - data_dir.clone(), - env_config, - bitcoin_target_block, - ) - .await?; - let (monero_wallet, _process) = - init_monero_wallet(data_dir, monero_daemon_address, env_config).await?; - let bitcoin_wallet = Arc::new(bitcoin_wallet); - let seller_peer_id = seller - .extract_peer_id() - .context("Seller address must contain peer ID")?; - db.insert_address(seller_peer_id, seller.clone()).await?; - - let behaviour = cli::Behaviour::new( - seller_peer_id, - env_config, - bitcoin_wallet.clone(), - (seed.derive_libp2p_identity(), namespace), - ); - let mut swarm = - swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?; - swarm.behaviour_mut().add_address(seller_peer_id, seller); - - tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized"); - - let (event_loop, mut event_loop_handle) = - EventLoop::new(swap_id, swarm, seller_peer_id)?; - let event_loop = tokio::spawn(event_loop.run()); - - let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size()); - let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount); - - let (amount, fees) = match determine_btc_to_swap( - json, - event_loop_handle.request_quote(), - bitcoin_wallet.new_address(), - || bitcoin_wallet.balance(), - max_givable, - || bitcoin_wallet.sync(), - estimate_fee, - ) - .await - { - Ok(val) => val, - Err(error) => match error.downcast::() { - Ok(_) => { - bail!("Seller's XMR balance is currently too low to initiate a swap, please try again later") - } - Err(other) => bail!(other), - }, - }; - - tracing::info!(%amount, %fees, "Determined swap amount"); - - db.insert_peer_id(swap_id, seller_peer_id).await?; - db.insert_monero_address(swap_id, monero_receive_address) - .await?; - - let swap = Swap::new( - db, - swap_id, - bitcoin_wallet, - Arc::new(monero_wallet), - env_config, - event_loop_handle, - monero_receive_address, - bitcoin_change_address, - amount, - ); - - tokio::select! { - result = event_loop => { - result - .context("EventLoop panicked")?; - }, - result = bob::run(swap) => { - result.context("Failed to complete swap")?; - } - } - } - Command::History => { - cli::tracing::init(debug, json, data_dir.join("logs"), None)?; - - let db = open_db(data_dir.join("sqlite")).await?; - let swaps = db.all().await?; - - if json { - for (swap_id, state) in swaps { - let state: BobState = state.try_into()?; - tracing::info!(swap_id=%swap_id.to_string(), state=%state.to_string(), "Read swap state from database"); - } - } else { - let mut table = Table::new(); - - table.set_header(vec!["SWAP ID", "STATE"]); - - for (swap_id, state) in swaps { - let state: BobState = state.try_into()?; - table.add_row(vec![swap_id.to_string(), state.to_string()]); - } - - println!("{}", table); - } - } - Command::Config => { - cli::tracing::init(debug, json, data_dir.join("logs"), None)?; - - tracing::info!(path=%data_dir.display(), "Data directory"); - tracing::info!(path=%format!("{}/logs", data_dir.display()), "Log files directory"); - tracing::info!(path=%format!("{}/sqlite", data_dir.display()), "Sqlite file location"); - tracing::info!(path=%format!("{}/seed.pem", data_dir.display()), "Seed file location"); - tracing::info!(path=%format!("{}/monero", data_dir.display()), "Monero-wallet-rpc directory"); - tracing::info!(path=%format!("{}/wallet", data_dir.display()), "Internal bitcoin wallet directory"); - } - Command::WithdrawBtc { - bitcoin_electrum_rpc_url, - bitcoin_target_block, - amount, - address, - } => { - cli::tracing::init(debug, json, data_dir.join("logs"), None)?; - - let seed = Seed::from_file_or_generate(data_dir.as_path()) - .context("Failed to read in seed file")?; - let bitcoin_wallet = init_bitcoin_wallet( - bitcoin_electrum_rpc_url, - &seed, - data_dir.clone(), - env_config, - bitcoin_target_block, - ) - .await?; - - let amount = match amount { - Some(amount) => amount, - None => { - bitcoin_wallet - .max_giveable(address.script_pubkey().len()) - .await? - } - }; - - let psbt = bitcoin_wallet - .send_to_address(address, amount, None) - .await?; - let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?; - - bitcoin_wallet.broadcast(signed_tx, "withdraw").await?; - } - - Command::Balance { - bitcoin_electrum_rpc_url, - bitcoin_target_block, - } => { - cli::tracing::init(debug, json, data_dir.join("logs"), None)?; - - let seed = Seed::from_file_or_generate(data_dir.as_path()) - .context("Failed to read in seed file")?; - let bitcoin_wallet = init_bitcoin_wallet( - bitcoin_electrum_rpc_url, - &seed, - data_dir.clone(), - env_config, - bitcoin_target_block, - ) - .await?; - - let bitcoin_balance = bitcoin_wallet.balance().await?; - tracing::info!( - balance = %bitcoin_balance, - "Checked Bitcoin balance", - ); - } - Command::Resume { - swap_id, - bitcoin_electrum_rpc_url, - bitcoin_target_block, - monero_daemon_address, - tor_socks5_port, - namespace, - } => { - cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; - - let db = open_db(data_dir.join("sqlite")).await?; - let seed = Seed::from_file_or_generate(data_dir.as_path()) - .context("Failed to read in seed file")?; - - let bitcoin_wallet = init_bitcoin_wallet( - bitcoin_electrum_rpc_url, - &seed, - data_dir.clone(), - env_config, - bitcoin_target_block, - ) - .await?; - let (monero_wallet, _process) = - init_monero_wallet(data_dir, monero_daemon_address, env_config).await?; - let bitcoin_wallet = Arc::new(bitcoin_wallet); - - let seller_peer_id = db.get_peer_id(swap_id).await?; - let seller_addresses = db.get_addresses(seller_peer_id).await?; - - let behaviour = cli::Behaviour::new( - seller_peer_id, - env_config, - bitcoin_wallet.clone(), - (seed.derive_libp2p_identity(), namespace), - ); - let mut swarm = - swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?; - let our_peer_id = swarm.local_peer_id(); - tracing::debug!(peer_id = %our_peer_id, "Network layer initialized"); - - for seller_address in seller_addresses { - swarm - .behaviour_mut() - .add_address(seller_peer_id, seller_address); - } - - let (event_loop, event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id)?; - let handle = tokio::spawn(event_loop.run()); - - let monero_receive_address = db.get_monero_address(swap_id).await?; - let swap = Swap::from_db( - db, - swap_id, - bitcoin_wallet, - Arc::new(monero_wallet), - env_config, - event_loop_handle, - monero_receive_address, - ) - .await?; - - tokio::select! { - event_loop_result = handle => { - event_loop_result?; - }, - swap_result = bob::run(swap) => { - swap_result?; - } - } - } - Command::CancelAndRefund { - swap_id, - bitcoin_electrum_rpc_url, - bitcoin_target_block, - } => { - cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; - - let db = open_db(data_dir.join("sqlite")).await?; - let seed = Seed::from_file_or_generate(data_dir.as_path()) - .context("Failed to read in seed file")?; - - let bitcoin_wallet = init_bitcoin_wallet( - bitcoin_electrum_rpc_url, - &seed, - data_dir, - env_config, - bitcoin_target_block, - ) - .await?; - - cli::cancel_and_refund(swap_id, Arc::new(bitcoin_wallet), db).await?; - } - Command::ListSellers { - rendezvous_point, - namespace, - tor_socks5_port, - } => { - let rendezvous_node_peer_id = rendezvous_point - .extract_peer_id() - .context("Rendezvous node address must contain peer ID")?; - - cli::tracing::init(debug, json, data_dir.join("logs"), None)?; - - let seed = Seed::from_file_or_generate(data_dir.as_path()) - .context("Failed to read in seed file")?; - let identity = seed.derive_libp2p_identity(); - - let sellers = list_sellers( - rendezvous_node_peer_id, - rendezvous_point, - namespace, - tor_socks5_port, - identity, - ) - .await?; - - if json { - for seller in sellers { - match seller.status { - SellerStatus::Online(quote) => { - tracing::info!( - price = %quote.price.to_string(), - min_quantity = %quote.min_quantity.to_string(), - max_quantity = %quote.max_quantity.to_string(), - status = "Online", - address = %seller.multiaddr.to_string(), - "Fetched peer status" - ); - } - SellerStatus::Unreachable => { - tracing::info!( - status = "Unreachable", - address = %seller.multiaddr.to_string(), - "Fetched peer status" - ); - } - } - } - } else { - let mut table = Table::new(); - - table.set_header(vec![ - "PRICE", - "MIN_QUANTITY", - "MAX_QUANTITY", - "STATUS", - "ADDRESS", - ]); - - for seller in sellers { - let row = match seller.status { - SellerStatus::Online(quote) => { - vec![ - quote.price.to_string(), - quote.min_quantity.to_string(), - quote.max_quantity.to_string(), - "Online".to_owned(), - seller.multiaddr.to_string(), - ] - } - SellerStatus::Unreachable => { - vec![ - "???".to_owned(), - "???".to_owned(), - "???".to_owned(), - "Unreachable".to_owned(), - seller.multiaddr.to_string(), - ] - } - }; - - table.add_row(row); - } - - println!("{}", table); - } - } - Command::ExportBitcoinWallet { - bitcoin_electrum_rpc_url, - bitcoin_target_block, - } => { - cli::tracing::init(debug, json, data_dir.join("logs"), None)?; - - let seed = Seed::from_file_or_generate(data_dir.as_path()) - .context("Failed to read in seed file")?; - let bitcoin_wallet = init_bitcoin_wallet( - bitcoin_electrum_rpc_url, - &seed, - data_dir.clone(), - env_config, - bitcoin_target_block, - ) - .await?; - let wallet_export = bitcoin_wallet.wallet_export("cli").await?; - tracing::info!(descriptor=%wallet_export.to_string(), "Exported bitcoin wallet"); - } - Command::MoneroRecovery { swap_id } => { - cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; - - let db = open_db(data_dir.join("sqlite")).await?; - - let swap_state: BobState = db.get_state(swap_id).await?.try_into()?; - - match swap_state { - BobState::Started { .. } - | BobState::SwapSetupCompleted(_) - | BobState::BtcLocked { .. } - | BobState::XmrLockProofReceived { .. } - | BobState::XmrLocked(_) - | BobState::EncSigSent(_) - | BobState::CancelTimelockExpired(_) - | BobState::BtcCancelled(_) - | BobState::BtcRefunded(_) - | BobState::BtcPunished { .. } - | BobState::SafelyAborted - | BobState::XmrRedeemed { .. } => { - bail!("Cannot print monero recovery information in state {}, only possible for BtcRedeemed", swap_state) - } - BobState::BtcRedeemed(state5) => { - let (spend_key, view_key) = state5.xmr_keys(); - - let address = monero::Address::standard( - env_config.monero_network, - monero::PublicKey::from_private_key(&spend_key), - monero::PublicKey::from(view_key.public()), - ); - tracing::info!("Wallet address: {}", address.to_string()); - - let view_key = serde_json::to_string(&view_key)?; - println!("View key: {}", view_key); - - println!("Spend key: {}", spend_key); - } - } - } - }; + request.call(context.clone()).await?; + context.tasks.wait_for_tasks().await?; Ok(()) } -async fn init_bitcoin_wallet( - electrum_rpc_url: Url, - seed: &Seed, - data_dir: PathBuf, - env_config: Config, - bitcoin_target_block: usize, -) -> Result { - tracing::debug!("Initializing bitcoin wallet"); - let xprivkey = seed.derive_extended_private_key(env_config.bitcoin_network)?; - - let wallet = bitcoin::Wallet::new( - electrum_rpc_url.clone(), - data_dir, - xprivkey, - env_config, - bitcoin_target_block, - ) - .await - .context("Failed to initialize Bitcoin wallet")?; - - tracing::debug!("Syncing bitcoin wallet"); - wallet.sync().await?; - - Ok(wallet) -} - -async fn init_monero_wallet( - data_dir: PathBuf, - monero_daemon_address: Option, - env_config: Config, -) -> Result<(monero::Wallet, monero::WalletRpcProcess)> { - let network = env_config.monero_network; - - const MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME: &str = "swap-tool-blockchain-monitoring-wallet"; - - let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?; - - let monero_wallet_rpc_process = monero_wallet_rpc - .run(network, monero_daemon_address) - .await?; - - let monero_wallet = monero::Wallet::open_or_create( - monero_wallet_rpc_process.endpoint(), - MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME.to_string(), - env_config, - ) - .await?; - - Ok((monero_wallet, monero_wallet_rpc_process)) -} - -fn qr_code(value: &impl ToString) -> Result { - let code = QrCode::new(value.to_string())?; - let qr_code = code - .render::() - .dark_color(unicode::Dense1x2::Light) - .light_color(unicode::Dense1x2::Dark) - .build(); - Ok(qr_code) -} - -async fn determine_btc_to_swap( - json: bool, - bid_quote: impl Future>, - get_new_address: impl Future>, - balance: FB, - max_giveable_fn: FMG, - sync: FS, - estimate_fee: FFE, -) -> Result<(bitcoin::Amount, bitcoin::Amount)> -where - TB: Future>, - FB: Fn() -> TB, - TMG: Future>, - FMG: Fn() -> TMG, - TS: Future>, - FS: Fn() -> TS, - FFE: Fn(bitcoin::Amount) -> TFE, - TFE: Future>, -{ - tracing::debug!("Requesting quote"); - let bid_quote = bid_quote.await?; - - if bid_quote.max_quantity == bitcoin::Amount::ZERO { - bail!(ZeroQuoteReceived) - } - - tracing::info!( - price = %bid_quote.price, - minimum_amount = %bid_quote.min_quantity, - maximum_amount = %bid_quote.max_quantity, - "Received quote", - ); - - let mut max_giveable = max_giveable_fn().await?; - - if max_giveable == bitcoin::Amount::ZERO || max_giveable < bid_quote.min_quantity { - let deposit_address = get_new_address.await?; - let minimum_amount = bid_quote.min_quantity; - let maximum_amount = bid_quote.max_quantity; - - if !json { - eprintln!("{}", qr_code(&deposit_address)?); - } - - loop { - let min_outstanding = bid_quote.min_quantity - max_giveable; - let min_fee = estimate_fee(min_outstanding).await?; - let min_deposit = min_outstanding + min_fee; - - tracing::info!( - "Deposit at least {} to cover the min quantity with fee!", - min_deposit - ); - tracing::info!( - %deposit_address, - %min_deposit, - %max_giveable, - %minimum_amount, - %maximum_amount, - "Waiting for Bitcoin deposit", - ); - - max_giveable = loop { - sync().await?; - let new_max_givable = max_giveable_fn().await?; - - if new_max_givable > max_giveable { - break new_max_givable; - } - - tokio::time::sleep(Duration::from_secs(1)).await; - }; - - let new_balance = balance().await?; - tracing::info!(%new_balance, %max_giveable, "Received Bitcoin"); - - if max_giveable < bid_quote.min_quantity { - tracing::info!("Deposited amount is less than `min_quantity`"); - continue; - } - - break; - } - }; - - let balance = balance().await?; - let fees = balance - max_giveable; - let max_accepted = bid_quote.max_quantity; - let btc_swap_amount = min(max_giveable, max_accepted); - - Ok((btc_swap_amount, fees)) -} - #[cfg(test)] mod tests { use super::*; - use crate::determine_btc_to_swap; use ::bitcoin::Amount; - use std::sync::Mutex; + use std::sync::{Arc, Mutex}; + use std::time::Duration; + use swap::api::request::determine_btc_to_swap; + use swap::network::quote::BidQuote; use swap::tracing_ext::capture_logs; use tracing::level_filters::LevelFilter; @@ -666,7 +56,7 @@ mod tests { let (amount, fees) = determine_btc_to_swap( true, - async { Ok(quote_with_max(0.01)) }, + quote_with_max(0.01), get_dummy_address(), || async { Ok(Amount::from_btc(0.001)?) }, || async { @@ -685,10 +75,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Received Bitcoin new_balance=0.00100000 BTC max_giveable=0.00090000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Deposit at least 0.00001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.001 BTC max_giveable=0.0009 BTC " ); } @@ -703,7 +93,7 @@ mod tests { let (amount, fees) = determine_btc_to_swap( true, - async { Ok(quote_with_max(0.01)) }, + quote_with_max(0.01), get_dummy_address(), || async { Ok(Amount::from_btc(0.1001)?) }, || async { @@ -722,10 +112,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Deposit at least 0.00001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC - INFO swap: Received Bitcoin new_balance=0.10010000 BTC max_giveable=0.10000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Deposit at least 0.00001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.1001 BTC max_giveable=0.1 BTC " ); } @@ -740,7 +130,7 @@ mod tests { let (amount, fees) = determine_btc_to_swap( true, - async { Ok(quote_with_max(0.01)) }, + quote_with_max(0.01), async { panic!("should not request new address when initial balance is > 0") }, || async { Ok(Amount::from_btc(0.005)?) }, || async { @@ -759,7 +149,7 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - " INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n" + " INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC\n" ); } @@ -773,7 +163,7 @@ mod tests { let (amount, fees) = determine_btc_to_swap( true, - async { Ok(quote_with_max(0.01)) }, + quote_with_max(0.01), async { panic!("should not request new address when initial balance is > 0") }, || async { Ok(Amount::from_btc(0.1001)?) }, || async { @@ -792,7 +182,7 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - " INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC\n" + " INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC\n" ); } @@ -806,7 +196,7 @@ mod tests { let (amount, fees) = determine_btc_to_swap( true, - async { Ok(quote_with_min(0.01)) }, + quote_with_min(0.01), get_dummy_address(), || async { Ok(Amount::from_btc(0.0101)?) }, || async { @@ -825,10 +215,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.01001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.01001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.01001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.01001 BTC max_giveable=0 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC " ); } @@ -843,7 +233,7 @@ mod tests { let (amount, fees) = determine_btc_to_swap( true, - async { Ok(quote_with_min(0.01)) }, + quote_with_min(0.01), get_dummy_address(), || async { Ok(Amount::from_btc(0.0101)?) }, || async { @@ -862,10 +252,10 @@ mod tests { assert_eq!((amount, fees), (expected_amount, expected_fees)); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.00991000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00991000 BTC max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.00991 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00991 BTC max_giveable=0.0001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC " ); } @@ -885,7 +275,7 @@ mod tests { Duration::from_secs(1), determine_btc_to_swap( true, - async { Ok(quote_with_min(0.1)) }, + quote_with_min(0.1), get_dummy_address(), || async { Ok(Amount::from_btc(0.0101)?) }, || async { @@ -902,13 +292,13 @@ mod tests { assert!(matches!(error, tokio::time::error::Elapsed { .. })); assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC - INFO swap: Deposited amount is less than `min_quantity` - INFO swap: Deposit at least 0.09001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.09001000 BTC max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.10001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001 BTC max_giveable=0 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC + INFO swap::api::request: Deposited amount is less than `min_quantity` + INFO swap::api::request: Deposit at least 0.09001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.09001 BTC max_giveable=0.01 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC " ); } @@ -933,7 +323,7 @@ mod tests { Duration::from_secs(10), determine_btc_to_swap( true, - async { Ok(quote_with_min(0.1)) }, + quote_with_min(0.1), get_dummy_address(), || async { Ok(Amount::from_btc(0.21)?) }, || async { @@ -951,10 +341,10 @@ mod tests { assert_eq!( writer.captured(), - r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Deposit at least 0.10001000 BTC to cover the min quantity with fee! - INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001000 BTC max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC - INFO swap: Received Bitcoin new_balance=0.21000000 BTC max_giveable=0.20000000 BTC + r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Deposit at least 0.10001 BTC to cover the min quantity with fee! + INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001 BTC max_giveable=0 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC + INFO swap::api::request: Received Bitcoin new_balance=0.21 BTC max_giveable=0.2 BTC " ); } @@ -968,7 +358,7 @@ mod tests { let determination_error = determine_btc_to_swap( true, - async { Ok(quote_with_max(0.00)) }, + quote_with_max(0.00), get_dummy_address(), || async { Ok(Amount::from_btc(0.0101)?) }, || async { diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index 925f48e6..556d8453 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -15,7 +15,7 @@ pub use crate::bitcoin::refund::TxRefund; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; pub use ::bitcoin::util::amount::Amount; pub use ::bitcoin::util::psbt::PartiallySignedTransaction; -pub use ::bitcoin::{Address, Network, Transaction, Txid}; +pub use ::bitcoin::{Address, AddressType, Network, Transaction, Txid}; pub use ecdsa_fun::adaptor::EncryptedSignature; pub use ecdsa_fun::fun::Scalar; pub use ecdsa_fun::Signature; @@ -244,10 +244,65 @@ pub fn current_epoch( } if tx_lock_status.is_confirmed_with(cancel_timelock) { - return ExpiredTimelocks::Cancel; + return ExpiredTimelocks::Cancel { + blocks_left: tx_cancel_status.blocks_left_until(punish_timelock), + }; } - ExpiredTimelocks::None + ExpiredTimelocks::None { + blocks_left: tx_lock_status.blocks_left_until(cancel_timelock), + } +} + +pub mod bitcoin_address { + use anyhow::{bail, Result}; + use serde::Serialize; + use std::str::FromStr; + + #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Serialize)] + #[error("Invalid Bitcoin address provided, expected address on network {expected:?} but address provided is on {actual:?}")] + pub struct BitcoinAddressNetworkMismatch { + #[serde(with = "crate::bitcoin::network")] + expected: bitcoin::Network, + #[serde(with = "crate::bitcoin::network")] + actual: bitcoin::Network, + } + + pub fn parse(addr_str: &str) -> Result { + let address = bitcoin::Address::from_str(addr_str)?; + + if address.address_type() != Some(bitcoin::AddressType::P2wpkh) { + anyhow::bail!("Invalid Bitcoin address provided, only bech32 format is supported!") + } + + Ok(address) + } + + pub fn validate( + address: bitcoin::Address, + expected_network: bitcoin::Network, + ) -> Result { + if address.network != expected_network { + bail!(BitcoinAddressNetworkMismatch { + expected: expected_network, + actual: address.network + }); + } + + Ok(address) + } + + pub fn validate_is_testnet( + address: bitcoin::Address, + is_testnet: bool, + ) -> Result { + let expected_network = if is_testnet { + bitcoin::Network::Testnet + } else { + bitcoin::Network::Bitcoin + }; + validate(address, expected_network) + } } /// Bitcoin error codes: https://github.com/bitcoin/bitcoin/blob/97d3500601c1d28642347d014a6de1e38f53ae4e/src/rpc/protocol.h#L23 @@ -324,6 +379,7 @@ mod tests { use crate::env::{GetConfig, Regtest}; use crate::protocol::{alice, bob}; use rand::rngs::OsRng; + use std::matches; use uuid::Uuid; #[test] @@ -338,7 +394,7 @@ mod tests { tx_cancel_status, ); - assert_eq!(expired_timelock, ExpiredTimelocks::None) + assert!(matches!(expired_timelock, ExpiredTimelocks::None { .. })); } #[test] @@ -353,7 +409,7 @@ mod tests { tx_cancel_status, ); - assert_eq!(expired_timelock, ExpiredTimelocks::Cancel) + assert!(matches!(expired_timelock, ExpiredTimelocks::Cancel { .. })); } #[test] diff --git a/swap/src/bitcoin/cancel.rs b/swap/src/bitcoin/cancel.rs index 35b6b197..aec3fe38 100644 --- a/swap/src/bitcoin/cancel.rs +++ b/swap/src/bitcoin/cancel.rs @@ -24,6 +24,12 @@ use std::ops::Add; #[serde(transparent)] pub struct CancelTimelock(u32); +impl From for u32 { + fn from(cancel_timelock: CancelTimelock) -> Self { + cancel_timelock.0 + } +} + impl CancelTimelock { pub const fn new(number_of_blocks: u32) -> Self { Self(number_of_blocks) @@ -64,6 +70,12 @@ impl fmt::Display for CancelTimelock { #[serde(transparent)] pub struct PunishTimelock(u32); +impl From for u32 { + fn from(punish_timelock: PunishTimelock) -> Self { + punish_timelock.0 + } +} + impl PunishTimelock { pub const fn new(number_of_blocks: u32) -> Self { Self(number_of_blocks) diff --git a/swap/src/bitcoin/lock.rs b/swap/src/bitcoin/lock.rs index 42819ad5..f8aa9a39 100644 --- a/swap/src/bitcoin/lock.rs +++ b/swap/src/bitcoin/lock.rs @@ -4,9 +4,10 @@ use crate::bitcoin::{ }; use ::bitcoin::util::psbt::PartiallySignedTransaction; use ::bitcoin::{OutPoint, TxIn, TxOut, Txid}; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; use bdk::database::BatchDatabase; use bdk::miniscript::Descriptor; +use bdk::psbt::PsbtUtils; use bitcoin::{PackedLockTime, Script, Sequence}; use serde::{Deserialize, Serialize}; @@ -100,6 +101,15 @@ impl TxLock { Amount::from_sat(self.inner.clone().extract_tx().output[self.lock_output_vout()].value) } + pub fn fee(&self) -> Result { + Ok(Amount::from_sat( + self.inner + .clone() + .fee_amount() + .context("The PSBT is missing a TxOut for an input")?, + )) + } + pub fn txid(&self) -> Txid { self.inner.clone().extract_tx().txid() } diff --git a/swap/src/bitcoin/timelocks.rs b/swap/src/bitcoin/timelocks.rs index e8b72ea6..427bef80 100644 --- a/swap/src/bitcoin/timelocks.rs +++ b/swap/src/bitcoin/timelocks.rs @@ -37,9 +37,9 @@ impl Add for BlockHeight { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum ExpiredTimelocks { - None, - Cancel, + None { blocks_left: u32 }, + Cancel { blocks_left: u32 }, Punish, } diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 2e5732f8..5d8685f3 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -24,6 +24,7 @@ use std::path::Path; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::{watch, Mutex}; +use tracing::{debug_span, Instrument}; const SLED_TREE_NAME: &str = "default_tree"; @@ -192,7 +193,7 @@ impl Wallet { tokio::time::sleep(Duration::from_secs(5)).await; } - }); + }.instrument(debug_span!("BitcoinWalletSubscription"))); Subscription { receiver, @@ -274,7 +275,7 @@ impl Subscription { pub async fn wait_until_confirmed_with(&self, target: T) -> Result<()> where - u32: PartialOrd, + T: Into, T: Copy, { self.wait_until(|status| status.is_confirmed_with(target)) @@ -933,9 +934,20 @@ impl Confirmed { pub fn meets_target(&self, target: T) -> bool where - u32: PartialOrd, + T: Into, { - self.confirmations() >= target + self.confirmations() >= target.into() + } + + pub fn blocks_left_until(&self, target: T) -> u32 + where + T: Into + Copy, + { + if self.meets_target(target) { + 0 + } else { + target.into() - self.confirmations() + } } } @@ -948,7 +960,7 @@ impl ScriptStatus { /// Check if the script has met the given confirmation target. pub fn is_confirmed_with(&self, target: T) -> bool where - u32: PartialOrd, + T: Into, { match self { ScriptStatus::Confirmed(inner) => inner.meets_target(target), @@ -956,6 +968,17 @@ impl ScriptStatus { } } + // Calculate the number of blocks left until the target is met. + pub fn blocks_left_until(&self, target: T) -> u32 + where + T: Into + Copy, + { + match self { + ScriptStatus::Confirmed(inner) => inner.blocks_left_until(target), + _ => target.into(), + } + } + pub fn has_been_seen(&self) -> bool { matches!(self, ScriptStatus::InMempool | ScriptStatus::Confirmed(_)) } @@ -987,7 +1010,7 @@ mod tests { fn given_depth_0_should_meet_confirmation_target_one() { let script = ScriptStatus::Confirmed(Confirmed { depth: 0 }); - let confirmed = script.is_confirmed_with(1); + let confirmed = script.is_confirmed_with(1_u32); assert!(confirmed) } @@ -996,7 +1019,7 @@ mod tests { fn given_confirmations_1_should_meet_confirmation_target_one() { let script = ScriptStatus::from_confirmations(1); - let confirmed = script.is_confirmed_with(1); + let confirmed = script.is_confirmed_with(1_u32); assert!(confirmed) } @@ -1011,6 +1034,33 @@ mod tests { assert_eq!(confirmed.depth, 0) } + #[test] + fn given_depth_0_should_return_0_blocks_left_until_1() { + let script = ScriptStatus::Confirmed(Confirmed { depth: 0 }); + + let blocks_left = script.blocks_left_until(1_u32); + + assert_eq!(blocks_left, 0) + } + + #[test] + fn given_depth_1_should_return_0_blocks_left_until_1() { + let script = ScriptStatus::Confirmed(Confirmed { depth: 1 }); + + let blocks_left = script.blocks_left_until(1_u32); + + assert_eq!(blocks_left, 0) + } + + #[test] + fn given_depth_0_should_return_1_blocks_left_until_2() { + let script = ScriptStatus::Confirmed(Confirmed { depth: 0 }); + + let blocks_left = script.blocks_left_until(2_u32); + + assert_eq!(blocks_left, 1) + } + #[test] fn given_one_BTC_and_100k_sats_per_vb_fees_should_not_hit_max() { // 400 weight = 100 vbyte diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index b42c124c..d542b7ed 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -10,7 +10,7 @@ use uuid::Uuid; pub async fn cancel_and_refund( swap_id: Uuid, bitcoin_wallet: Arc, - db: Arc, + db: Arc, ) -> Result { if let Err(err) = cancel(swap_id, bitcoin_wallet.clone(), db.clone()).await { tracing::info!(%err, "Could not submit cancel transaction"); @@ -28,7 +28,7 @@ pub async fn cancel_and_refund( pub async fn cancel( swap_id: Uuid, bitcoin_wallet: Arc, - db: Arc, + db: Arc, ) -> Result<(Txid, Subscription, BobState)> { let state = db.get_state(swap_id).await?.try_into()?; @@ -80,7 +80,7 @@ pub async fn cancel( pub async fn refund( swap_id: Uuid, bitcoin_wallet: Arc, - db: Arc, + db: Arc, ) -> Result { let state = db.get_state(swap_id).await?.try_into()?; diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 5c3b2827..affd6a19 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -1,43 +1,39 @@ -use crate::bitcoin::Amount; -use crate::env::GetConfig; -use crate::fs::system_data_dir; -use crate::network::rendezvous::XmrBtcNamespace; -use crate::{env, monero}; -use anyhow::{bail, Context, Result}; -use bitcoin::{Address, AddressType}; +use crate::api::request::{Method, Request}; +use crate::api::Context; +use crate::bitcoin::{bitcoin_address, Amount}; +use crate::monero; +use crate::monero::monero_address; +use anyhow::Result; use libp2p::core::Multiaddr; -use serde::Serialize; use std::ffi::OsString; +use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; +use std::sync::Arc; use structopt::{clap, StructOpt}; use url::Url; use uuid::Uuid; +// See: https://moneroworld.com/ +pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.community.rino.io:18081"; +pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.community.rino.io:38081"; + // See: https://1209k.com/bitcoin-eye/ele.php?chain=btc const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700"; // See: https://1209k.com/bitcoin-eye/ele.php?chain=tbtc pub const DEFAULT_ELECTRUM_RPC_URL_TESTNET: &str = "ssl://electrum.blockstream.info:60002"; const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 3; -const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: usize = 1; +pub const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: usize = 1; const DEFAULT_TOR_SOCKS5_PORT: &str = "9050"; -#[derive(Debug, PartialEq, Eq)] -pub struct Arguments { - pub env_config: env::Config, - pub debug: bool, - pub json: bool, - pub data_dir: PathBuf, - pub cmd: Command, -} - /// Represents the result of parsing the command-line parameters. -#[derive(Debug, PartialEq, Eq)] + +#[derive(Debug)] pub enum ParseResult { /// The arguments we were invoked in. - Arguments(Box), + Context(Arc, Box), /// A flag or command was given that does not need further processing other /// than printing the provided message. /// @@ -45,13 +41,13 @@ pub enum ParseResult { PrintAndExitZero { message: String }, } -pub fn parse_args_and_apply_defaults(raw_args: I) -> Result +pub async fn parse_args_and_apply_defaults(raw_args: I) -> Result where I: IntoIterator, T: Into + Clone, { - let args = match RawArguments::clap().get_matches_from_safe(raw_args) { - Ok(matches) => RawArguments::from_clap(&matches), + let args = match Arguments::clap().get_matches_from_safe(raw_args) { + Ok(matches) => Arguments::from_clap(&matches), Err(clap::Error { message, kind: clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed, @@ -64,233 +60,195 @@ where let json = args.json; let is_testnet = args.testnet; let data = args.data; - - let arguments = match args.cmd { - RawCommand::BuyXmr { + let (context, request) = match args.cmd { + CliCommand::BuyXmr { seller: Seller { seller }, bitcoin, bitcoin_change_address, monero, monero_receive_address, - tor: Tor { tor_socks5_port }, + tor, } => { - let (bitcoin_electrum_rpc_url, bitcoin_target_block) = - bitcoin.apply_defaults(is_testnet)?; let monero_receive_address = - validate_monero_address(monero_receive_address, is_testnet)?; + monero_address::validate_is_testnet(monero_receive_address, is_testnet)?; let bitcoin_change_address = - validate_bitcoin_address(bitcoin_change_address, is_testnet)?; - let monero_daemon_address = monero.monero_daemon_address; + bitcoin_address::validate_is_testnet(bitcoin_change_address, is_testnet)?; - Arguments { - env_config: env_config_from(is_testnet), + let request = Request::new(Method::BuyXmr { + seller, + bitcoin_change_address, + monero_receive_address, + swap_id: Uuid::new_v4(), + }); + + let context = Context::build( + Some(bitcoin), + Some(monero), + Some(tor), + data, + is_testnet, debug, json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::BuyXmr { - seller, - bitcoin_electrum_rpc_url, - bitcoin_target_block, - bitcoin_change_address, - monero_receive_address, - monero_daemon_address, - tor_socks5_port, - namespace: XmrBtcNamespace::from_is_testnet(is_testnet), - }, - } + None, + ) + .await?; + (context, request) } - RawCommand::History => Arguments { - env_config: env_config_from(is_testnet), - debug, - json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::History, - }, - RawCommand::Config => Arguments { - env_config: env_config_from(is_testnet), - debug, - json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::Config, - }, - RawCommand::Balance { - bitcoin_electrum_rpc_url, + CliCommand::History => { + let request = Request::new(Method::History); + + let context = + Context::build(None, None, None, data, is_testnet, debug, json, None).await?; + (context, request) + } + CliCommand::Config => { + let request = Request::new(Method::Config); + + let context = + Context::build(None, None, None, data, is_testnet, debug, json, None).await?; + (context, request) + } + CliCommand::Balance { bitcoin } => { + let request = Request::new(Method::Balance { + force_refresh: true, + }); + + let context = Context::build( + Some(bitcoin), + None, + None, + data, + is_testnet, + debug, + json, + None, + ) + .await?; + (context, request) + } + CliCommand::StartDaemon { + server_address, + bitcoin, + monero, + tor, } => { - let bitcoin = Bitcoin { - bitcoin_electrum_rpc_url, - bitcoin_target_block: None, - }; - let (bitcoin_electrum_rpc_url, bitcoin_target_block) = - bitcoin.apply_defaults(is_testnet)?; + let request = Request::new(Method::StartDaemon { server_address }); - Arguments { - env_config: env_config_from(is_testnet), + let context = Context::build( + Some(bitcoin), + Some(monero), + Some(tor), + data, + is_testnet, debug, json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::Balance { - bitcoin_electrum_rpc_url, - bitcoin_target_block, - }, - } + server_address, + ) + .await?; + (context, request) } - RawCommand::WithdrawBtc { + CliCommand::WithdrawBtc { bitcoin, amount, address, } => { - let (bitcoin_electrum_rpc_url, bitcoin_target_block) = - bitcoin.apply_defaults(is_testnet)?; + let address = bitcoin_address::validate_is_testnet(address, is_testnet)?; + let request = Request::new(Method::WithdrawBtc { amount, address }); - Arguments { - env_config: env_config_from(is_testnet), + let context = Context::build( + Some(bitcoin), + None, + None, + data, + is_testnet, debug, json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::WithdrawBtc { - bitcoin_electrum_rpc_url, - bitcoin_target_block, - amount, - address: bitcoin_address(address, is_testnet)?, - }, - } + None, + ) + .await?; + (context, request) } - RawCommand::Resume { + CliCommand::Resume { swap_id: SwapId { swap_id }, bitcoin, monero, - tor: Tor { tor_socks5_port }, + tor, } => { - let (bitcoin_electrum_rpc_url, bitcoin_target_block) = - bitcoin.apply_defaults(is_testnet)?; - let monero_daemon_address = monero.monero_daemon_address; + let request = Request::new(Method::Resume { swap_id }); - Arguments { - env_config: env_config_from(is_testnet), + let context = Context::build( + Some(bitcoin), + Some(monero), + Some(tor), + data, + is_testnet, debug, json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::Resume { - swap_id, - bitcoin_electrum_rpc_url, - bitcoin_target_block, - monero_daemon_address, - tor_socks5_port, - namespace: XmrBtcNamespace::from_is_testnet(is_testnet), - }, - } + None, + ) + .await?; + (context, request) } - RawCommand::CancelAndRefund { + CliCommand::CancelAndRefund { swap_id: SwapId { swap_id }, bitcoin, + tor, } => { - let (bitcoin_electrum_rpc_url, bitcoin_target_block) = - bitcoin.apply_defaults(is_testnet)?; + let request = Request::new(Method::CancelAndRefund { swap_id }); - Arguments { - env_config: env_config_from(is_testnet), + let context = Context::build( + Some(bitcoin), + None, + Some(tor), + data, + is_testnet, debug, json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::CancelAndRefund { - swap_id, - bitcoin_electrum_rpc_url, - bitcoin_target_block, - }, - } + None, + ) + .await?; + (context, request) } - RawCommand::ListSellers { + CliCommand::ListSellers { rendezvous_point, - tor: Tor { tor_socks5_port }, - } => Arguments { - env_config: env_config_from(is_testnet), - debug, - json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::ListSellers { - rendezvous_point, - tor_socks5_port, - namespace: XmrBtcNamespace::from_is_testnet(is_testnet), - }, - }, - RawCommand::ExportBitcoinWallet { bitcoin } => { - let (bitcoin_electrum_rpc_url, bitcoin_target_block) = - bitcoin.apply_defaults(is_testnet)?; + tor, + } => { + let request = Request::new(Method::ListSellers { rendezvous_point }); - Arguments { - env_config: env_config_from(is_testnet), + let context = + Context::build(None, None, Some(tor), data, is_testnet, debug, json, None).await?; + + (context, request) + } + CliCommand::ExportBitcoinWallet { bitcoin } => { + let request = Request::new(Method::ExportBitcoinWallet); + + let context = Context::build( + Some(bitcoin), + None, + None, + data, + is_testnet, debug, json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::ExportBitcoinWallet { - bitcoin_electrum_rpc_url, - bitcoin_target_block, - }, - } + None, + ) + .await?; + (context, request) + } + CliCommand::MoneroRecovery { + swap_id: SwapId { swap_id }, + } => { + let request = Request::new(Method::MoneroRecovery { swap_id }); + + let context = + Context::build(None, None, None, data, is_testnet, debug, json, None).await?; + + (context, request) } - RawCommand::MoneroRecovery { swap_id } => Arguments { - env_config: env_config_from(is_testnet), - debug, - json, - data_dir: data::data_dir_from(data, is_testnet)?, - cmd: Command::MoneroRecovery { - swap_id: swap_id.swap_id, - }, - }, }; - Ok(ParseResult::Arguments(Box::new(arguments))) -} - -#[derive(Debug, PartialEq, Eq)] -pub enum Command { - BuyXmr { - seller: Multiaddr, - bitcoin_electrum_rpc_url: Url, - bitcoin_target_block: usize, - bitcoin_change_address: bitcoin::Address, - monero_receive_address: monero::Address, - monero_daemon_address: Option, - tor_socks5_port: u16, - namespace: XmrBtcNamespace, - }, - History, - Config, - WithdrawBtc { - bitcoin_electrum_rpc_url: Url, - bitcoin_target_block: usize, - amount: Option, - address: Address, - }, - Balance { - bitcoin_electrum_rpc_url: Url, - bitcoin_target_block: usize, - }, - Resume { - swap_id: Uuid, - bitcoin_electrum_rpc_url: Url, - bitcoin_target_block: usize, - monero_daemon_address: Option, - tor_socks5_port: u16, - namespace: XmrBtcNamespace, - }, - CancelAndRefund { - swap_id: Uuid, - bitcoin_electrum_rpc_url: Url, - bitcoin_target_block: usize, - }, - ListSellers { - rendezvous_point: Multiaddr, - namespace: XmrBtcNamespace, - tor_socks5_port: u16, - }, - ExportBitcoinWallet { - bitcoin_electrum_rpc_url: Url, - bitcoin_target_block: usize, - }, - MoneroRecovery { - swap_id: Uuid, - }, + Ok(ParseResult::Context(Arc::new(context), Box::new(request))) } #[derive(structopt::StructOpt, Debug)] @@ -300,7 +258,7 @@ pub enum Command { author, version = env!("VERGEN_GIT_DESCRIBE") )] -struct RawArguments { +struct Arguments { // global is necessary to ensure that clap can match against testnet in subcommands #[structopt( long, @@ -327,11 +285,11 @@ struct RawArguments { json: bool, #[structopt(subcommand)] - cmd: RawCommand, + cmd: CliCommand, } #[derive(structopt::StructOpt, Debug)] -enum RawCommand { +enum CliCommand { /// Start a BTC for XMR swap BuyXmr { #[structopt(flatten)] @@ -342,7 +300,8 @@ enum RawCommand { #[structopt( long = "change-address", - help = "The bitcoin address where any form of change or excess funds should be sent to" + help = "The bitcoin address where any form of change or excess funds should be sent to", + parse(try_from_str = bitcoin_address::parse) )] bitcoin_change_address: bitcoin::Address, @@ -351,7 +310,7 @@ enum RawCommand { #[structopt(long = "receive-address", help = "The monero address where you would like to receive monero", - parse(try_from_str = parse_monero_address) + parse(try_from_str = monero_address::parse) )] monero_receive_address: monero::Address, @@ -372,13 +331,34 @@ enum RawCommand { help = "Optionally specify the amount of Bitcoin to be withdrawn. If not specified the wallet will be drained." )] amount: Option, - #[structopt(long = "address", help = "The address to receive the Bitcoin.")] - address: Address, + + #[structopt(long = "address", + help = "The address to receive the Bitcoin.", + parse(try_from_str = bitcoin_address::parse) + )] + address: bitcoin::Address, }, #[structopt(about = "Prints the Bitcoin balance.")] Balance { - #[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")] - bitcoin_electrum_rpc_url: Option, + #[structopt(flatten)] + bitcoin: Bitcoin, + }, + #[structopt(about = "Starts a JSON-RPC server")] + StartDaemon { + #[structopt(flatten)] + bitcoin: Bitcoin, + + #[structopt(flatten)] + monero: Monero, + + #[structopt( + long = "server-address", + help = "The socket address the server should use" + )] + server_address: Option, + + #[structopt(flatten)] + tor: Tor, }, /// Resume a swap Resume { @@ -402,6 +382,9 @@ enum RawCommand { #[structopt(flatten)] bitcoin: Bitcoin, + + #[structopt(flatten)] + tor: Tor, }, /// Discover and list sellers (i.e. ASB providers) ListSellers { @@ -429,28 +412,40 @@ enum RawCommand { } #[derive(structopt::StructOpt, Debug)] -struct Monero { +pub struct Monero { #[structopt( long = "monero-daemon-address", - help = "Specify to connect to a monero daemon of your choice: :. If none is specified, we will connect to a public node." + help = "Specify to connect to a monero daemon of your choice: :" )] - monero_daemon_address: Option, + pub monero_daemon_address: Option, +} + +impl Monero { + pub fn apply_defaults(self, testnet: bool) -> String { + if let Some(address) = self.monero_daemon_address { + address + } else if testnet { + DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string() + } else { + DEFAULT_MONERO_DAEMON_ADDRESS.to_string() + } + } } #[derive(structopt::StructOpt, Debug)] -struct Bitcoin { +pub struct Bitcoin { #[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")] - bitcoin_electrum_rpc_url: Option, + pub bitcoin_electrum_rpc_url: Option, #[structopt( long = "bitcoin-target-block", help = "Estimate Bitcoin fees such that transactions are confirmed within the specified number of blocks" )] - bitcoin_target_block: Option, + pub bitcoin_target_block: Option, } impl Bitcoin { - fn apply_defaults(self, testnet: bool) -> Result<(Url, usize)> { + pub fn apply_defaults(self, testnet: bool) -> Result<(Url, usize)> { let bitcoin_electrum_rpc_url = if let Some(url) = self.bitcoin_electrum_rpc_url { url } else if testnet { @@ -472,13 +467,13 @@ impl Bitcoin { } #[derive(structopt::StructOpt, Debug)] -struct Tor { +pub struct Tor { #[structopt( long = "tor-socks5-port", help = "Your local Tor socks5 proxy port", default_value = DEFAULT_TOR_SOCKS5_PORT )] - tor_socks5_port: u16, + pub tor_socks5_port: u16, } #[derive(structopt::StructOpt, Debug)] @@ -499,137 +494,26 @@ struct Seller { seller: Multiaddr, } -mod data { - use super::*; - - pub fn data_dir_from(arg_dir: Option, testnet: bool) -> Result { - let base_dir = match arg_dir { - Some(custom_base_dir) => custom_base_dir, - None => os_default()?, - }; - - let sub_directory = if testnet { "testnet" } else { "mainnet" }; - - Ok(base_dir.join(sub_directory)) - } - - fn os_default() -> Result { - Ok(system_data_dir()?.join("cli")) - } -} - -fn env_config_from(testnet: bool) -> env::Config { - if testnet { - env::Testnet::get_config() - } else { - env::Mainnet::get_config() - } -} - -fn bitcoin_address(address: Address, is_testnet: bool) -> Result

{ - let network = if is_testnet { - bitcoin::Network::Testnet - } else { - bitcoin::Network::Bitcoin - }; - - if address.network != network { - bail!(BitcoinAddressNetworkMismatch { - expected: network, - actual: address.network - }); - } - - Ok(address) -} - -fn validate_monero_address( - address: monero::Address, - testnet: bool, -) -> Result { - let expected_network = if testnet { - monero::Network::Stagenet - } else { - monero::Network::Mainnet - }; - - if address.network != expected_network { - return Err(MoneroAddressNetworkMismatch { - expected: expected_network, - actual: address.network, - }); - } - - Ok(address) -} - -fn validate_bitcoin_address(address: bitcoin::Address, testnet: bool) -> Result { - let expected_network = if testnet { - bitcoin::Network::Testnet - } else { - bitcoin::Network::Bitcoin - }; - - if address.network != expected_network { - anyhow::bail!( - "Invalid Bitcoin address provided; expected network {} but provided address is for {}", - expected_network, - address.network - ); - } - - if address.address_type() != Some(AddressType::P2wpkh) { - anyhow::bail!("Invalid Bitcoin address provided, only bech32 format is supported!") - } - - Ok(address) -} - -fn parse_monero_address(s: &str) -> Result { - monero::Address::from_str(s).with_context(|| { - format!( - "Failed to parse {} as a monero address, please make sure it is a valid address", - s - ) - }) -} - -#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] -#[error("Invalid monero address provided, expected address on network {expected:?} but address provided is on {actual:?}")] -pub struct MoneroAddressNetworkMismatch { - expected: monero::Network, - actual: monero::Network, -} - -#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq, Serialize)] -#[error("Invalid Bitcoin address provided, expected address on network {expected:?} but address provided is on {actual:?}")] -pub struct BitcoinAddressNetworkMismatch { - #[serde(with = "crate::bitcoin::network")] - expected: bitcoin::Network, - #[serde(with = "crate::bitcoin::network")] - actual: bitcoin::Network, -} - #[cfg(test)] mod tests { use super::*; - use crate::tor::DEFAULT_SOCKS5_PORT; + + use crate::api::api_test::*; + use crate::api::Config; + use crate::monero::monero_address::MoneroAddressNetworkMismatch; const BINARY_NAME: &str = "swap"; + const ARGS_DATA_DIR: &str = "/tmp/dir/"; - const TESTNET: &str = "testnet"; - const MAINNET: &str = "mainnet"; + #[tokio::test] - const MONERO_STAGENET_ADDRESS: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"; - const BITCOIN_TESTNET_ADDRESS: &str = "tb1qr3em6k3gfnyl8r7q0v7t4tlnyxzgxma3lressv"; - const MONERO_MAINNET_ADDRESS: &str = "44Ato7HveWidJYUAVw5QffEcEtSH1DwzSP3FPPkHxNAS4LX9CqgucphTisH978FLHE34YNEx7FcbBfQLQUU8m3NUC4VqsRa"; - const BITCOIN_MAINNET_ADDRESS: &str = "bc1qe4epnfklcaa0mun26yz5g8k24em5u9f92hy325"; - const MULTI_ADDRESS: &str = - "/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; - const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b"; + // this test is very long, however it just checks that various CLI arguments sets the + // internal Context and Request properly. It is unlikely to fail and splitting it in various + // tests would require to run the tests sequantially which is very slow (due to the context + // need to access files like the Bitcoin wallet). + async fn test_cli_arguments() { + // given_buy_xmr_on_mainnet_then_defaults_to_mainnet - #[test] - fn given_buy_xmr_on_mainnet_then_defaults_to_mainnet() { let raw_ars = vec![ BINARY_NAME, "buy-xmr", @@ -641,15 +525,34 @@ mod tests { MULTI_ADDRESS, ]; - let expected_args = - ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults().into_boxed()); - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, false, false); - assert_eq!(expected_args, args); - } + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; - #[test] - fn given_buy_xmr_on_testnet_then_defaults_to_testnet() { + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::buy_xmr(is_testnet), + ); + + // since Uuid is random, copy before comparing requests + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_testnet_then_defaults_to_testnet let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -662,16 +565,33 @@ mod tests { MULTI_ADDRESS, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, false, false); - assert_eq!( - args, - ParseResult::Arguments(Arguments::buy_xmr_testnet_defaults().into_boxed()) + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::buy_xmr(is_testnet), ); - } - #[test] - fn given_buy_xmr_on_mainnet_with_testnet_address_then_fails() { + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_mainnet_with_testnet_address_then_fails let raw_ars = vec![ BINARY_NAME, "buy-xmr", @@ -683,7 +603,7 @@ mod tests { MULTI_ADDRESS, ]; - let err = parse_args_and_apply_defaults(raw_ars).unwrap_err(); + let err = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); assert_eq!( err.downcast_ref::().unwrap(), @@ -692,10 +612,8 @@ mod tests { actual: monero::Network::Stagenet } ); - } - #[test] - fn given_buy_xmr_on_testnet_with_mainnet_address_then_fails() { + // given_buy_xmr_on_testnet_with_mainnet_address_then_fails let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -708,7 +626,7 @@ mod tests { MULTI_ADDRESS, ]; - let err = parse_args_and_apply_defaults(raw_ars).unwrap_err(); + let err = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); assert_eq!( err.downcast_ref::().unwrap(), @@ -717,88 +635,126 @@ mod tests { actual: monero::Network::Mainnet } ); - } - #[test] - fn given_resume_on_mainnet_then_defaults_to_mainnet() { + // given_resume_on_mainnet_then_defaults_to_mainnet let raw_ars = vec![BINARY_NAME, "resume", "--swap-id", SWAP_ID]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, false, false); - assert_eq!( - args, - ParseResult::Arguments(Arguments::resume_mainnet_defaults().into_boxed()) + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::resume(), ); - } - #[test] - fn given_resume_on_testnet_then_defaults_to_testnet() { + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_resume_on_testnet_then_defaults_to_testnet let raw_ars = vec![BINARY_NAME, "--testnet", "resume", "--swap-id", SWAP_ID]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, false, false); - assert_eq!( - args, - ParseResult::Arguments(Arguments::resume_testnet_defaults().into_boxed()) + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::resume(), ); - } - #[test] - fn given_cancel_on_mainnet_then_defaults_to_mainnet() { + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_cancel_on_mainnet_then_defaults_to_mainnet let raw_ars = vec![BINARY_NAME, "cancel", "--swap-id", SWAP_ID]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); - assert_eq!( - args, - ParseResult::Arguments(Arguments::cancel_mainnet_defaults().into_boxed()) + let (is_testnet, debug, json) = (false, false, false); + + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::cancel(), ); - } - #[test] - fn given_cancel_on_testnet_then_defaults_to_testnet() { + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_cancel_on_testnet_then_defaults_to_testnet let raw_ars = vec![BINARY_NAME, "--testnet", "cancel", "--swap-id", SWAP_ID]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, false, false); - assert_eq!( - args, - ParseResult::Arguments(Arguments::cancel_testnet_defaults().into_boxed()) + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::cancel(), ); - } - #[test] - fn given_refund_on_mainnet_then_defaults_to_mainnet() { + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + let raw_ars = vec![BINARY_NAME, "refund", "--swap-id", SWAP_ID]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, false, false); - assert_eq!( - args, - ParseResult::Arguments(Arguments::refund_mainnet_defaults().into_boxed()) + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::refund(), ); - } - #[test] - fn given_refund_on_testnet_then_defaults_to_testnet() { + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_refund_on_testnet_then_defaults_to_testnet let raw_ars = vec![BINARY_NAME, "--testnet", "refund", "--swap-id", SWAP_ID]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, false, false); - assert_eq!( - args, - ParseResult::Arguments(Arguments::refund_testnet_defaults().into_boxed()) + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::refund(), ); - } - #[test] - fn given_with_data_dir_then_data_dir_set() { - let data_dir = "/some/path/to/dir"; + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_mainnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--data-base-dir", - data_dir, + ARGS_DATA_DIR, "buy-xmr", "--change-address", BITCOIN_MAINNET_ADDRESS, @@ -808,22 +764,39 @@ mod tests { MULTI_ADDRESS, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, false, false); + let data_dir = PathBuf::from_str(ARGS_DATA_DIR).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::buy_xmr_mainnet_defaults() - .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet")) - .into_boxed() - ) + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, Some(data_dir.clone()), debug, json), + Request::buy_xmr(is_testnet), ); + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_testnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--testnet", "--data-base-dir", - data_dir, + ARGS_DATA_DIR, "buy-xmr", "--change-address", BITCOIN_TESTNET_ADDRESS, @@ -833,61 +806,99 @@ mod tests { MULTI_ADDRESS, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let data_dir = PathBuf::from_str(ARGS_DATA_DIR).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, false, false); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::buy_xmr_testnet_defaults() - .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet")) - .into_boxed() - ) + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, Some(data_dir.clone()), debug, json), + Request::buy_xmr(is_testnet), ); + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_resume_on_mainnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--data-base-dir", - data_dir, + ARGS_DATA_DIR, "resume", "--swap-id", SWAP_ID, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let data_dir = PathBuf::from_str(ARGS_DATA_DIR).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, false, false); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::resume_mainnet_defaults() - .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet")) - .into_boxed() - ) + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, Some(data_dir.clone()), debug, json), + Request::resume(), ); + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_resume_on_testnet_with_data_dir_then_data_dir_set let raw_ars = vec![ BINARY_NAME, "--testnet", "--data-base-dir", - data_dir, + ARGS_DATA_DIR, "resume", "--swap-id", SWAP_ID, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + let data_dir = PathBuf::from_str(ARGS_DATA_DIR).unwrap(); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, false, false); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::resume_testnet_defaults() - .with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet")) - .into_boxed() - ) + let (expected_config, expected_request) = ( + Config::default(is_testnet, Some(data_dir.clone()), debug, json), + Request::resume(), ); - } - #[test] - fn given_with_debug_then_debug_set() { + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_mainnet_with_debug_then_debug_set let raw_ars = vec![ BINARY_NAME, "--debug", @@ -900,16 +911,33 @@ mod tests { MULTI_ADDRESS, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::buy_xmr_mainnet_defaults() - .with_debug() - .into_boxed() - ) + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, true, false); + + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::buy_xmr(is_testnet), ); + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_testnet_with_debug_then_debug_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -923,28 +951,62 @@ mod tests { MULTI_ADDRESS, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::buy_xmr_testnet_defaults() - .with_debug() - .into_boxed() - ) + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, true, false); + + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::buy_xmr(is_testnet), ); + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_resume_on_mainnet_with_debug_then_debug_set let raw_ars = vec![BINARY_NAME, "--debug", "resume", "--swap-id", SWAP_ID]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::resume_mainnet_defaults() - .with_debug() - .into_boxed() - ) + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, true, false); + + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::resume(), ); + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_resume_on_testnet_with_debug_then_debug_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -954,19 +1016,23 @@ mod tests { SWAP_ID, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::resume_testnet_defaults() - .with_debug() - .into_boxed() - ) - ); - } + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, true, false); - #[test] - fn given_with_json_then_json_set() { + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::resume(), + ); + + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_mainnet_with_json_then_json_set let raw_ars = vec![ BINARY_NAME, "--json", @@ -979,16 +1045,33 @@ mod tests { MULTI_ADDRESS, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::buy_xmr_mainnet_defaults() - .with_json() - .into_boxed() - ) + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, false, true); + + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::buy_xmr(is_testnet), ); + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_buy_xmr_on_testnet_with_json_then_json_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -1002,28 +1085,51 @@ mod tests { MULTI_ADDRESS, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::buy_xmr_testnet_defaults() - .with_json() - .into_boxed() - ) - ); + let (is_testnet, debug, json) = (true, false, true); + let (expected_config, mut expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::buy_xmr(is_testnet), + ); + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + if let Method::BuyXmr { + ref mut swap_id, .. + } = expected_request.cmd + { + *swap_id = match actual_request.cmd { + Method::BuyXmr { swap_id, .. } => swap_id, + _ => panic!("Not the Method we expected"), + } + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_resume_on_mainnet_with_json_then_json_set let raw_ars = vec![BINARY_NAME, "--json", "resume", "--swap-id", SWAP_ID]; + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (false, false, true); - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::resume_mainnet_defaults() - .with_json() - .into_boxed() - ) + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::resume(), ); + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // given_resume_on_testnet_with_json_then_json_set let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -1033,19 +1139,23 @@ mod tests { SWAP_ID, ]; - let args = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert_eq!( - args, - ParseResult::Arguments( - Arguments::resume_testnet_defaults() - .with_json() - .into_boxed() - ) - ); - } + let args = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + let (is_testnet, debug, json) = (true, false, true); - #[test] - fn only_bech32_addresses_mainnet_are_allowed() { + let (expected_config, expected_request) = ( + Config::default(is_testnet, None, debug, json), + Request::resume(), + ); + + let (actual_config, actual_request) = match args { + ParseResult::Context(context, request) => (context.config.clone(), request), + _ => panic!("Couldn't parse result"), + }; + + assert_eq!(actual_config, expected_config); + assert_eq!(actual_request, Box::new(expected_request)); + + // only_bech32_addresses_mainnet_are_allowed let raw_ars = vec![ BINARY_NAME, "buy-xmr", @@ -1056,11 +1166,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars); - assert_eq!( - result.unwrap_err().to_string(), - "Invalid Bitcoin address provided, only bech32 format is supported!" - ); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1072,11 +1178,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars); - assert_eq!( - result.unwrap_err().to_string(), - "Invalid Bitcoin address provided, only bech32 format is supported!" - ); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1088,12 +1190,10 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert!(matches!(result, ParseResult::Arguments(_))); - } + let result = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + assert!(matches!(result, ParseResult::Context(_, _))); - #[test] - fn only_bech32_addresses_testnet_are_allowed() { + // only_bech32_addresses_testnet_are_allowed let raw_ars = vec![ BINARY_NAME, "--testnet", @@ -1105,11 +1205,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars); - assert_eq!( - result.unwrap_err().to_string(), - "Invalid Bitcoin address provided, only bech32 format is supported!" - ); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1122,11 +1218,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars); - assert_eq!( - result.unwrap_err().to_string(), - "Invalid Bitcoin address provided, only bech32 format is supported!" - ); + parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); let raw_ars = vec![ BINARY_NAME, @@ -1139,166 +1231,7 @@ mod tests { "--seller", MULTI_ADDRESS, ]; - let result = parse_args_and_apply_defaults(raw_ars).unwrap(); - assert!(matches!(result, ParseResult::Arguments(_))); - } - - impl Arguments { - pub fn buy_xmr_testnet_defaults() -> Self { - Self { - env_config: env::Testnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(TESTNET), - cmd: Command::BuyXmr { - seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) - .unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, - bitcoin_change_address: BITCOIN_TESTNET_ADDRESS.parse().unwrap(), - monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS) - .unwrap(), - monero_daemon_address: None, - tor_socks5_port: DEFAULT_SOCKS5_PORT, - namespace: XmrBtcNamespace::Testnet, - }, - } - } - - pub fn buy_xmr_mainnet_defaults() -> Self { - Self { - env_config: env::Mainnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(MAINNET), - cmd: Command::BuyXmr { - seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, - bitcoin_change_address: BITCOIN_MAINNET_ADDRESS.parse().unwrap(), - monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS) - .unwrap(), - monero_daemon_address: None, - tor_socks5_port: DEFAULT_SOCKS5_PORT, - namespace: XmrBtcNamespace::Mainnet, - }, - } - } - - pub fn resume_testnet_defaults() -> Self { - Self { - env_config: env::Testnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(TESTNET), - cmd: Command::Resume { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) - .unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, - monero_daemon_address: None, - tor_socks5_port: DEFAULT_SOCKS5_PORT, - namespace: XmrBtcNamespace::Testnet, - }, - } - } - - pub fn resume_mainnet_defaults() -> Self { - Self { - env_config: env::Mainnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(MAINNET), - cmd: Command::Resume { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, - monero_daemon_address: None, - tor_socks5_port: DEFAULT_SOCKS5_PORT, - namespace: XmrBtcNamespace::Mainnet, - }, - } - } - - pub fn cancel_testnet_defaults() -> Self { - Self { - env_config: env::Testnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(TESTNET), - cmd: Command::CancelAndRefund { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) - .unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, - }, - } - } - - pub fn cancel_mainnet_defaults() -> Self { - Self { - env_config: env::Mainnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(MAINNET), - cmd: Command::CancelAndRefund { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, - }, - } - } - - pub fn refund_testnet_defaults() -> Self { - Self { - env_config: env::Testnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(TESTNET), - cmd: Command::CancelAndRefund { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) - .unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, - }, - } - } - - pub fn refund_mainnet_defaults() -> Self { - Self { - env_config: env::Mainnet::get_config(), - debug: false, - json: false, - data_dir: data_dir_path_cli().join(MAINNET), - cmd: Command::CancelAndRefund { - swap_id: Uuid::from_str(SWAP_ID).unwrap(), - bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), - bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, - }, - } - } - - pub fn with_data_dir(mut self, data_dir: PathBuf) -> Self { - self.data_dir = data_dir; - self - } - - pub fn with_debug(mut self) -> Self { - self.debug = true; - self - } - - pub fn with_json(mut self) -> Self { - self.json = true; - self - } - - pub fn into_boxed(self) -> Box { - Box::new(self) - } - } - - fn data_dir_path_cli() -> PathBuf { - system_data_dir().unwrap().join("cli") + let result = parse_args_and_apply_defaults(raw_ars).await.unwrap(); + assert!(matches!(result, ParseResult::Context(_, _))); } } diff --git a/swap/src/cli/event_loop.rs b/swap/src/cli/event_loop.rs index 1799d20b..23aa0f38 100644 --- a/swap/src/cli/event_loop.rs +++ b/swap/src/cli/event_loop.rs @@ -151,17 +151,17 @@ impl EventLoop { return; } SwarmEvent::Behaviour(OutEvent::Failure { peer, error }) => { - tracing::warn!(%peer, "Communication error: {:#}", error); + tracing::warn!(%peer, err = %error, "Communication error"); return; } SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } if peer_id == self.alice_peer_id => { - tracing::info!("Connected to Alice at {}", endpoint.get_remote_address()); + tracing::info!(peer_id = %endpoint.get_remote_address(), "Connected to Alice"); } SwarmEvent::Dialing(peer_id) if peer_id == self.alice_peer_id => { - tracing::debug!("Dialling Alice at {}", peer_id); + tracing::debug!(%peer_id, "Dialling Alice"); } SwarmEvent::ConnectionClosed { peer_id, endpoint, num_established, cause: Some(error) } if peer_id == self.alice_peer_id && num_established == 0 => { - tracing::warn!("Lost connection to Alice at {}, cause: {}", endpoint.get_remote_address(), error); + tracing::warn!(peer_id = %endpoint.get_remote_address(), cause = %error, "Lost connection to Alice"); } SwarmEvent::ConnectionClosed { peer_id, num_established, cause: None, .. } if peer_id == self.alice_peer_id && num_established == 0 => { // no error means the disconnection was requested @@ -169,10 +169,10 @@ impl EventLoop { return; } SwarmEvent::OutgoingConnectionError { peer_id, error } if matches!(peer_id, Some(alice_peer_id) if alice_peer_id == self.alice_peer_id) => { - tracing::warn!( "Failed to dial Alice: {}", error); + tracing::warn!(%error, "Failed to dial Alice"); if let Some(duration) = self.swarm.behaviour_mut().redial.until_next_redial() { - tracing::info!("Next redial attempt in {}s", duration.as_secs()); + tracing::info!(seconds_until_next_redial = %duration.as_secs(), "Waiting for next redial attempt"); } } @@ -241,6 +241,7 @@ impl EventLoopHandle { } pub async fn request_quote(&mut self) -> Result { + tracing::debug!("Requesting quote"); Ok(self.quote.send_receive(()).await?) } diff --git a/swap/src/cli/tracing.rs b/swap/src/cli/tracing.rs index a1cd77fb..7cb0721b 100644 --- a/swap/src/cli/tracing.rs +++ b/swap/src/cli/tracing.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use std::option::Option::Some; use std::path::Path; use time::format_description::well_known::Rfc3339; use tracing::subscriber::set_global_default; @@ -7,55 +6,32 @@ use tracing::{Event, Level, Subscriber}; use tracing_subscriber::fmt::format::{DefaultFields, Format, JsonFields}; use tracing_subscriber::fmt::time::UtcTime; use tracing_subscriber::layer::{Context, SubscriberExt}; -use tracing_subscriber::{fmt, EnvFilter, FmtSubscriber, Layer, Registry}; -use uuid::Uuid; +use tracing_subscriber::{fmt, EnvFilter, Layer, Registry}; -pub fn init(debug: bool, json: bool, dir: impl AsRef, swap_id: Option) -> Result<()> { - if let Some(swap_id) = swap_id { - let level_filter = EnvFilter::try_new("swap=debug")?; +pub fn init(debug: bool, json: bool, dir: impl AsRef) -> Result<()> { + let level_filter = EnvFilter::try_new("swap=debug")?; + let registry = Registry::default().with(level_filter); - let registry = Registry::default().with(level_filter); + let appender = tracing_appender::rolling::never(dir.as_ref(), "swap-all.log"); + let (appender, _guard) = tracing_appender::non_blocking(appender); - let appender = - tracing_appender::rolling::never(dir.as_ref(), format!("swap-{}.log", swap_id)); - let (appender, guard) = tracing_appender::non_blocking(appender); + let file_logger = registry.with( + fmt::layer() + .with_ansi(false) + .with_target(false) + .json() + .with_writer(appender), + ); - std::mem::forget(guard); - - let file_logger = registry.with( - fmt::layer() - .with_ansi(false) - .with_target(false) - .json() - .with_writer(appender), - ); - - if json && debug { - set_global_default(file_logger.with(debug_json_terminal_printer()))?; - } else if json && !debug { - set_global_default(file_logger.with(info_json_terminal_printer()))?; - } else if !json && debug { - set_global_default(file_logger.with(debug_terminal_printer()))?; - } else { - set_global_default(file_logger.with(info_terminal_printer()))?; - } + if json && debug { + set_global_default(file_logger.with(debug_json_terminal_printer()))?; + } else if json && !debug { + set_global_default(file_logger.with(info_json_terminal_printer()))?; + } else if !json && debug { + set_global_default(file_logger.with(debug_terminal_printer()))?; } else { - let level = if debug { Level::DEBUG } else { Level::INFO }; - let is_terminal = atty::is(atty::Stream::Stderr); - - let builder = FmtSubscriber::builder() - .with_env_filter(format!("swap={}", level)) - .with_writer(std::io::stderr) - .with_ansi(is_terminal) - .with_timer(UtcTime::rfc_3339()) - .with_target(false); - - if json { - builder.json().init(); - } else { - builder.init(); - } - }; + set_global_default(file_logger.with(info_terminal_printer()))?; + } tracing::info!("Logging initialized to {}", dir.as_ref().display()); Ok(()) @@ -66,19 +42,11 @@ pub struct StdErrPrinter { level: Level, } -type StdErrLayer = tracing_subscriber::fmt::Layer< - S, - DefaultFields, - Format, - fn() -> std::io::Stderr, ->; +type StdErrLayer = + fmt::Layer, fn() -> std::io::Stderr>; -type StdErrJsonLayer = tracing_subscriber::fmt::Layer< - S, - JsonFields, - Format, - fn() -> std::io::Stderr, ->; +type StdErrJsonLayer = + fmt::Layer, fn() -> std::io::Stderr>; fn debug_terminal_printer() -> StdErrPrinter>> { let is_terminal = atty::is(atty::Stream::Stderr); diff --git a/swap/src/database/sqlite.rs b/swap/src/database/sqlite.rs index d39e3f87..751f76aa 100644 --- a/swap/src/database/sqlite.rs +++ b/swap/src/database/sqlite.rs @@ -1,11 +1,12 @@ use crate::database::Swap; use crate::monero::Address; use crate::protocol::{Database, State}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use libp2p::{Multiaddr, PeerId}; use sqlx::sqlite::Sqlite; use sqlx::{Pool, SqlitePool}; +use std::collections::HashMap; use std::path::Path; use std::str::FromStr; use time::OffsetDateTime; @@ -149,7 +150,7 @@ impl Database for SqliteDatabase { let rows = sqlx::query!( r#" - SELECT address + SELECT DISTINCT address FROM peer_addresses WHERE peer_id = ? "#, @@ -169,6 +170,25 @@ impl Database for SqliteDatabase { addresses } + async fn get_swap_start_date(&self, swap_id: Uuid) -> Result { + let mut conn = self.pool.acquire().await?; + let swap_id = swap_id.to_string(); + + let row = sqlx::query!( + r#" + SELECT min(entered_at) as start_date + FROM swap_states + WHERE swap_id = ? + "#, + swap_id + ) + .fetch_one(&mut conn) + .await?; + + row.start_date + .ok_or_else(|| anyhow!("Could not get swap start date")) + } + async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()> { let mut conn = self.pool.acquire().await?; let entered_at = OffsetDateTime::now_utc(); @@ -249,6 +269,69 @@ impl Database for SqliteDatabase { result } + + async fn get_states(&self, swap_id: Uuid) -> Result> { + let mut conn = self.pool.acquire().await?; + let swap_id = swap_id.to_string(); + + // TODO: We should use query! instead of query here to allow for at-compile-time validation + // I didn't manage to generate the mappings for the query! macro because of problems with sqlx-cli + let rows = sqlx::query!( + r#" + SELECT state + FROM swap_states + WHERE swap_id = ? + "#, + swap_id + ) + .fetch_all(&mut conn) + .await?; + + let result = rows + .iter() + .map(|row| { + let state_str: &str = &row.state; + + let state = match serde_json::from_str::(state_str) { + Ok(a) => Ok(State::from(a)), + Err(e) => Err(e), + }?; + Ok(state) + }) + .collect::>>(); + + result + } + + async fn raw_all(&self) -> Result>> { + let mut conn = self.pool.acquire().await?; + let rows = sqlx::query!( + r#" + SELECT swap_id, state + FROM swap_states + "# + ) + .fetch_all(&mut conn) + .await?; + + let mut swaps: HashMap> = HashMap::new(); + + for row in &rows { + let swap_id = Uuid::from_str(&row.swap_id)?; + let state = serde_json::from_str(&row.state)?; + + if let std::collections::hash_map::Entry::Vacant(e) = swaps.entry(swap_id) { + e.insert(vec![state]); + } else { + swaps + .get_mut(&swap_id) + .ok_or_else(|| anyhow!("Error while retrieving the swap"))? + .push(state); + } + } + + Ok(swaps) + } } #[cfg(test)] diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 4865187b..84a39dcc 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -16,6 +16,7 @@ missing_copy_implementations )] +pub mod api; pub mod asb; pub mod bitcoin; pub mod cli; @@ -28,6 +29,7 @@ pub mod libp2p_ext; pub mod monero; pub mod network; pub mod protocol; +pub mod rpc; pub mod seed; pub mod tor; pub mod tracing_ext; diff --git a/swap/src/monero.rs b/swap/src/monero.rs index d46fc157..8205e75f 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -42,6 +42,13 @@ pub fn private_key_from_secp256k1_scalar(scalar: bitcoin::Scalar) -> PrivateKey #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct PrivateViewKey(#[serde(with = "monero_private_key")] PrivateKey); +impl fmt::Display for PrivateViewKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Delegate to the Display implementation of PrivateKey + write!(f, "{}", self.0) + } +} + impl PrivateViewKey { pub fn new_random(rng: &mut R) -> Self { let scalar = Scalar::random(rng); @@ -320,6 +327,52 @@ pub mod monero_amount { } } +pub mod monero_address { + use anyhow::{bail, Context, Result}; + use std::str::FromStr; + + #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq)] + #[error("Invalid monero address provided, expected address on network {expected:?} but address provided is on {actual:?}")] + pub struct MoneroAddressNetworkMismatch { + pub expected: monero::Network, + pub actual: monero::Network, + } + + pub fn parse(s: &str) -> Result { + monero::Address::from_str(s).with_context(|| { + format!( + "Failed to parse {} as a monero address, please make sure it is a valid address", + s + ) + }) + } + + pub fn validate( + address: monero::Address, + expected_network: monero::Network, + ) -> Result { + if address.network != expected_network { + bail!(MoneroAddressNetworkMismatch { + expected: expected_network, + actual: address.network, + }); + } + Ok(address) + } + + pub fn validate_is_testnet( + address: monero::Address, + is_testnet: bool, + ) -> Result { + let expected_network = if is_testnet { + monero::Network::Stagenet + } else { + monero::Network::Mainnet + }; + validate(address, expected_network) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/swap/src/protocol.rs b/swap/src/protocol.rs index 9389e3ae..0e15f89a 100644 --- a/swap/src/protocol.rs +++ b/swap/src/protocol.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use sigma_fun::ext::dl_secp256k1_ed25519_eq::{CrossCurveDLEQ, CrossCurveDLEQProof}; use sigma_fun::HashTranscript; +use std::collections::HashMap; use std::convert::TryInto; use uuid::Uuid; @@ -139,7 +140,10 @@ pub trait Database { async fn get_monero_address(&self, swap_id: Uuid) -> Result; async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()>; async fn get_addresses(&self, peer_id: PeerId) -> Result>; + async fn get_swap_start_date(&self, swap_id: Uuid) -> Result; async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()>; async fn get_state(&self, swap_id: Uuid) -> Result; + async fn get_states(&self, swap_id: Uuid) -> Result>; async fn all(&self) -> Result>; + async fn raw_all(&self) -> Result>>; } diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 941e5a43..0eef7dcd 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -112,7 +112,7 @@ where } AliceState::BtcLocked { state3 } => { match state3.expired_timelocks(bitcoin_wallet).await? { - ExpiredTimelocks::None => { + ExpiredTimelocks::None { .. } => { // Record the current monero wallet block height so we don't have to scan from // block 0 for scenarios where we create a refund wallet. let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; @@ -135,7 +135,7 @@ where transfer_proof, state3, } => match state3.expired_timelocks(bitcoin_wallet).await? { - ExpiredTimelocks::None => { + ExpiredTimelocks::None { .. } => { monero_wallet .watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof.clone(), 1)) .await @@ -221,7 +221,7 @@ where encrypted_signature, state3, } => match state3.expired_timelocks(bitcoin_wallet).await? { - ExpiredTimelocks::None => { + ExpiredTimelocks::None { .. } => { let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await; match state3.signed_redeem_transaction(*encrypted_signature) { Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await { diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 4d0e5a31..edf902b6 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -21,9 +21,10 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::fmt; use uuid::Uuid; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] pub enum BobState { Started { + #[serde(with = "::bitcoin::util::amount::serde::as_sat")] btc_amount: bitcoin::Amount, change_address: bitcoin::Address, }, @@ -287,13 +288,13 @@ pub struct State2 { S_a_monero: monero::PublicKey, S_a_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, - xmr: monero::Amount, - cancel_timelock: CancelTimelock, - punish_timelock: PunishTimelock, - refund_address: bitcoin::Address, + pub xmr: monero::Amount, + pub cancel_timelock: CancelTimelock, + pub punish_timelock: PunishTimelock, + pub refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, - tx_lock: bitcoin::TxLock, + pub tx_lock: bitcoin::TxLock, tx_cancel_sig_a: Signature, tx_refund_encsig: bitcoin::EncryptedSignature, min_monero_confirmations: u64, @@ -302,9 +303,9 @@ pub struct State2 { #[serde(with = "::bitcoin::util::amount::serde::as_sat")] tx_punish_fee: bitcoin::Amount, #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - tx_refund_fee: bitcoin::Amount, + pub tx_refund_fee: bitcoin::Amount, #[serde(with = "::bitcoin::util::amount::serde::as_sat")] - tx_cancel_fee: bitcoin::Amount, + pub tx_cancel_fee: bitcoin::Amount, } impl State2 { @@ -439,7 +440,7 @@ impl State3 { self.tx_lock.txid() } - pub async fn current_epoch( + pub async fn expired_timelock( &self, bitcoin_wallet: &bitcoin::Wallet, ) -> Result { diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 66933a87..6f5de482 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -117,7 +117,7 @@ async fn next_state( } => { let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await; - if let ExpiredTimelocks::None = state3.current_epoch(bitcoin_wallet).await? { + if let ExpiredTimelocks::None { .. } = state3.expired_timelock(bitcoin_wallet).await? { let transfer_proof_watcher = event_loop_handle.recv_transfer_proof(); let cancel_timelock_expires = tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock); @@ -156,7 +156,7 @@ async fn next_state( } => { let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await; - if let ExpiredTimelocks::None = state.current_epoch(bitcoin_wallet).await? { + if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? { let watch_request = state.lock_xmr_watch_request(lock_transfer_proof); select! { @@ -185,7 +185,7 @@ async fn next_state( BobState::XmrLocked(state) => { let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await; - if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet).await? { + if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? { // Alice has locked Xmr // Bob sends Alice his key @@ -209,7 +209,7 @@ async fn next_state( BobState::EncSigSent(state) => { let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await; - if let ExpiredTimelocks::None = state.expired_timelock(bitcoin_wallet).await? { + if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? { select! { state5 = state.watch_for_redeem_btc(bitcoin_wallet) => { BobState::BtcRedeemed(state5?) @@ -269,12 +269,12 @@ async fn next_state( BobState::BtcCancelled(state) => { // Bob has cancelled the swap match state.expired_timelock(bitcoin_wallet).await? { - ExpiredTimelocks::None => { + ExpiredTimelocks::None { .. } => { bail!( "Internal error: canceled state reached before cancel timelock was expired" ); } - ExpiredTimelocks::Cancel => { + ExpiredTimelocks::Cancel { .. } => { state.publish_refund_btc(bitcoin_wallet).await?; BobState::BtcRefunded(state) } diff --git a/swap/src/rpc.rs b/swap/src/rpc.rs new file mode 100644 index 00000000..6e759057 --- /dev/null +++ b/swap/src/rpc.rs @@ -0,0 +1,31 @@ +use crate::api::Context; +use jsonrpsee::server::{RpcModule, ServerBuilder, ServerHandle}; +use std::net::SocketAddr; +use std::sync::Arc; +use thiserror::Error; + +pub mod methods; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Could not parse key value from params")] + ParseError, +} + +pub async fn run_server( + server_address: SocketAddr, + context: Arc, +) -> anyhow::Result<(SocketAddr, ServerHandle)> { + let server = ServerBuilder::default().build(server_address).await?; + let mut modules = RpcModule::new(()); + { + modules + .merge(methods::register_modules(Arc::clone(&context))?) + .expect("Could not register RPC modules") + } + + let addr = server.local_addr()?; + let server_handle = server.start(modules)?; + + Ok((addr, server_handle)) +} diff --git a/swap/src/rpc/methods.rs b/swap/src/rpc/methods.rs new file mode 100644 index 00000000..83100d4a --- /dev/null +++ b/swap/src/rpc/methods.rs @@ -0,0 +1,236 @@ +use crate::api::request::{Method, Request}; +use crate::api::Context; +use crate::bitcoin::bitcoin_address; +use crate::monero::monero_address; +use crate::{bitcoin, monero}; +use anyhow::Result; +use jsonrpsee::server::RpcModule; +use jsonrpsee::types::Params; +use libp2p::core::Multiaddr; +use std::collections::HashMap; +use std::str::FromStr; +use std::sync::Arc; +use uuid::Uuid; + +pub fn register_modules(context: Arc) -> Result>> { + let mut module = RpcModule::new(context); + + module.register_async_method("suspend_current_swap", |params, context| async move { + execute_request(params, Method::SuspendCurrentSwap, &context).await + })?; + + module.register_async_method("get_swap_info", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let swap_id = params + .get("swap_id") + .ok_or_else(|| jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string()))?; + + let swap_id = as_uuid(swap_id) + .ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?; + + execute_request(params_raw, Method::GetSwapInfo { swap_id }, &context).await + })?; + + module.register_async_method("get_bitcoin_balance", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let force_refresh = params + .get("force_refresh") + .ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain force_refresh".to_string()) + })? + .as_bool() + .ok_or_else(|| { + jsonrpsee_core::Error::Custom("force_refesh is not a boolean".to_string()) + })?; + + execute_request(params_raw, Method::Balance { force_refresh }, &context).await + })?; + + module.register_async_method("get_history", |params, context| async move { + execute_request(params, Method::History, &context).await + })?; + + module.register_async_method("get_raw_states", |params, context| async move { + execute_request(params, Method::GetRawStates, &context).await + })?; + + module.register_async_method("resume_swap", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let swap_id = params + .get("swap_id") + .ok_or_else(|| jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string()))?; + + let swap_id = as_uuid(swap_id) + .ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?; + + execute_request(params_raw, Method::Resume { swap_id }, &context).await + })?; + + module.register_async_method("cancel_refund_swap", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let swap_id = params + .get("swap_id") + .ok_or_else(|| jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string()))?; + + let swap_id = as_uuid(swap_id) + .ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?; + + execute_request(params_raw, Method::CancelAndRefund { swap_id }, &context).await + })?; + + module.register_async_method( + "get_monero_recovery_info", + |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let swap_id = params.get("swap_id").ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain swap_id".to_string()) + })?; + + let swap_id = as_uuid(swap_id).ok_or_else(|| { + jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()) + })?; + + execute_request(params_raw, Method::MoneroRecovery { swap_id }, &context).await + }, + )?; + + module.register_async_method("withdraw_btc", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let amount = if let Some(amount_str) = params.get("amount") { + Some( + ::bitcoin::Amount::from_str_in(amount_str, ::bitcoin::Denomination::Bitcoin) + .map_err(|_| { + jsonrpsee_core::Error::Custom("Unable to parse amount".to_string()) + })?, + ) + } else { + None + }; + + let withdraw_address = + bitcoin::Address::from_str(params.get("address").ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain address".to_string()) + })?) + .map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?; + let withdraw_address = + bitcoin_address::validate(withdraw_address, context.config.env_config.bitcoin_network)?; + + execute_request( + params_raw, + Method::WithdrawBtc { + amount, + address: withdraw_address, + }, + &context, + ) + .await + })?; + + module.register_async_method("buy_xmr", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let bitcoin_change_address = + bitcoin::Address::from_str(params.get("bitcoin_change_address").ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain bitcoin_change_address".to_string()) + })?) + .map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?; + + let bitcoin_change_address = bitcoin_address::validate( + bitcoin_change_address, + context.config.env_config.bitcoin_network, + )?; + + let monero_receive_address = + monero::Address::from_str(params.get("monero_receive_address").ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain monero_receiveaddress".to_string()) + })?) + .map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?; + + let monero_receive_address = monero_address::validate( + monero_receive_address, + context.config.env_config.monero_network, + )?; + + let seller = + Multiaddr::from_str(params.get("seller").ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain seller".to_string()) + })?) + .map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))?; + + execute_request( + params_raw, + Method::BuyXmr { + bitcoin_change_address, + monero_receive_address, + seller, + swap_id: Uuid::new_v4(), + }, + &context, + ) + .await + })?; + + module.register_async_method("list_sellers", |params_raw, context| async move { + let params: HashMap = params_raw.parse()?; + + let rendezvous_point = params.get("rendezvous_point").ok_or_else(|| { + jsonrpsee_core::Error::Custom("Does not contain rendezvous_point".to_string()) + })?; + + let rendezvous_point = rendezvous_point + .as_str() + .and_then(|addr_str| Multiaddr::from_str(addr_str).ok()) + .ok_or_else(|| { + jsonrpsee_core::Error::Custom("Could not parse valid multiaddr".to_string()) + })?; + + execute_request( + params_raw, + Method::ListSellers { + rendezvous_point: rendezvous_point.clone(), + }, + &context, + ) + .await + })?; + + module.register_async_method("get_current_swap", |params, context| async move { + execute_request(params, Method::GetCurrentSwap, &context).await + })?; + + Ok(module) +} + +fn as_uuid(json_value: &serde_json::Value) -> Option { + if let Some(uuid_str) = json_value.as_str() { + Uuid::parse_str(uuid_str).ok() + } else { + None + } +} + +async fn execute_request( + params: Params<'static>, + cmd: Method, + context: &Arc, +) -> Result { + // If we fail to parse the params as a String HashMap, it's most likely because its an empty object + // In that case, we want to make sure not to fail the request, so we set the log_reference_id to None + // and swallow the error + let reference_id = params + .parse::>() + .ok() + .and_then(|params_parsed| params_parsed.get("log_reference_id").cloned()); + + let request = Request::with_id(cmd, reference_id.map(|log_ref| log_ref.to_string())); + request + .call(Arc::clone(context)) + .await + .map_err(|err| jsonrpsee_core::Error::Custom(format!("{:#}", err))) +} diff --git a/swap/src/seed.rs b/swap/src/seed.rs index 5ffe9124..aa363905 100644 --- a/swap/src/seed.rs +++ b/swap/src/seed.rs @@ -16,7 +16,7 @@ use torut::onion::TorSecretKeyV3; pub const SEED_LENGTH: usize = 32; -#[derive(Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub struct Seed([u8; SEED_LENGTH]); impl Seed { diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index bd039477..c099376d 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -23,9 +23,9 @@ use swap::network::rendezvous::XmrBtcNamespace; use swap::network::swarm; use swap::protocol::alice::{AliceState, Swap}; use swap::protocol::bob::BobState; -use swap::protocol::{alice, bob}; +use swap::protocol::{alice, bob, Database}; use swap::seed::Seed; -use swap::{asb, bitcoin, cli, env, monero}; +use swap::{api, asb, bitcoin, cli, env, monero}; use tempfile::{tempdir, NamedTempFile}; use testcontainers::clients::Cli; use testcontainers::{Container, RunnableImage}; @@ -400,7 +400,7 @@ impl StartingBalances { } } -struct BobParams { +pub struct BobParams { seed: Seed, db_path: PathBuf, bitcoin_wallet: Arc, @@ -411,6 +411,21 @@ struct BobParams { } impl BobParams { + pub fn get_concentenated_alice_address(&self) -> String { + format!( + "{}/p2p/{}", + self.alice_address.clone(), + self.alice_peer_id.clone().to_base58() + ) + } + + pub async fn get_change_receive_addresses(&self) -> (bitcoin::Address, monero::Address) { + ( + self.bitcoin_wallet.new_address().await.unwrap(), + self.monero_wallet.get_main_address(), + ) + } + pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, cli::EventLoop)> { let (event_loop, handle) = self.new_eventloop(swap_id).await?; @@ -452,6 +467,8 @@ impl BobParams { } let db = Arc::new(SqliteDatabase::open(&self.db_path).await?); + db.insert_peer_id(swap_id, self.alice_peer_id).await?; + let swap = bob::Swap::new( db, swap_id, @@ -525,13 +542,24 @@ pub struct TestContext { alice_swap_handle: mpsc::Receiver, alice_handle: AliceApplicationHandle, - bob_params: BobParams, + pub bob_params: BobParams, bob_starting_balances: StartingBalances, bob_bitcoin_wallet: Arc, bob_monero_wallet: Arc, } impl TestContext { + pub async fn get_bob_context(self) -> api::Context { + api::Context::for_harness( + self.bob_params.seed, + self.env_config, + self.bob_params.db_path, + self.bob_bitcoin_wallet, + self.bob_monero_wallet, + ) + .await + } + pub async fn restart_alice(&mut self) { self.alice_handle.abort(); diff --git a/swap/tests/rpc.rs b/swap/tests/rpc.rs new file mode 100644 index 00000000..5dc640d4 --- /dev/null +++ b/swap/tests/rpc.rs @@ -0,0 +1,436 @@ +pub mod harness; +#[cfg(test)] +mod test { + + use anyhow::Result; + + use jsonrpsee::ws_client::WsClientBuilder; + use jsonrpsee_core::client::{Client, ClientT}; + use jsonrpsee_core::params::ObjectParams; + + use serial_test::serial; + + use serde_json::Value; + use std::collections::HashMap; + use std::net::SocketAddr; + use std::sync::Arc; + use std::time::Duration; + use swap::api::request::{Method, Request}; + use swap::api::Context; + + use crate::harness::alice_run_until::is_xmr_lock_transaction_sent; + use crate::harness::bob_run_until::is_btc_locked; + use crate::harness::{setup_test, SlowCancelConfig, TestContext}; + use swap::asb::FixedRate; + use swap::protocol::{alice, bob}; + use swap::tracing_ext::{capture_logs, MakeCapturingWriter}; + use tracing_subscriber::filter::LevelFilter; + use uuid::Uuid; + + const SERVER_ADDRESS: &str = "127.0.0.1:1234"; + const SERVER_START_TIMEOUT_SECS: u64 = 50; + const BITCOIN_ADDR: &str = "bcrt1qahvhjfc7vx5857zf8knxs8yp5lkm26jgyt0k76"; + const MONERO_ADDR: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"; + const SELLER: &str = + "/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; + const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b"; + + pub async fn setup_daemon( + harness_ctx: TestContext, + ) -> (Client, MakeCapturingWriter, Arc) { + let writer = capture_logs(LevelFilter::DEBUG); + let server_address: SocketAddr = SERVER_ADDRESS.parse().unwrap(); + + let request = Request::new(Method::StartDaemon { + server_address: Some(server_address), + }); + + let context = Arc::new(harness_ctx.get_bob_context().await); + + let context_clone = context.clone(); + + tokio::spawn(async move { + if let Err(err) = request.call(context_clone).await { + println!("Failed to initialize daemon for testing: {}", err); + } + }); + + for _ in 0..SERVER_START_TIMEOUT_SECS { + if writer.captured().contains("Started RPC server") { + let url = format!("ws://{}", SERVER_ADDRESS); + let client = WsClientBuilder::default().build(&url).await.unwrap(); + + return (client, writer, context); + } + + tokio::time::sleep(Duration::from_secs(1)).await; + } + + panic!( + "Failed to start RPC server after {} seconds", + SERVER_START_TIMEOUT_SECS + ); + } + + fn assert_has_keys_serde(map: &serde_json::Map, keys: &[&str]) { + for &key in keys { + assert!(map.contains_key(key), "Key {} is missing", key); + } + } + + // Helper function for HashMap + fn assert_has_keys_hashmap(map: &HashMap, keys: &[&str]) { + for &key in keys { + assert!(map.contains_key(key), "Key {} is missing", key); + } + } + + #[tokio::test] + #[serial] + pub async fn get_swap_info() { + setup_test(SlowCancelConfig, |mut harness_ctx| async move { + // Start a swap and wait for xmr lock transaction to be published (XmrLockTransactionSent) + let (bob_swap, _) = harness_ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); + let alice_swap = harness_ctx.alice_next_swap().await; + alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + ) + .await?; + + let (client, _, _) = setup_daemon(harness_ctx).await; + + let response: HashMap> = client + .request("get_history", ObjectParams::new()) + .await + .unwrap(); + let swaps: Vec<(Uuid, String)> = vec![(bob_swap_id, "btc is locked".to_string())]; + + assert_eq!(response, HashMap::from([("swaps".to_string(), swaps)])); + + let response: HashMap>> = client + .request("get_raw_states", ObjectParams::new()) + .await + .unwrap(); + + let response_raw_states = response.get("raw_states").unwrap(); + + assert!(response_raw_states.contains_key(&bob_swap_id)); + assert_eq!(response_raw_states.get(&bob_swap_id).unwrap().len(), 2); + + let mut params = ObjectParams::new(); + params.insert("swap_id", bob_swap_id).unwrap(); + let response: HashMap = + client.request("get_swap_info", params).await.unwrap(); + + // Check primary keys in response + assert_has_keys_hashmap( + &response, + &[ + "txRefundFee", + "swapId", + "cancelTimelock", + "timelock", + "punishTimelock", + "stateName", + "btcAmount", + "startDate", + "btcRefundAddress", + "txCancelFee", + "xmrAmount", + "completed", + "txLockId", + "seller", + ], + ); + + // Assert specific fields + assert_eq!(response.get("swapId").unwrap(), &bob_swap_id.to_string()); + assert_eq!( + response.get("stateName").unwrap(), + &"btc is locked".to_string() + ); + assert_eq!(response.get("completed").unwrap(), &Value::Bool(false)); + + // Check seller object and its keys + let seller = response + .get("seller") + .expect("Field 'seller' is missing from response") + .as_object() + .expect("'seller' is not an object"); + assert_has_keys_serde(seller, &["peerId"]); + + // Check timelock object, nested 'None' object, and blocks_left + let timelock = response + .get("timelock") + .expect("Field 'timelock' is missing from response") + .as_object() + .expect("'timelock' is not an object"); + let none_obj = timelock + .get("None") + .expect("Field 'None' is missing from 'timelock'") + .as_object() + .expect("'None' is not an object in 'timelock'"); + let blocks_left = none_obj + .get("blocks_left") + .expect("Field 'blocks_left' is missing from 'None'") + .as_i64() + .expect("'blocks_left' is not an integer"); + + // Validate blocks_left + assert!( + blocks_left > 0 && blocks_left <= 180, + "Field 'blocks_left' should be > 0 and <= 180 but got {}", + blocks_left + ); + + Ok(()) + }) + .await; + } + + #[tokio::test] + #[serial] + pub async fn test_rpc_calls() { + setup_test(SlowCancelConfig, |harness_ctx| async move { + let alice_addr = harness_ctx.bob_params.get_concentenated_alice_address(); + let (change_address, receive_address) = + harness_ctx.bob_params.get_change_receive_addresses().await; + + let (client, writer, _) = setup_daemon(harness_ctx).await; + assert!(client.is_connected()); + + let mut params = ObjectParams::new(); + + params.insert("force_refresh", false).unwrap(); + let response: HashMap = client + .request("get_bitcoin_balance", params) + .await + .unwrap(); + + assert_eq!(response, HashMap::from([("balance".to_string(), 10000000)])); + + + let mut params = ObjectParams::new(); + params.insert("log_reference_id", "test_ref_id").unwrap(); + params.insert("force_refresh", false).unwrap(); + + let _: HashMap = client.request("get_bitcoin_balance", params).await.unwrap(); + + assert!(writer.captured().contains( + r#"method{method_name="Balance" log_reference_id="\"test_ref_id\""}: swap::api::request: Current Bitcoin balance as of last sync balance=0.1 BTC"# + )); + + for method in ["get_swap_info", "resume_swap", "cancel_refund_swap"].iter() { + let mut params = ObjectParams::new(); + params.insert("swap_id", "invalid_swap").unwrap(); + + let response: Result, _> = + client.request(method, params).await; + response.expect_err(&format!( + "Expected an error when swap_id is invalid for method {}", + method + )); + + let params = ObjectParams::new(); + + let response: Result, _> = + client.request(method, params).await; + response.expect_err(&format!( + "Expected an error when swap_id is missing for method {}", + method + )); + } + + let params = ObjectParams::new(); + let result: Result, _> = + client.request("list_sellers", params).await; + + result.expect_err("Expected an error when rendezvous_point is missing"); + + let params = ObjectParams::new(); + let result: Result, _> = + client.request("list_sellers", params).await; + + result.expect_err("Expected an error when rendezvous_point is missing"); + + let params = ObjectParams::new(); + let response: Result, _> = + client.request("withdraw_btc", params).await; + response.expect_err("Expected an error when withdraw_address is missing"); + + let mut params = ObjectParams::new(); + params.insert("address", "invalid_address").unwrap(); + let response: Result, _> = + client.request("withdraw_btc", params).await; + response.expect_err("Expected an error when withdraw_address is malformed"); + + let mut params = ObjectParams::new(); + params.insert("address", BITCOIN_ADDR).unwrap(); + params.insert("amount", "0").unwrap(); + let response: Result, _> = + client.request("withdraw_btc", params).await; + response.expect_err("Expected an error when amount is 0"); + + let mut params = ObjectParams::new(); + params + .insert("address", BITCOIN_ADDR) + .unwrap(); + params.insert("amount", "0.01").unwrap(); + let response: HashMap = client + .request("withdraw_btc", params) + .await + .expect("Expected a valid response"); + + assert_has_keys_hashmap(&response, &["signed_tx", "amount", "txid"]); + assert_eq!( + response.get("amount").unwrap().as_u64().unwrap(), + 1_000_000 + ); + + let params = ObjectParams::new(); + let response: Result, _> = + client.request("buy_xmr", params).await; + response.expect_err("Expected an error when no params are given"); + + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + let response: Result, _> = + client.request("buy_xmr", params).await; + response.expect_err("Expected an error when seller is missing"); + + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = + client.request("buy_xmr", params).await; + response.expect_err("Expected an error when monero_receive_address is missing"); + + let mut params = ObjectParams::new(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = + client.request("buy_xmr", params).await; + response.expect_err("Expected an error when bitcoin_change_address is missing"); + + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", "invalid_address") + .unwrap(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = + client.request("buy_xmr", params).await; + response.expect_err("Expected an error when bitcoin_change_address is malformed"); + + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params + .insert("monero_receive_address", "invalid_address") + .unwrap(); + params.insert("seller", SELLER).unwrap(); + let response: Result, _> = + client.request("buy_xmr", params).await; + response.expect_err("Expected an error when monero_receive_address is malformed"); + + + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", BITCOIN_ADDR) + .unwrap(); + params + .insert("monero_receive_address", MONERO_ADDR) + .unwrap(); + params.insert("seller", "invalid_seller").unwrap(); + let response: Result, _> = + client.request("buy_xmr", params).await; + response.expect_err("Expected an error when seller is malformed"); + + let response: Result, _> = client + .request("suspend_current_swap", ObjectParams::new()) + .await; + response.expect_err("Expected an error when no swap is running"); + + let mut params = ObjectParams::new(); + params + .insert("bitcoin_change_address", change_address) + .unwrap(); + params + .insert("monero_receive_address", receive_address) + .unwrap(); + + params.insert("seller", alice_addr).unwrap(); + let response: HashMap = client + .request("buy_xmr", params) + .await + .expect("Expected a HashMap, got an error"); + + assert_has_keys_hashmap(&response, &["swapId"]); + + Ok(()) + }) + .await; + } + + #[tokio::test] + #[serial] + pub async fn suspend_current_swap_swap_running() { + setup_test(SlowCancelConfig, |harness_ctx| async move { + let (client, _, ctx) = setup_daemon(harness_ctx).await; + + ctx.swap_lock + .acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap()) + .await + .unwrap(); + let cloned_ctx = ctx.clone(); + + tokio::spawn(async move { + // Immediately release lock when suspend signal is received. Mocks a running swap that is then cancelled. + ctx.swap_lock + .listen_for_swap_force_suspension() + .await + .unwrap(); + ctx.swap_lock.release_swap_lock().await.unwrap(); + }); + + let response: HashMap = client + .request("suspend_current_swap", ObjectParams::new()) + .await + .unwrap(); + assert_eq!( + response, + HashMap::from([("swapId".to_string(), SWAP_ID.to_string())]) + ); + + cloned_ctx + .swap_lock + .acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap()) + .await + .unwrap(); + + let response: Result, _> = client + .request("suspend_current_swap", ObjectParams::new()) + .await; + response.expect_err("Expected an error when suspend signal times out"); + + Ok(()) + }) + .await; + } +} From 97210739a177d27c12f1056ec8d2c89b77abf4f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 11:10:26 +0000 Subject: [PATCH 35/41] build(deps): bump Swatinem/rust-cache from 2.7.1 to 2.7.3 Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.1 to 2.7.3. - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/swatinem/rust-cache/compare/v2.7.1...v2.7.3) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da82b266..5711b8f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -181,7 +181,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v4.1.1 - - uses: Swatinem/rust-cache@v2.7.1 + - uses: Swatinem/rust-cache@v2.7.3 - name: Run RPC server tests run: cargo test --package swap --all-features --test rpc -- --nocapture From b443a964695bcc0cd707b6fdedbca6f3f1a5c476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 11:10:29 +0000 Subject: [PATCH 36/41] build(deps): bump actions/checkout from 4.1.1 to 4.1.6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.6) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da82b266..23072bf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,7 +179,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.6 - uses: Swatinem/rust-cache@v2.7.1 From 0c4b7d50c267ff0c94157a7ad225e19e5b5e724f Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 24 May 2024 18:02:29 +0200 Subject: [PATCH 37/41] Lower DEFAULT_BITCOIN_CONFIRMATION_TARGET to 1 to ensure timely confirmation of bitcoin transactions (#1640) --- swap/src/cli/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index affd6a19..af72df1b 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -23,7 +23,7 @@ const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700"; // See: https://1209k.com/bitcoin-eye/ele.php?chain=tbtc pub const DEFAULT_ELECTRUM_RPC_URL_TESTNET: &str = "ssl://electrum.blockstream.info:60002"; -const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 3; +const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 1; pub const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: usize = 1; const DEFAULT_TOR_SOCKS5_PORT: &str = "9050"; From 2f28ef940183d8272244c2a81ec4295873569a49 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Fri, 24 May 2024 18:06:21 +0200 Subject: [PATCH 38/41] Add missing changelog entries (#1641) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3ad105..2eaf409c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Minimum Supported Rust Version (MSRV) bumped to 1.70 +- Lowered default Bitcoin confirmation target for Bob to 1 to make sure Bitcoin transactions get confirmed in time +- Added support for starting the CLI (using the `start-daemon` subcommand) as a Daemon that accepts JSON-RPC requests ## [0.12.3] - 2023-09-20 From 0ca98cd0b78aff95a38021e9574fe3c28c968963 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Sat, 25 May 2024 14:43:22 +0200 Subject: [PATCH 39/41] Make tracing file appender blocking (#1643) --- swap/src/cli/tracing.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/swap/src/cli/tracing.rs b/swap/src/cli/tracing.rs index 7cb0721b..6b6de60f 100644 --- a/swap/src/cli/tracing.rs +++ b/swap/src/cli/tracing.rs @@ -13,7 +13,6 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef) -> Result<()> { let registry = Registry::default().with(level_filter); let appender = tracing_appender::rolling::never(dir.as_ref(), "swap-all.log"); - let (appender, _guard) = tracing_appender::non_blocking(appender); let file_logger = registry.with( fmt::layer() From c433bd2389300f05f167462a8fe45a66e1040915 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Sat, 25 May 2024 17:59:12 +0200 Subject: [PATCH 40/41] CLI: Initiate tracing earlier to avoid lost logs (#1646) --- swap/src/api.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/swap/src/api.rs b/swap/src/api.rs index ca3ae21d..6940b8b1 100644 --- a/swap/src/api.rs +++ b/swap/src/api.rs @@ -184,6 +184,10 @@ impl Context { let data_dir = data::data_dir_from(data, is_testnet)?; let env_config = env_config_from(is_testnet); + START.call_once(|| { + let _ = cli::tracing::init(debug, json, data_dir.join("logs")); + }); + let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read seed in file")?; @@ -219,10 +223,6 @@ impl Context { let tor_socks5_port = tor.map_or(9050, |tor| tor.tor_socks5_port); - START.call_once(|| { - let _ = cli::tracing::init(debug, json, data_dir.join("logs")); - }); - let context = Context { db: open_db(data_dir.join("sqlite")).await?, bitcoin_wallet, From 32ca0b1a4ac868505f1a6522c16fd3690c73c649 Mon Sep 17 00:00:00 2001 From: binarybaron <86064887+binarybaron@users.noreply.github.com> Date: Sat, 25 May 2024 18:08:17 +0200 Subject: [PATCH 41/41] CLI: Upgrade monero-wallet-rpc to 0.18.3.1, clarify a few log. messages --- CHANGELOG.md | 1 + swap/src/api.rs | 2 +- swap/src/monero/wallet_rpc.rs | 43 +++++++++++++++++++++++------------ swap/src/protocol/bob/swap.rs | 3 +++ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eaf409c..0a299af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Minimum Supported Rust Version (MSRV) bumped to 1.70 - Lowered default Bitcoin confirmation target for Bob to 1 to make sure Bitcoin transactions get confirmed in time - Added support for starting the CLI (using the `start-daemon` subcommand) as a Daemon that accepts JSON-RPC requests +- Update monero-wallet-rpc version to v0.18.3.1 ## [0.12.3] - 2023-09-20 diff --git a/swap/src/api.rs b/swap/src/api.rs index 6940b8b1..340c2e39 100644 --- a/swap/src/api.rs +++ b/swap/src/api.rs @@ -187,7 +187,7 @@ impl Context { START.call_once(|| { let _ = cli::tracing::init(debug, json, data_dir.join("logs")); }); - + let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read seed in file")?; diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs index e227c3f3..8af1167e 100644 --- a/swap/src/monero/wallet_rpc.rs +++ b/swap/src/monero/wallet_rpc.rs @@ -46,30 +46,30 @@ const MONERO_DAEMONS: [MoneroDaemon; 17] = [ compile_error!("unsupported operating system"); #[cfg(all(target_os = "macos", target_arch = "x86_64"))] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-x64-v0.18.1.2.tar.bz2"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-x64-v0.18.3.1.tar.bz2"; #[cfg(all(target_os = "macos", target_arch = "x86_64"))] -const DOWNLOAD_HASH: &str = "ba1108c7a5e5efe15b6a628fb007c50f01c231f61137bba7427605286dbc6f01"; +const DOWNLOAD_HASH: &str = "7f8bd9364ef16482b418aa802a65be0e4cc660c794bb5d77b2d17bc84427883a"; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-armv8-v0.18.1.2.tar.bz2"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-mac-armv8-v0.18.3.1.tar.bz2"; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] -const DOWNLOAD_HASH: &str = "620b825c04f84845ed09de03b207a3230a34f74b30a8a07dde504a7d376ee4b9"; +const DOWNLOAD_HASH: &str = "915288b023cb5811e626e10052adc6ac5323dd283c5a25b91059b0fb86a21fb6"; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.1.2.tar.bz2"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.1.tar.bz2"; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -const DOWNLOAD_HASH: &str = "7d51e7072351f65d0c7909e745827cfd3b00abe5e7c4cc4c104a3c9b526da07e"; +const DOWNLOAD_HASH: &str = "23af572fdfe3459b9ab97e2e9aa7e3c11021c955d6064b801a27d7e8c21ae09d"; #[cfg(all(target_os = "linux", target_arch = "arm"))] const DOWNLOAD_URL: &str = - "https://downloads.getmonero.org/cli/monero-linux-armv7-v0.18.1.2.tar.bz2"; + "https://downloads.getmonero.org/cli/monero-linux-armv7-v0.18.3.1.tar.bz2"; #[cfg(all(target_os = "linux", target_arch = "arm"))] -const DOWNLOAD_HASH: &str = "94ece435ed60f85904114643482c2b6716f74bf97040a7af237450574a9cf06d"; +const DOWNLOAD_HASH: &str = "2ea2c8898cbab88f49423f4f6c15f2a94046cb4bbe827493dd061edc0fd5f1ca"; #[cfg(target_os = "windows")] -const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-win-x64-v0.18.1.2.zip"; +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-win-x64-v0.18.3.1.zip"; #[cfg(target_os = "windows")] -const DOWNLOAD_HASH: &str = "0a3d4d1af7e094c05352c31b2dafcc6ccbc80edc195ca9eaedc919c36accd05a"; +const DOWNLOAD_HASH: &str = "35dcc4bee4caad3442659d37837e0119e4649a77f2e3b5e80dd6d9b8fc4fb6ad"; #[cfg(any(target_os = "macos", target_os = "linux"))] const PACKED_FILE: &str = "monero-wallet-rpc"; @@ -77,7 +77,7 @@ const PACKED_FILE: &str = "monero-wallet-rpc"; #[cfg(target_os = "windows")] const PACKED_FILE: &str = "monero-wallet-rpc.exe"; -const WALLET_RPC_VERSION: &str = "v0.18.1.2"; +const WALLET_RPC_VERSION: &str = "v0.18.3.1"; #[derive(Debug, Clone, Copy, thiserror::Error)] #[error("monero wallet rpc executable not found in downloaded archive")] @@ -233,9 +233,10 @@ impl WalletRpc { .parse::()?; tracing::info!( - "Downloading monero-wallet-rpc ({}) from {}", - content_length.big_byte(2), - DOWNLOAD_URL + progress="0%", + size=%content_length.big_byte(2), + download_url=DOWNLOAD_URL, + "Downloading monero-wallet-rpc", ); let mut hasher = Sha256::new(); @@ -268,12 +269,24 @@ impl WalletRpc { let total = 3 * content_length; let percent = 100 * received as u64 / total; if percent != notified && percent % 10 == 0 { - tracing::debug!("{}%", percent); + tracing::info!( + progress=format!("{}%", percent), + size=%content_length.big_byte(2), + download_url=DOWNLOAD_URL, + "Downloading monero-wallet-rpc", + ); notified = percent; } file.write_all(&bytes).await?; } + tracing::info!( + progress="100%", + size=%content_length.big_byte(2), + download_url=DOWNLOAD_URL, + "Downloading monero-wallet-rpc", + ); + let result = hasher.finalize(); let result_hash = HEXLOWER.encode(result.as_ref()); if result_hash != DOWNLOAD_HASH { diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 6f5de482..37a6b65a 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -227,6 +227,9 @@ async fn next_state( let (spend_key, view_key) = state.xmr_keys(); let wallet_file_name = swap_id.to_string(); + + tracing::info!(%wallet_file_name, "Generating and opening Monero wallet from the extracted keys to redeem the Monero"); + if let Err(e) = monero_wallet .create_from_and_load( wallet_file_name.clone(),