mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-04-10 18:39:11 -04:00
Compare commits
148 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
81b89d63c2 | ||
![]() |
a2cc3591b1 | ||
![]() |
44fafc792e | ||
![]() |
050d4f5f48 | ||
![]() |
0fb411c8a6 | ||
![]() |
d69cf30efe | ||
![]() |
fac5df93fa | ||
![]() |
02a3711cc9 | ||
![]() |
4afd8a44e4 | ||
![]() |
8e6327a2e6 | ||
![]() |
cb42f7ae27 | ||
![]() |
d5d2663442 | ||
![]() |
7b1b5ec3a5 | ||
![]() |
de7c8aec0a | ||
![]() |
d521815e91 | ||
![]() |
ec567ccba1 | ||
![]() |
61c7afb358 | ||
![]() |
d841741d3c | ||
![]() |
f2ee1004e1 | ||
![]() |
f151653c65 | ||
![]() |
9e68fd0d9f | ||
![]() |
5d88d33243 | ||
![]() |
23980df163 | ||
![]() |
a837d1c22b | ||
![]() |
3d4bc971a2 | ||
![]() |
19710e296d | ||
![]() |
851086c5a2 | ||
![]() |
88e6112736 | ||
![]() |
a3ffd35fc0 | ||
![]() |
12c6974458 | ||
![]() |
6abd951b46 | ||
![]() |
bfb0053919 | ||
![]() |
c78ffa7351 | ||
![]() |
523adc6d26 | ||
![]() |
e01986bb50 | ||
![]() |
a979871610 | ||
![]() |
e3f31af88a | ||
![]() |
fe77b5af95 | ||
![]() |
f20d71f74b | ||
![]() |
0584256622 | ||
![]() |
dcb4d02988 | ||
![]() |
545ef8fb30 | ||
![]() |
433ed87263 | ||
![]() |
8022d8b423 | ||
![]() |
4114772bb7 | ||
![]() |
ff9f349889 | ||
![]() |
a8a99db738 | ||
![]() |
29ee6acb95 | ||
![]() |
aef729331f | ||
![]() |
2faf7561bd | ||
![]() |
7871cf256b | ||
![]() |
55bfd95694 | ||
![]() |
3cebddf593 | ||
![]() |
c3831e93cc | ||
![]() |
4eff6fe503 | ||
![]() |
018e803f88 | ||
![]() |
504d05bfad | ||
![]() |
33901a2ea9 | ||
![]() |
c8cbd27b79 | ||
![]() |
f755993fc6 | ||
![]() |
ae614b5567 | ||
![]() |
f799da5203 | ||
![]() |
952fb71a6a | ||
![]() |
180458d587 | ||
![]() |
6a76e9efbe | ||
![]() |
7634fe1702 | ||
![]() |
9aaa7d358f | ||
![]() |
204aa85e59 | ||
![]() |
7c509d3ea3 | ||
![]() |
cc854be8f4 | ||
![]() |
af6bc47ed3 | ||
![]() |
587212abc7 | ||
![]() |
b18ba95e8c | ||
![]() |
6e09c73cf3 | ||
![]() |
132cfe782c | ||
![]() |
3611a1aced | ||
![]() |
21f26a5b44 | ||
![]() |
9700d192b2 | ||
![]() |
254874276c | ||
![]() |
011aa0cb9c | ||
![]() |
8bf4ea08be | ||
![]() |
f3640aceb2 | ||
![]() |
45e14c5a1e | ||
![]() |
f29bf20e8d | ||
![]() |
b52e07ace9 | ||
![]() |
77a43ba28c | ||
![]() |
49cae19059 | ||
![]() |
7b5929deb5 | ||
![]() |
8284bea778 | ||
![]() |
0ad78e4f30 | ||
![]() |
fc6bb336c8 | ||
![]() |
33ad3c374a | ||
![]() |
2fe428779a | ||
![]() |
2eda2476eb | ||
![]() |
aaa52e9559 | ||
![]() |
ce8d3afe60 | ||
![]() |
75cfc6b0d4 | ||
![]() |
c80bdb2d8c | ||
![]() |
ef49b471d8 | ||
![]() |
cf87f19e82 | ||
![]() |
410fcf2117 | ||
![]() |
8ba8e3bd5c | ||
![]() |
eef78cddb2 | ||
![]() |
f24c11f0a3 | ||
![]() |
8e6d1e37e8 | ||
![]() |
f4630fa9d1 | ||
![]() |
cb85439905 | ||
![]() |
74af379f80 | ||
![]() |
b3813f3769 | ||
![]() |
7c9af191bc | ||
![]() |
97788e6f54 | ||
![]() |
85c4db1d75 | ||
![]() |
612784b3e0 | ||
![]() |
7c54fb2848 | ||
![]() |
f5bda640a0 | ||
![]() |
d8f84cbad9 | ||
![]() |
c430e89502 | ||
![]() |
eab4b00478 | ||
![]() |
d55c6e8c1a | ||
![]() |
60a3177710 | ||
![]() |
1f322b78c8 | ||
![]() |
12b9ceebcf | ||
![]() |
77f7222f71 | ||
![]() |
31d76cbdf4 | ||
![]() |
845b9428b7 | ||
![]() |
ee04ff8a3b | ||
![]() |
f8c3276642 | ||
![]() |
073baa9752 | ||
![]() |
f72005312c | ||
![]() |
4115a452e3 | ||
![]() |
c385059138 | ||
![]() |
c283d6a911 | ||
![]() |
dcb4edf585 | ||
![]() |
47f8a65209 | ||
![]() |
bf9abd4101 | ||
![]() |
2af22c1aba | ||
![]() |
e2198d49a0 | ||
![]() |
f0abb4396d | ||
![]() |
537672df08 | ||
![]() |
cd29870e11 | ||
![]() |
ab1001a18c | ||
![]() |
294b658f43 | ||
![]() |
2824ebc893 | ||
![]() |
07f788eb81 | ||
![]() |
ef75019ac6 | ||
![]() |
26e66ce9b9 | ||
![]() |
d7b649b7a6 | ||
![]() |
c5aa7edb6b |
@ -1,2 +1,6 @@
|
||||
[target.armv7-unknown-linux-gnueabihf]
|
||||
linker = "arm-linux-gnueabihf-gcc"
|
||||
|
||||
# windows defaults to smaller stack sizes which isn't enough
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["-C", "link-args=/STACK:8388608"]
|
||||
|
4
.github/workflows/build-release-binaries.yml
vendored
4
.github/workflows/build-release-binaries.yml
vendored
@ -54,12 +54,12 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout tagged commit
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
with:
|
||||
ref: ${{ github.event.release.target_commitish }}
|
||||
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@ -11,14 +11,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.74"
|
||||
components: clippy,rustfmt
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- name: Check formatting
|
||||
uses: dprint/check@v2.2
|
||||
@ -35,9 +35,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- name: Build swap
|
||||
run: cargo build --bin swap
|
||||
@ -49,9 +49,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- name: Install sqlx-cli
|
||||
run: cargo install --locked --version 0.6.3 sqlx-cli
|
||||
@ -78,9 +78,9 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@ -131,9 +131,9 @@ jobs:
|
||||
tool-cache: false
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- name: Build tests
|
||||
run: cargo build --tests --workspace --all-features
|
||||
@ -171,9 +171,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- name: Run test ${{ matrix.test_name }}
|
||||
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture
|
||||
@ -182,9 +182,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- name: Run RPC server tests
|
||||
run: cargo test --package swap --all-features --test rpc -- --nocapture
|
||||
@ -193,11 +193,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2.7.3
|
||||
- uses: Swatinem/rust-cache@v2.7.5
|
||||
|
||||
- name: Run cargo check on stable rust
|
||||
run: cargo check --all-targets
|
||||
|
2
.github/workflows/create-release.yml
vendored
2
.github/workflows/create-release.yml
vendored
@ -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.7
|
||||
- uses: actions/checkout@v4.2.1
|
||||
|
||||
- name: Extract version from branch name
|
||||
id: extract-version
|
||||
|
4
.github/workflows/draft-new-release.yml
vendored
4
.github/workflows/draft-new-release.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
name: "Draft a new release"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: actions/checkout@v4.2.1
|
||||
with:
|
||||
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
||||
|
||||
@ -20,7 +20,7 @@ jobs:
|
||||
run: git checkout -b release/${{ github.event.inputs.version }}
|
||||
|
||||
- name: Update changelog
|
||||
uses: thomaseizinger/keep-a-changelog-new-release@3.0.0
|
||||
uses: thomaseizinger/keep-a-changelog-new-release@3.1.0
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
changelogPath: CHANGELOG.md
|
||||
|
2
.github/workflows/preview-release.yml
vendored
2
.github/workflows/preview-release.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
name: Create preview release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- uses: actions/checkout@v4.2.1
|
||||
|
||||
- name: Delete 'preview' release
|
||||
uses: larryjoelane/delete-release-action@v1.0.24
|
||||
|
17
CHANGELOG.md
17
CHANGELOG.md
@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- ASB + CLI: You can now use the `logs` command to retrieve logs stored in the past, redacting addresses and id's using `logs --redact`.
|
||||
- ASB: The `--disable-timestamp` flag has been removed
|
||||
|
||||
## [0.13.4] - 2024-07-25
|
||||
|
||||
- ASB: The `history` command can now be used while the asb is running.
|
||||
- ASB: Retry locking of Monero if it fails on first attempt
|
||||
|
||||
## [0.13.3] - 2024-07-15
|
||||
|
||||
- Introduced a cooperative Monero redeem feature for Bob to request from Alice if Bob is punished for not refunding in time. Alice can choose to cooperate but is not obligated to do so. This change is backwards compatible. To attempt recovery, resume a swap in the "Bitcoin punished" state. Success depends on Alice being active and still having a record of the swap. Note that Alice's cooperation is voluntary and recovery is not guaranteed
|
||||
- CLI: `--change-address` can now be omitted. In that case, any change is refunded to the internal bitcoin wallet.
|
||||
|
||||
## [0.13.2] - 2024-07-02
|
||||
|
||||
- CLI: Buffer received transfer proofs for later processing if we're currently running a different swap
|
||||
@ -365,7 +378,9 @@ It is possible to migrate critical data from the old db to the sqlite but there
|
||||
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
|
||||
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
|
||||
|
||||
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.2...HEAD
|
||||
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.4...HEAD
|
||||
[0.13.4]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.3...0.13.4
|
||||
[0.13.3]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.2...0.13.3
|
||||
[0.13.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.1...0.13.2
|
||||
[0.13.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.13.0...0.13.1
|
||||
[0.13.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.3...0.13.0
|
||||
|
366
Cargo.lock
generated
366
Cargo.lock
generated
@ -78,6 +78,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
@ -98,9 +107,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
@ -154,13 +163,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.80"
|
||||
version = "0.1.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
|
||||
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -743,7 +752,7 @@ dependencies = [
|
||||
"nom",
|
||||
"pathdiff",
|
||||
"serde",
|
||||
"toml 0.8.14",
|
||||
"toml 0.8.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1215,9 +1224,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.1"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
@ -1301,9 +1310,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -1316,9 +1325,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@ -1326,15 +1335,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@ -1355,19 +1364,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1383,15 +1392,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
@ -1401,9 +1410,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -1483,9 +1492,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd"
|
||||
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"libc",
|
||||
@ -1500,7 +1509,7 @@ version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"aho-corasick 0.7.18",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
@ -1763,9 +1772,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.3.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
|
||||
checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@ -1774,6 +1783,7 @@ dependencies = [
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite 0.2.13",
|
||||
"smallvec",
|
||||
@ -1789,7 +1799,7 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"hyper 1.3.1",
|
||||
"hyper 1.5.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.10",
|
||||
"rustls-pki-types",
|
||||
@ -1810,7 +1820,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"hyper 1.3.1",
|
||||
"hyper 1.5.0",
|
||||
"pin-project-lite 0.2.13",
|
||||
"socket2 0.5.5",
|
||||
"tokio",
|
||||
@ -1905,7 +1915,7 @@ dependencies = [
|
||||
"socket2 0.3.19",
|
||||
"widestring",
|
||||
"winapi",
|
||||
"winreg 0.6.2",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2122,15 +2132,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.159"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.16.1+1.7.1"
|
||||
version = "0.17.0+1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c"
|
||||
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@ -2458,9 +2468,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@ -2507,7 +2517,7 @@ version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
"regex-automata 0.1.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2516,7 +2526,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
"regex-automata 0.1.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2593,14 +2603,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mockito"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6e023aa5bdf392aa06c78e4a4e6d498baab5138d0c993503350ebbc37bf1e"
|
||||
checksum = "09b34bd91b9e5c5b06338d392463e1318d683cf82ec3d3af4014609be6e2108d"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"bytes",
|
||||
"colored",
|
||||
"futures-core",
|
||||
"hyper 0.14.28",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper 1.5.0",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"rand 0.8.3",
|
||||
"regex",
|
||||
@ -2647,7 +2662,7 @@ dependencies = [
|
||||
"testcontainers",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"tracing-subscriber 0.3.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2833,9 +2848,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@ -3141,9 +3156,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.74"
|
||||
version = "1.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db"
|
||||
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -3162,7 +3177,7 @@ dependencies = [
|
||||
"rand 0.8.3",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_xorshift",
|
||||
"regex-syntax 0.8.2",
|
||||
"regex-syntax 0.8.5",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
"unarray",
|
||||
@ -3243,9 +3258,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "qrcode"
|
||||
version = "0.14.0"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e719ca51966ff9f5a8436edb00d6115b3c606a0bb27c8f8ca74a38ff2b036d"
|
||||
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
|
||||
dependencies = [
|
||||
"image",
|
||||
]
|
||||
@ -3443,13 +3458,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"aho-corasick 1.1.3",
|
||||
"memchr",
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3462,6 +3478,17 @@ dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick 1.1.3",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
@ -3470,9 +3497,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rend"
|
||||
@ -3485,9 +3512,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.5"
|
||||
version = "0.12.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
|
||||
checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@ -3497,7 +3524,7 @@ dependencies = [
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper 1.3.1",
|
||||
"hyper 1.5.0",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
@ -3526,7 +3553,7 @@ dependencies = [
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.26.1",
|
||||
"winreg 0.52.0",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3650,9 +3677,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.31"
|
||||
version = "0.38.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
@ -3995,9 +4022,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.203"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -4024,31 +4051,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.203"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.118"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
||||
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@ -4109,7 +4137,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4498,7 +4526,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4515,7 +4543,7 @@ checksum = "8049cf85f0e715d6af38dde439cb0ccb91f67fb9f5f63c80f8b43e48356e1a3f"
|
||||
|
||||
[[package]]
|
||||
name = "swap"
|
||||
version = "0.13.2"
|
||||
version = "0.13.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
@ -4541,7 +4569,7 @@ dependencies = [
|
||||
"futures",
|
||||
"get-port",
|
||||
"hex",
|
||||
"hyper 1.3.1",
|
||||
"hyper 1.5.0",
|
||||
"itertools 0.13.0",
|
||||
"jsonrpsee",
|
||||
"jsonrpsee-core",
|
||||
@ -4550,12 +4578,14 @@ dependencies = [
|
||||
"monero",
|
||||
"monero-harness",
|
||||
"monero-rpc",
|
||||
"once_cell",
|
||||
"pem",
|
||||
"port_check",
|
||||
"proptest",
|
||||
"qrcode",
|
||||
"rand 0.8.3",
|
||||
"rand_chacha 0.3.1",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rust_decimal",
|
||||
"rust_decimal_macros",
|
||||
@ -4579,7 +4609,7 @@ dependencies = [
|
||||
"tokio-tar",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"toml 0.8.14",
|
||||
"toml 0.8.19",
|
||||
"torut",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@ -4605,9 +4635,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.46"
|
||||
version = "2.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -4619,6 +4649,9 @@ name = "sync_wrapper"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
@ -4634,14 +4667,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
version = "3.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"fastrand",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4682,22 +4716,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.61"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4778,9 +4812,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.38.0"
|
||||
version = "1.38.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
|
||||
checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@ -4803,7 +4837,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4841,9 +4875,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
|
||||
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
@ -4896,9 +4930,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.11"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
|
||||
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@ -4919,9 +4953,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.14"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@ -4931,18 +4965,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.6"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.14"
|
||||
version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap 2.1.0",
|
||||
"serde",
|
||||
@ -5029,7 +5063,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5327,9 +5361,9 @@ checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.9.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
dependencies = [
|
||||
"getrandom 0.2.11",
|
||||
"serde",
|
||||
@ -5355,9 +5389,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "8.3.1"
|
||||
version = "8.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525"
|
||||
checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if 1.0.0",
|
||||
@ -5436,7 +5470,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -5470,7 +5504,7 @@ checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -5589,6 +5623,36 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.32.0"
|
||||
@ -5617,7 +5681,16 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5637,17 +5710,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5658,9 +5732,9 @@ checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@ -5676,9 +5750,9 @@ checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@ -5694,9 +5768,15 @@ checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@ -5712,9 +5792,9 @@ checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@ -5730,9 +5810,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@ -5742,9 +5822,9 @@ checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@ -5760,15 +5840,15 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.5"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
|
||||
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -5782,16 +5862,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "1.1.0"
|
||||
@ -5845,7 +5915,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.46",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@ -0,0 +1,22 @@
|
||||
# This Dockerfile builds the asb binary
|
||||
|
||||
FROM rust:1.79-slim AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y git clang cmake libsnappy-dev
|
||||
|
||||
COPY . .
|
||||
|
||||
WORKDIR /build/swap
|
||||
|
||||
RUN cargo build --release --bin=asb
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
COPY --from=builder /build/target/release/asb /bin/asb
|
||||
|
||||
ENTRYPOINT ["asb"]
|
@ -9,6 +9,10 @@ Currently, swaps are only offered in one direction with the `swap` CLI on the bu
|
||||
We are working on implementing a protocol where XMR moves first, but are currently blocked by advances on Monero itself.
|
||||
You can read [this blogpost](https://comit.network/blog/2021/07/02/transaction-presigning) for more information.
|
||||
|
||||
## Maintenance
|
||||
|
||||
**This repository is unmaintained**. The original developers (@comit-network) have moved on to other projects. Community volunteers are continuing development at [UnstoppableSwap/core](https://github.com/UnstoppableSwap/core), which includes a graphical user interface. Please note that the fork has introduced network-level breaking changes, making it incompatible with peers running this repository - you will not be able to initiate swaps with them.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Download the [latest `swap` binary release](https://github.com/comit-network/xmr-btc-swap/releases/latest) for your operating system.
|
||||
|
@ -75,7 +75,7 @@ OPTIONS:
|
||||
|
||||
This command has three core options:
|
||||
|
||||
- `--change-address`: A Bitcoin address you control. Will be used for refunds of any kind.
|
||||
- `--change-address`: A Bitcoin address you control. Will be used for refunds of any kind. You can also omit this flag which will refund any change to the internal wallet.
|
||||
- `--receive-address`: A Monero address you control. This is where you will receive the Monero after the swap.
|
||||
- `--seller`: The multiaddress of the seller you want to swap with.
|
||||
|
||||
|
@ -13,4 +13,4 @@ rand = "0.7"
|
||||
testcontainers = "0.15"
|
||||
tokio = { version = "1", default-features = false, features = [ "rt-multi-thread", "time", "macros" ] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "tracing-log" ] }
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = [ "fmt", "ansi", "env-filter", "tracing-log" ] }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "swap"
|
||||
version = "0.13.2"
|
||||
version = "0.13.4"
|
||||
authors = [ "The COMIT guys <hello@comit.network>" ]
|
||||
edition = "2021"
|
||||
description = "XMR/BTC trustless atomic swaps."
|
||||
@ -37,11 +37,13 @@ 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" }
|
||||
once_cell = "1.20"
|
||||
pem = "3.0"
|
||||
proptest = "1"
|
||||
qrcode = "0.14"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
regex = "1.11"
|
||||
reqwest = { version = "0.12", features = [ "http2", "rustls-tls", "stream", "socks" ], default-features = false }
|
||||
rust_decimal = { version = "1", features = [ "serde-float" ] }
|
||||
rust_decimal_macros = "1"
|
||||
@ -67,7 +69,7 @@ tracing-appender = "0.2"
|
||||
tracing-futures = { version = "0.2", features = [ "std-future", "futures-03" ] }
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = [ "fmt", "ansi", "env-filter", "time", "tracing-log", "json" ] }
|
||||
url = { version = "2", features = [ "serde" ] }
|
||||
uuid = { version = "1.9", features = [ "serde", "v4" ] }
|
||||
uuid = { version = "1.11", features = [ "serde", "v4" ] }
|
||||
void = "1"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
@ -79,9 +81,9 @@ zip = "0.5"
|
||||
[dev-dependencies]
|
||||
bitcoin-harness = { git = "https://github.com/delta1/bitcoin-harness-rs.git", rev = "80cc8d05db2610d8531011be505b7bee2b5cdf9f" }
|
||||
get-port = "3"
|
||||
hyper = "1.3"
|
||||
hyper = "1.5"
|
||||
jsonrpsee = { version = "0.16.2", features = [ "ws-client" ] }
|
||||
mockito = "1.4"
|
||||
mockito = "1.5"
|
||||
monero-harness = { path = "../monero-harness" }
|
||||
port_check = "0.2"
|
||||
proptest = "1"
|
||||
|
135
swap/migrations/20240615140942_btcpunished_update.sql
Normal file
135
swap/migrations/20240615140942_btcpunished_update.sql
Normal file
@ -0,0 +1,135 @@
|
||||
-- This migration script modifies swap states to be compatible with the new state structure introduced in PR #1676.
|
||||
-- The following changes are made:
|
||||
-- 1. Bob: BtcPunished state now has a new attribute 'state' (type: State6), 'tx_lock_id' (type: string) remains the same
|
||||
-- 2. Bob: State6 has two new attributes: 'v' (monero viewkey) and 'monero_wallet_restore_blockheight' (type: BlockHeight)
|
||||
-- State6 is used in BtcPunished, CancelTimelockExpired, BtcCancelled, BtcRefunded states
|
||||
-- 3. Alice: BtcPunished state now has a new attribute 'state3' (type: State3)
|
||||
|
||||
-- Alice: Add new attribute 'state3' (type: State3) to the BtcPunished state by copying it from the BtcLocked state
|
||||
UPDATE swap_states SET
|
||||
state = json_replace( -- Replaces "{"Alice":{"Done":"BtcPunished"}}" with "{"Alice": {"Done": "BtcPunished": {"state": <state3 object from BtcLocked>} }}"
|
||||
state,
|
||||
'$.Alice.Done',
|
||||
json_object(
|
||||
'BtcPunished',
|
||||
(
|
||||
SELECT json_extract(states.state, '$.Alice.BtcLocked') -- Read state3 object from BtcLocked
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id -- swap_states.swap_id is id of the BtcPunished row
|
||||
AND json_extract(states.state, '$.Alice.BtcLocked') IS NOT NULL -- Filters out only the BtcLocked state
|
||||
)
|
||||
)
|
||||
)
|
||||
WHERE json_extract(state, '$.Alice.Done') = 'BtcPunished'; -- Apply update only to BtcPunished state rows
|
||||
|
||||
-- Bob: Add new attribute 'state6' (type: State6) to the BtcPunished state by copying it from the BtcCancelled state
|
||||
-- and add new State6 attributes 'v' and 'monero_wallet_restore_blockheight' from the BtcLocked state
|
||||
UPDATE swap_states SET
|
||||
state = json_replace(
|
||||
state,
|
||||
'$.Bob', -- Replace '{"Bob":{"Done": {"BtcPunished": {"tx_lock_id":"..."} }}}' with {"Bob":{"BtcPunished":{"state":{<state6 object>}, "tx_lock_id": "..."}}}
|
||||
json_object(
|
||||
'BtcPunished', -- {"Bob":{"BtcPunished":{}}
|
||||
json_object(
|
||||
'state', -- {"Bob":{"BtcPunished":{"state": {}}}
|
||||
json_insert(
|
||||
( -- object that we insert properties into (original state6 from BtcCancelled state)
|
||||
SELECT json_extract(states.state, '$.Bob.BtcCancelled') -- Get state6 from BtcCancelled state
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id
|
||||
AND json_extract(states.state, '$.Bob.BtcCancelled') IS NOT NULL -- Filters out only the BtcCancelled state
|
||||
),
|
||||
'$.v', -- {"Bob":{"BtcPunished":{"state": {..., "v": "..."}, "tx_lock_id": "..."}}}
|
||||
( -- Get v property from BtcLocked state
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.state3.v')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id -- swap_states.swap_id is id of the BtcPunished row
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL -- Filters out only the BtcLocked state
|
||||
),
|
||||
'$.monero_wallet_restore_blockheight', -- { "Bob": { "BtcPunished":{"state": {..., "monero_wallet_restore_blockheight": {"height":...}} }, "tx_lock_id": "..."} } }
|
||||
( -- Get monero_wallet_restore_blockheight property from BtcLocked state
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.monero_wallet_restore_blockheight')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id -- swap_states.swap_id is id of the BtcPunished row, states.swap_id is id of the row that we are looking for
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL -- Filters out only the BtcLocked state
|
||||
)
|
||||
),
|
||||
'tx_lock_id', -- Insert tx_lock_id BtcPunished -> {"Bob": {"Done": {"BtcPunished": {"state":{<state object>}, "tx_lock_id": "..."} } }
|
||||
json_extract(state, '$.Bob.Done.BtcPunished.tx_lock_id') -- Gets tx_lock_id from original state row
|
||||
)
|
||||
)
|
||||
)
|
||||
WHERE json_extract(state, '$.Bob.Done.BtcPunished') IS NOT NULL; -- Apply update only to BtcPunished state rows
|
||||
|
||||
-- Bob: Add new State6 attributes 'v' and 'monero_wallet_restore_blockheight' to the BtcRefunded state
|
||||
UPDATE swap_states SET
|
||||
state = json_insert(
|
||||
state, -- Object that we insert properties into (original state from the row)
|
||||
'$.Bob.Done.BtcRefunded.v', -- {"Bob":{"BtcRefunded":{..., "v": "..."}}}
|
||||
(
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.state3.v')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id -- swap_states.swap_id is id of the BtcRefunded row, states.swap_id is id of the row that we are looking for
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL
|
||||
),
|
||||
'$.Bob.Done.BtcRefunded.monero_wallet_restore_blockheight', -- {"Bob":{"BtcRefunded":{..., "monero_wallet_restore_blockheight": {"height":...}} }}
|
||||
(
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.monero_wallet_restore_blockheight')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL
|
||||
)
|
||||
)
|
||||
WHERE json_extract(state, '$.Bob.Done.BtcRefunded') IS NOT NULL; -- Apply update only to BtcRefunded state rows
|
||||
|
||||
-- Bob: Add new State6 attributes 'v' and 'monero_wallet_restore_blockheight' to the BtcCancelled state
|
||||
UPDATE swap_states SET
|
||||
state = json_insert(
|
||||
state,
|
||||
'$.Bob.BtcCancelled.v',
|
||||
(
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.state3.v')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL
|
||||
),
|
||||
'$.Bob.BtcCancelled.monero_wallet_restore_blockheight',
|
||||
(
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.monero_wallet_restore_blockheight')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL
|
||||
)
|
||||
)
|
||||
WHERE json_extract(state, '$.Bob.BtcCancelled') IS NOT NULL; -- Apply update only to BtcCancelled state rows
|
||||
|
||||
-- Bob: Add new State6 attributes 'v' and 'monero_wallet_restore_blockheight' to the CancelTimelockExpired state
|
||||
UPDATE swap_states SET
|
||||
state = json_insert(
|
||||
state,
|
||||
'$.Bob.CancelTimelockExpired.v',
|
||||
(
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.state3.v')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL
|
||||
),
|
||||
'$.Bob.CancelTimelockExpired.monero_wallet_restore_blockheight',
|
||||
(
|
||||
SELECT json_extract(states.state, '$.Bob.BtcLocked.monero_wallet_restore_blockheight')
|
||||
FROM swap_states AS states
|
||||
WHERE
|
||||
states.swap_id = swap_states.swap_id
|
||||
AND json_extract(states.state, '$.Bob.BtcLocked') IS NOT NULL
|
||||
)
|
||||
)
|
||||
WHERE json_extract(state, '$.Bob.CancelTimelockExpired') IS NOT NULL; -- Apply update only to CancelTimelockExpired state rows
|
@ -1,12 +1,13 @@
|
||||
pub mod request;
|
||||
use crate::cli::command::{Bitcoin, Monero, Tor};
|
||||
use crate::database::open_db;
|
||||
use crate::common::tracing_util::Format;
|
||||
use crate::database::{open_db, AccessMode};
|
||||
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 crate::{bitcoin, common, monero};
|
||||
use anyhow::{bail, Context as AnyContext, Error, Result};
|
||||
use futures::future::try_join_all;
|
||||
use std::fmt;
|
||||
@ -16,6 +17,8 @@ use std::path::PathBuf;
|
||||
use std::sync::{Arc, Once};
|
||||
use tokio::sync::{broadcast, broadcast::Sender, Mutex, RwLock};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing::Level;
|
||||
use url::Url;
|
||||
|
||||
static START: Once = Once::new();
|
||||
@ -167,6 +170,7 @@ pub struct Context {
|
||||
pub swap_lock: Arc<SwapLock>,
|
||||
pub config: Config,
|
||||
pub tasks: Arc<PendingTaskList>,
|
||||
pub is_daemon: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -180,12 +184,20 @@ impl Context {
|
||||
debug: bool,
|
||||
json: bool,
|
||||
server_address: Option<SocketAddr>,
|
||||
is_daemon: bool,
|
||||
) -> Result<Context> {
|
||||
let data_dir = data::data_dir_from(data, is_testnet)?;
|
||||
let env_config = env_config_from(is_testnet);
|
||||
|
||||
let format = if json { Format::Json } else { Format::Raw };
|
||||
let level_filter = if debug {
|
||||
LevelFilter::from_level(Level::DEBUG)
|
||||
} else {
|
||||
LevelFilter::from_level(Level::INFO)
|
||||
};
|
||||
|
||||
START.call_once(|| {
|
||||
let _ = cli::tracing::init(debug, json, data_dir.join("logs"));
|
||||
let _ = common::tracing_util::init(level_filter, format, data_dir.join("logs"));
|
||||
});
|
||||
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
@ -224,7 +236,7 @@ impl Context {
|
||||
let tor_socks5_port = tor.map_or(9050, |tor| tor.tor_socks5_port);
|
||||
|
||||
let context = Context {
|
||||
db: open_db(data_dir.join("sqlite")).await?,
|
||||
db: open_db(data_dir.join("sqlite"), AccessMode::ReadWrite).await?,
|
||||
bitcoin_wallet,
|
||||
monero_wallet,
|
||||
monero_rpc_process,
|
||||
@ -241,6 +253,7 @@ impl Context {
|
||||
},
|
||||
swap_lock: Arc::new(SwapLock::new()),
|
||||
tasks: Arc::new(PendingTaskList::default()),
|
||||
is_daemon,
|
||||
};
|
||||
|
||||
Ok(context)
|
||||
@ -259,14 +272,19 @@ impl Context {
|
||||
bitcoin_wallet: Some(bob_bitcoin_wallet),
|
||||
monero_wallet: Some(bob_monero_wallet),
|
||||
config,
|
||||
db: open_db(db_path)
|
||||
db: open_db(db_path, AccessMode::ReadWrite)
|
||||
.await
|
||||
.expect("Could not open sqlite database"),
|
||||
monero_rpc_process: None,
|
||||
swap_lock: Arc::new(SwapLock::new()),
|
||||
tasks: Arc::new(PendingTaskList::default()),
|
||||
is_daemon: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitcoin_wallet(&self) -> Option<Arc<bitcoin::Wallet>> {
|
||||
self.bitcoin_wallet.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Context {
|
||||
@ -433,7 +451,7 @@ pub mod api_test {
|
||||
|
||||
Request::new(Method::BuyXmr {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
bitcoin_change_address: Some(bitcoin_change_address),
|
||||
monero_receive_address,
|
||||
swap_id: Uuid::new_v4(),
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::api::Context;
|
||||
use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock};
|
||||
use crate::cli::{list_sellers, EventLoop, SellerStatus};
|
||||
use crate::common::get_logs;
|
||||
use crate::libp2p_ext::MultiAddrExt;
|
||||
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||
use crate::network::swarm;
|
||||
@ -8,14 +9,18 @@ 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 comfy_table::Table;
|
||||
use libp2p::core::Multiaddr;
|
||||
use qrcode::render::unicode;
|
||||
use qrcode::QrCode;
|
||||
use rust_decimal::prelude::FromPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
use serde_json::json;
|
||||
use std::cmp::min;
|
||||
use std::convert::TryInto;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug_span, field, Instrument, Span};
|
||||
@ -31,7 +36,7 @@ pub struct Request {
|
||||
pub enum Method {
|
||||
BuyXmr {
|
||||
seller: Multiaddr,
|
||||
bitcoin_change_address: bitcoin::Address,
|
||||
bitcoin_change_address: Option<bitcoin::Address>,
|
||||
monero_receive_address: monero::Address,
|
||||
swap_id: Uuid,
|
||||
},
|
||||
@ -45,6 +50,11 @@ pub enum Method {
|
||||
swap_id: Uuid,
|
||||
},
|
||||
History,
|
||||
Logs {
|
||||
logs_dir: Option<PathBuf>,
|
||||
redact: bool,
|
||||
swap_id: Option<Uuid>,
|
||||
},
|
||||
Config,
|
||||
WithdrawBtc {
|
||||
amount: Option<Amount>,
|
||||
@ -122,6 +132,13 @@ impl Method {
|
||||
log_reference_id = field::Empty
|
||||
)
|
||||
}
|
||||
Method::Logs { .. } => {
|
||||
debug_span!(
|
||||
"method",
|
||||
method_name = "Logs",
|
||||
log_reference_id = field::Empty
|
||||
)
|
||||
}
|
||||
Method::ListSellers { .. } => {
|
||||
debug_span!(
|
||||
"method",
|
||||
@ -335,6 +352,25 @@ impl Request {
|
||||
let env_config = context.config.env_config;
|
||||
let seed = context.config.seed.clone().context("Could not get seed")?;
|
||||
|
||||
// When no change address was provided we default to the internal wallet
|
||||
let bitcoin_change_address = match bitcoin_change_address {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
let internal_wallet_address = context
|
||||
.bitcoin_wallet()
|
||||
.expect("bitcoin wallet should exist")
|
||||
.new_address()
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
internal_wallet_address=%internal_wallet_address,
|
||||
"No --change-address supplied. Any change will be received to the internal wallet."
|
||||
);
|
||||
|
||||
internal_wallet_address
|
||||
}
|
||||
};
|
||||
|
||||
let seller_peer_id = seller
|
||||
.extract_peer_id()
|
||||
.context("Seller address must contain peer ID")?;
|
||||
@ -619,14 +655,111 @@ impl Request {
|
||||
})
|
||||
}
|
||||
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()));
|
||||
let mut table = Table::new();
|
||||
table.set_header(vec![
|
||||
"Swap ID",
|
||||
"Start Date",
|
||||
"State",
|
||||
"BTC Amount",
|
||||
"XMR Amount",
|
||||
"Exchange Rate",
|
||||
"Trading Partner Peer ID",
|
||||
]);
|
||||
|
||||
let all_swaps = context.db.all().await?;
|
||||
let mut json_results = Vec::new();
|
||||
|
||||
for (swap_id, state) in all_swaps {
|
||||
let result: Result<_> = async {
|
||||
let latest_state: BobState = state.try_into()?;
|
||||
let all_states = context.db.get_states(swap_id).await?;
|
||||
let state3 = all_states
|
||||
.iter()
|
||||
.find_map(|s| {
|
||||
if let State::Bob(BobState::BtcLocked { state3, .. }) = s {
|
||||
Some(state3)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.context("Failed to get \"BtcLocked\" state")?;
|
||||
|
||||
let swap_start_date = context.db.get_swap_start_date(swap_id).await?;
|
||||
let peer_id = context.db.get_peer_id(swap_id).await?;
|
||||
let btc_amount = state3.tx_lock.lock_amount();
|
||||
let xmr_amount = state3.xmr;
|
||||
let exchange_rate = Decimal::from_f64(btc_amount.to_btc())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Failed to convert BTC amount to Decimal")
|
||||
})?
|
||||
.checked_div(xmr_amount.as_xmr())
|
||||
.ok_or_else(|| anyhow::anyhow!("Division by zero or overflow"))?;
|
||||
let exchange_rate = format!("{} XMR/BTC", exchange_rate.round_dp(8));
|
||||
|
||||
let swap_data = json!({
|
||||
"swapId": swap_id.to_string(),
|
||||
"startDate": swap_start_date.to_string(),
|
||||
"state": latest_state.to_string(),
|
||||
"btcAmount": btc_amount.to_string(),
|
||||
"xmrAmount": xmr_amount.to_string(),
|
||||
"exchangeRate": exchange_rate,
|
||||
"tradingPartnerPeerId": peer_id.to_string()
|
||||
});
|
||||
|
||||
if context.config.json {
|
||||
tracing::info!(
|
||||
swap_id = %swap_id,
|
||||
swap_start_date = %swap_start_date,
|
||||
latest_state = %latest_state,
|
||||
btc_amount = %btc_amount,
|
||||
xmr_amount = %xmr_amount,
|
||||
exchange_rate = %exchange_rate,
|
||||
trading_partner_peer_id = %peer_id,
|
||||
"Found swap in database"
|
||||
);
|
||||
} else {
|
||||
table.add_row(vec![
|
||||
swap_id.to_string(),
|
||||
swap_start_date.to_string(),
|
||||
latest_state.to_string(),
|
||||
btc_amount.to_string(),
|
||||
xmr_amount.to_string(),
|
||||
exchange_rate,
|
||||
peer_id.to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
Ok(swap_data)
|
||||
}
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(swap_data) => json_results.push(swap_data),
|
||||
Err(e) => {
|
||||
tracing::error!(swap_id = %swap_id, error = %e, "Failed to get swap details")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(json!({ "swaps": vec }))
|
||||
if !context.config.json && !context.is_daemon {
|
||||
println!("{}", table);
|
||||
}
|
||||
|
||||
Ok(json!({"swaps": json_results}))
|
||||
}
|
||||
Method::Logs {
|
||||
logs_dir,
|
||||
redact,
|
||||
swap_id,
|
||||
} => {
|
||||
let dir = logs_dir.unwrap_or(context.config.data_dir.join("logs"));
|
||||
let logs = get_logs(dir, swap_id, redact).await?;
|
||||
|
||||
for msg in &logs {
|
||||
println!("{msg}");
|
||||
}
|
||||
|
||||
Ok(json!({ "logs": logs }))
|
||||
}
|
||||
Method::GetRawStates => {
|
||||
let raw_history = context.db.raw_all().await?;
|
||||
|
@ -4,7 +4,6 @@ mod event_loop;
|
||||
mod network;
|
||||
mod rate;
|
||||
mod recovery;
|
||||
pub mod tracing;
|
||||
|
||||
pub use event_loop::{EventLoop, EventLoopHandle, FixedRate, KrakenRate, LatestRate};
|
||||
pub use network::behaviour::{Behaviour, OutEvent};
|
||||
|
@ -19,7 +19,6 @@ where
|
||||
let args = RawArguments::from_clap(&matches);
|
||||
|
||||
let json = args.json;
|
||||
let disable_timestamp = args.disable_timestamp;
|
||||
let testnet = args.testnet;
|
||||
let config = args.config;
|
||||
let command: RawCommand = args.cmd;
|
||||
@ -28,23 +27,35 @@ where
|
||||
RawCommand::Start { resume_only } => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Start { resume_only },
|
||||
},
|
||||
RawCommand::History => Arguments {
|
||||
RawCommand::History { only_unfinished } => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::History,
|
||||
cmd: Command::History { only_unfinished },
|
||||
},
|
||||
RawCommand::Logs {
|
||||
logs_dir: dir_path,
|
||||
swap_id,
|
||||
redact,
|
||||
} => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Logs {
|
||||
logs_dir: dir_path,
|
||||
swap_id,
|
||||
redact,
|
||||
},
|
||||
},
|
||||
RawCommand::WithdrawBtc { amount, address } => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::WithdrawBtc {
|
||||
@ -55,7 +66,6 @@ where
|
||||
RawCommand::Balance => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Balance,
|
||||
@ -63,7 +73,6 @@ where
|
||||
RawCommand::Config => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Config,
|
||||
@ -71,7 +80,6 @@ where
|
||||
RawCommand::ExportBitcoinWallet => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::ExportBitcoinWallet,
|
||||
@ -82,7 +90,6 @@ where
|
||||
}) => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Redeem {
|
||||
@ -96,7 +103,6 @@ where
|
||||
}) => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Cancel { swap_id },
|
||||
@ -106,7 +112,6 @@ where
|
||||
}) => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Refund { swap_id },
|
||||
@ -116,7 +121,6 @@ where
|
||||
}) => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Punish { swap_id },
|
||||
@ -124,7 +128,6 @@ where
|
||||
RawCommand::ManualRecovery(ManualRecovery::SafelyAbort { swap_id }) => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::SafelyAbort { swap_id },
|
||||
@ -184,7 +187,6 @@ pub struct BitcoinAddressNetworkMismatch {
|
||||
pub struct Arguments {
|
||||
pub testnet: bool,
|
||||
pub json: bool,
|
||||
pub disable_timestamp: bool,
|
||||
pub config_path: PathBuf,
|
||||
pub env_config: env::Config,
|
||||
pub cmd: Command,
|
||||
@ -195,8 +197,15 @@ pub enum Command {
|
||||
Start {
|
||||
resume_only: bool,
|
||||
},
|
||||
History,
|
||||
History {
|
||||
only_unfinished: bool,
|
||||
},
|
||||
Config,
|
||||
Logs {
|
||||
logs_dir: Option<PathBuf>,
|
||||
swap_id: Option<Uuid>,
|
||||
redact: bool,
|
||||
},
|
||||
WithdrawBtc {
|
||||
amount: Option<Amount>,
|
||||
address: Address,
|
||||
@ -268,8 +277,33 @@ pub enum RawCommand {
|
||||
)]
|
||||
resume_only: bool,
|
||||
},
|
||||
#[structopt(about = "Prints all logging messages issued in the past.")]
|
||||
Logs {
|
||||
#[structopt(
|
||||
short = "d",
|
||||
help = "Print the logs from this directory instead of the default one."
|
||||
)]
|
||||
logs_dir: Option<PathBuf>,
|
||||
#[structopt(
|
||||
help = "Redact swap-ids, Bitcoin and Monero addresses.",
|
||||
long = "redact"
|
||||
)]
|
||||
redact: bool,
|
||||
#[structopt(
|
||||
long = "swap-id",
|
||||
help = "Filter for logs concerning this swap.",
|
||||
long_help = "This checks whether each logging message contains the swap id. Some messages might be skipped when they don't contain the swap id even though they're relevant."
|
||||
)]
|
||||
swap_id: Option<Uuid>,
|
||||
},
|
||||
#[structopt(about = "Prints swap-id and the state of each swap ever made.")]
|
||||
History,
|
||||
History {
|
||||
#[structopt(
|
||||
long = "only-unfinished",
|
||||
help = "If set, only unfinished swaps will be printed."
|
||||
)]
|
||||
only_unfinished: bool,
|
||||
},
|
||||
#[structopt(about = "Prints the current config")]
|
||||
Config,
|
||||
#[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")]
|
||||
@ -366,7 +400,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::Start { resume_only: false },
|
||||
@ -384,10 +417,11 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::History,
|
||||
cmd: Command::History {
|
||||
only_unfinished: false,
|
||||
},
|
||||
};
|
||||
let args = parse_args(raw_ars).unwrap();
|
||||
assert_eq!(expected_args, args);
|
||||
@ -402,7 +436,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::Balance,
|
||||
@ -424,7 +457,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::WithdrawBtc {
|
||||
@ -451,7 +483,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::Cancel {
|
||||
@ -477,7 +508,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::Refund {
|
||||
@ -503,7 +533,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::Punish {
|
||||
@ -529,7 +558,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::SafelyAbort {
|
||||
@ -549,7 +577,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::Start { resume_only: false },
|
||||
@ -567,10 +594,11 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::History,
|
||||
cmd: Command::History {
|
||||
only_unfinished: false,
|
||||
},
|
||||
};
|
||||
let args = parse_args(raw_ars).unwrap();
|
||||
assert_eq!(expected_args, args);
|
||||
@ -585,7 +613,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::Balance,
|
||||
@ -609,7 +636,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::WithdrawBtc {
|
||||
@ -636,7 +662,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::Cancel {
|
||||
@ -663,7 +688,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::Refund {
|
||||
@ -690,7 +714,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::Punish {
|
||||
@ -717,7 +740,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
cmd: Command::SafelyAbort {
|
||||
@ -737,7 +759,6 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
disable_timestamp: true,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
cmd: Command::Start { resume_only: false },
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::asb::{Behaviour, OutEvent, Rate};
|
||||
use crate::monero::Amount;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::CooperativeXmrRedeemRejectReason;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, Rejected};
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::swap_setup::alice::WalletSnapshot;
|
||||
use crate::network::transfer_proof;
|
||||
@ -253,6 +255,59 @@ where
|
||||
channel
|
||||
}.boxed());
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::CooperativeXmrRedeemRequested { swap_id, channel, peer }) => {
|
||||
let swap_peer = self.db.get_peer_id(swap_id).await;
|
||||
let swap_state = self.db.get_state(swap_id).await;
|
||||
|
||||
let (swap_peer, swap_state) = match (swap_peer, swap_state) {
|
||||
(Ok(peer), Ok(state)) => (peer, state),
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
swap_id = %swap_id,
|
||||
received_from = %peer,
|
||||
reason = "swap not found",
|
||||
"Rejecting cooperative XMR redeem request"
|
||||
);
|
||||
if self.swarm.behaviour_mut().cooperative_xmr_redeem.send_response(channel, Rejected { swap_id, reason: CooperativeXmrRedeemRejectReason::UnknownSwap }).is_err() {
|
||||
tracing::error!(swap_id = %swap_id, "Failed to reject cooperative XMR redeem request");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if swap_peer != peer {
|
||||
tracing::warn!(
|
||||
swap_id = %swap_id,
|
||||
received_from = %peer,
|
||||
expected_from = %swap_peer,
|
||||
reason = "unexpected peer",
|
||||
"Rejecting cooperative XMR redeem request"
|
||||
);
|
||||
if self.swarm.behaviour_mut().cooperative_xmr_redeem.send_response(channel, Rejected { swap_id, reason: CooperativeXmrRedeemRejectReason::MaliciousRequest }).is_err() {
|
||||
tracing::error!(swap_id = %swap_id, "Failed to reject cooperative XMR redeem request");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let State::Alice (AliceState::BtcPunished { state3 }) = swap_state else {
|
||||
tracing::warn!(
|
||||
swap_id = %swap_id,
|
||||
reason = "swap is in invalid state",
|
||||
"Rejecting cooperative XMR redeem request"
|
||||
);
|
||||
if self.swarm.behaviour_mut().cooperative_xmr_redeem.send_response(channel, Rejected { swap_id, reason: CooperativeXmrRedeemRejectReason::SwapInvalidState }).is_err() {
|
||||
tracing::error!(swap_id = %swap_id, "Failed to reject cooperative XMR redeem request");
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
if self.swarm.behaviour_mut().cooperative_xmr_redeem.send_response(channel, Fullfilled { swap_id, s_a: state3.s_a }).is_err() {
|
||||
tracing::error!(peer = %peer, "Failed to respond to cooperative XMR redeem request");
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::info!(swap_id = %swap_id, peer = %peer, "Fullfilled cooperative XMR redeem request");
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::Registered { rendezvous_node, ttl, namespace })) => {
|
||||
tracing::info!("Successfully registered with rendezvous node: {} with namespace: {} and TTL: {:?}", rendezvous_node, namespace, ttl);
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ use crate::network::rendezvous::XmrBtcNamespace;
|
||||
use crate::network::swap_setup::alice;
|
||||
use crate::network::swap_setup::alice::WalletSnapshot;
|
||||
use crate::network::transport::authenticate_and_multiplex;
|
||||
use crate::network::{encrypted_signature, quote, transfer_proof};
|
||||
use crate::network::{
|
||||
cooperative_xmr_redeem_after_punish, encrypted_signature, quote, transfer_proof,
|
||||
};
|
||||
use crate::protocol::alice::State3;
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use futures::FutureExt;
|
||||
@ -76,6 +78,11 @@ pub mod behaviour {
|
||||
channel: ResponseChannel<()>,
|
||||
peer: PeerId,
|
||||
},
|
||||
CooperativeXmrRedeemRequested {
|
||||
channel: ResponseChannel<cooperative_xmr_redeem_after_punish::Response>,
|
||||
swap_id: Uuid,
|
||||
peer: PeerId,
|
||||
},
|
||||
Rendezvous(libp2p::rendezvous::client::Event),
|
||||
Failure {
|
||||
peer: PeerId,
|
||||
@ -114,6 +121,7 @@ pub mod behaviour {
|
||||
pub quote: quote::Behaviour,
|
||||
pub swap_setup: alice::Behaviour<LR>,
|
||||
pub transfer_proof: transfer_proof::Behaviour,
|
||||
pub cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::Behaviour,
|
||||
pub encrypted_signature: encrypted_signature::Behaviour,
|
||||
pub identify: Identify,
|
||||
|
||||
@ -160,6 +168,7 @@ pub mod behaviour {
|
||||
),
|
||||
transfer_proof: transfer_proof::alice(),
|
||||
encrypted_signature: encrypted_signature::alice(),
|
||||
cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::alice(),
|
||||
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
|
||||
identify: Identify::new(identifyConfig),
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ pub async fn cancel(
|
||||
// Alice already in final state
|
||||
| AliceState::BtcRedeemed
|
||||
| AliceState::XmrRefunded
|
||||
| AliceState::BtcPunished
|
||||
| AliceState::BtcPunished { .. }
|
||||
| AliceState::SafelyAborted => bail!("Swap is in state {} which is not cancelable", state),
|
||||
};
|
||||
|
||||
|
@ -38,7 +38,7 @@ pub async fn punish(
|
||||
// Alice already in final state
|
||||
| AliceState::BtcRedeemed
|
||||
| AliceState::XmrRefunded
|
||||
| AliceState::BtcPunished
|
||||
| AliceState::BtcPunished { .. }
|
||||
| AliceState::SafelyAborted => bail!(Error::SwapNotPunishable(state)),
|
||||
};
|
||||
|
||||
@ -46,7 +46,9 @@ pub async fn punish(
|
||||
|
||||
let txid = state3.punish_btc(&bitcoin_wallet).await?;
|
||||
|
||||
let state = AliceState::BtcPunished;
|
||||
let state = AliceState::BtcPunished {
|
||||
state3: state3.clone(),
|
||||
};
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
|
@ -81,7 +81,7 @@ pub async fn redeem(
|
||||
| AliceState::BtcPunishable { .. }
|
||||
| AliceState::BtcRedeemed
|
||||
| AliceState::XmrRefunded
|
||||
| AliceState::BtcPunished
|
||||
| AliceState::BtcPunished { .. }
|
||||
| AliceState::SafelyAborted => bail!(
|
||||
"Cannot redeem swap {} because it is in state {} which cannot be manually redeemed",
|
||||
swap_id,
|
||||
|
@ -55,7 +55,7 @@ pub async fn refund(
|
||||
AliceState::BtcRedeemTransactionPublished { .. }
|
||||
| AliceState::BtcRedeemed
|
||||
| AliceState::XmrRefunded
|
||||
| AliceState::BtcPunished
|
||||
| AliceState::BtcPunished { .. }
|
||||
| AliceState::SafelyAborted => bail!(Error::SwapNotRefundable(state)),
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ pub async fn safely_abort(swap_id: Uuid, db: Arc<dyn Database>) -> Result<AliceS
|
||||
| AliceState::BtcPunishable { .. }
|
||||
| AliceState::BtcRedeemed
|
||||
| AliceState::XmrRefunded
|
||||
| AliceState::BtcPunished
|
||||
| AliceState::BtcPunished { .. }
|
||||
| AliceState::SafelyAborted => bail!(
|
||||
"Cannot safely abort swap {} because it is in state {} which cannot be safely aborted",
|
||||
swap_id,
|
||||
|
@ -1,30 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
use tracing_subscriber::fmt::time::UtcTime;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
pub fn init(level: LevelFilter, json_format: bool, timestamp: bool) -> Result<()> {
|
||||
if level == LevelFilter::OFF {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
|
||||
let builder = FmtSubscriber::builder()
|
||||
.with_env_filter(format!("asb={},swap={}", level, level))
|
||||
.with_writer(std::io::stderr)
|
||||
.with_ansi(is_terminal)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false);
|
||||
|
||||
match (json_format, timestamp) {
|
||||
(true, true) => builder.json().init(),
|
||||
(true, false) => builder.json().without_time().init(),
|
||||
(false, true) => builder.init(),
|
||||
(false, false) => builder.without_time().init(),
|
||||
}
|
||||
|
||||
tracing::info!(%level, "Initialized tracing");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -18,6 +18,8 @@ use libp2p::core::multiaddr::Protocol;
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::swarm::AddressScore;
|
||||
use libp2p::Swarm;
|
||||
use rust_decimal::prelude::FromPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
@ -29,51 +31,50 @@ use swap::asb::config::{
|
||||
initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized,
|
||||
};
|
||||
use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, Finality, KrakenRate};
|
||||
use swap::common::check_latest_version;
|
||||
use swap::database::open_db;
|
||||
use swap::common::tracing_util::Format;
|
||||
use swap::common::{self, check_latest_version, get_logs};
|
||||
use swap::database::{open_db, AccessMode};
|
||||
use swap::network::rendezvous::XmrBtcNamespace;
|
||||
use swap::network::swarm;
|
||||
use swap::protocol::alice::swap::is_complete;
|
||||
use swap::protocol::alice::{run, AliceState};
|
||||
use swap::protocol::State;
|
||||
use swap::seed::Seed;
|
||||
use swap::tor::AuthenticatedClient;
|
||||
use swap::{asb, bitcoin, kraken, monero, tor};
|
||||
use swap::{bitcoin, kraken, monero, tor};
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
|
||||
const DEFAULT_WALLET_NAME: &str = "asb-wallet";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// parse cli arguments
|
||||
let Arguments {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
config_path,
|
||||
env_config,
|
||||
cmd,
|
||||
} = match parse_args(env::args_os()) {
|
||||
Ok(args) => args,
|
||||
Err(e) => {
|
||||
// make sure to display the clap error message it exists
|
||||
if let Some(clap_err) = e.downcast_ref::<clap::Error>() {
|
||||
match clap_err.kind {
|
||||
ErrorKind::HelpDisplayed | ErrorKind::VersionDisplayed => {
|
||||
println!("{}", clap_err.message);
|
||||
std::process::exit(0);
|
||||
}
|
||||
_ => {
|
||||
bail!(e);
|
||||
}
|
||||
if let ErrorKind::HelpDisplayed | ErrorKind::VersionDisplayed = clap_err.kind {
|
||||
println!("{}", clap_err.message);
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
bail!(e);
|
||||
}
|
||||
};
|
||||
|
||||
// warn if we're not on the latest version
|
||||
if let Err(e) = check_latest_version(env!("CARGO_PKG_VERSION")).await {
|
||||
eprintln!("{}", e);
|
||||
}
|
||||
|
||||
asb::tracing::init(LevelFilter::DEBUG, json, !disable_timestamp).expect("initialize tracing");
|
||||
|
||||
// read config from the specified path
|
||||
let config = match read_config(config_path.clone())? {
|
||||
Ok(config) => config,
|
||||
Err(ConfigNotInitialized {}) => {
|
||||
@ -82,6 +83,12 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
// initialize tracing
|
||||
let format = if json { Format::Json } else { Format::Raw };
|
||||
let log_dir = config.data.dir.join("logs");
|
||||
common::tracing_util::init(LevelFilter::DEBUG, format, log_dir).expect("initialize tracing");
|
||||
|
||||
// check for conflicting env / config values
|
||||
if config.monero.network != env_config.monero_network {
|
||||
bail!(format!(
|
||||
"Expected monero network in config file to be {:?} but was {:?}",
|
||||
@ -95,19 +102,20 @@ async fn main() -> Result<()> {
|
||||
));
|
||||
}
|
||||
|
||||
let db = open_db(config.data.dir.join("sqlite")).await?;
|
||||
|
||||
let seed =
|
||||
Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed");
|
||||
|
||||
match cmd {
|
||||
Command::Start { resume_only } => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadWrite).await?;
|
||||
|
||||
// check and warn for duplicate rendezvous points
|
||||
let mut rendezvous_addrs = config.network.rendezvous_point.clone();
|
||||
let prev_len = rendezvous_addrs.len();
|
||||
rendezvous_addrs.sort();
|
||||
rendezvous_addrs.dedup();
|
||||
let new_len = rendezvous_addrs.len();
|
||||
|
||||
if new_len < prev_len {
|
||||
tracing::warn!(
|
||||
"`rendezvous_point` config has {} duplicate entries, they are being ignored.",
|
||||
@ -115,9 +123,12 @@ async fn main() -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
// initialize monero wallet
|
||||
let monero_wallet = init_monero_wallet(&config, env_config).await?;
|
||||
let monero_address = monero_wallet.get_main_address();
|
||||
tracing::info!(%monero_address, "Monero wallet address");
|
||||
|
||||
// check monero balance
|
||||
let monero = monero_wallet.get_balance().await?;
|
||||
match (monero.balance, monero.unlocked_balance) {
|
||||
(0, _) => {
|
||||
@ -140,6 +151,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// init bitcoin wallet
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
||||
tracing::info!(%bitcoin_balance, "Bitcoin wallet balance");
|
||||
@ -224,22 +236,105 @@ async fn main() -> Result<()> {
|
||||
|
||||
event_loop.run().await;
|
||||
}
|
||||
Command::History => {
|
||||
let mut table = Table::new();
|
||||
Command::History { only_unfinished } => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadOnly).await?;
|
||||
let mut table: Table = Table::new();
|
||||
|
||||
table.set_header(vec!["SWAP ID", "STATE"]);
|
||||
table.set_header(vec![
|
||||
"Swap ID",
|
||||
"Start Date",
|
||||
"State",
|
||||
"BTC Amount",
|
||||
"XMR Amount",
|
||||
"Exchange Rate",
|
||||
"Trading Partner Peer ID",
|
||||
"Completed",
|
||||
]);
|
||||
|
||||
for (swap_id, state) in db.all().await? {
|
||||
let state: AliceState = state.try_into()?;
|
||||
table.add_row(vec![swap_id.to_string(), state.to_string()]);
|
||||
let all_swaps = db.all().await?;
|
||||
for (swap_id, state) in all_swaps {
|
||||
if let Err(e) = async {
|
||||
let latest_state: AliceState = state.try_into()?;
|
||||
let is_completed = is_complete(&latest_state);
|
||||
|
||||
if only_unfinished && is_completed {
|
||||
return Ok::<_, anyhow::Error>(());
|
||||
}
|
||||
|
||||
let all_states = db.get_states(swap_id).await?;
|
||||
let state3 = all_states
|
||||
.iter()
|
||||
.find_map(|s| match s {
|
||||
State::Alice(AliceState::BtcLockTransactionSeen { state3 }) => {
|
||||
Some(state3)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.context("Failed to get \"BtcLockTransactionSeen\" state")?;
|
||||
|
||||
let swap_start_date = db.get_swap_start_date(swap_id).await?;
|
||||
let peer_id = db.get_peer_id(swap_id).await?;
|
||||
|
||||
let exchange_rate = Decimal::from_f64(state3.btc.to_btc())
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to convert BTC amount to Decimal"))?
|
||||
.checked_div(state3.xmr.as_xmr())
|
||||
.ok_or_else(|| anyhow::anyhow!("Division by zero or overflow"))?;
|
||||
let exchange_rate = format!("{} XMR/BTC", exchange_rate.round_dp(8));
|
||||
|
||||
if json {
|
||||
tracing::info!(
|
||||
swap_id = %swap_id,
|
||||
swap_start_date = %swap_start_date,
|
||||
latest_state = %latest_state,
|
||||
btc_amount = %state3.btc,
|
||||
xmr_amount = %state3.xmr,
|
||||
exchange_rate = %exchange_rate,
|
||||
trading_partner_peer_id = %peer_id,
|
||||
completed = is_completed,
|
||||
"Found swap in database"
|
||||
);
|
||||
} else {
|
||||
table.add_row(vec![
|
||||
swap_id.to_string(),
|
||||
swap_start_date.to_string(),
|
||||
latest_state.to_string(),
|
||||
state3.btc.to_string(),
|
||||
state3.xmr.to_string(),
|
||||
exchange_rate,
|
||||
peer_id.to_string(),
|
||||
is_completed.to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
tracing::error!(swap_id = %swap_id, error = %e, "Failed to get swap details");
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
if !json {
|
||||
println!("{}", table);
|
||||
}
|
||||
}
|
||||
Command::Config => {
|
||||
let config_json = serde_json::to_string_pretty(&config)?;
|
||||
println!("{}", config_json);
|
||||
}
|
||||
Command::Logs {
|
||||
logs_dir,
|
||||
swap_id,
|
||||
redact,
|
||||
} => {
|
||||
let dir = logs_dir.unwrap_or(config.data.dir.join("logs"));
|
||||
|
||||
let log_messages = get_logs(dir, swap_id, redact).await?;
|
||||
|
||||
for msg in log_messages {
|
||||
println!("{msg}");
|
||||
}
|
||||
}
|
||||
Command::WithdrawBtc { amount, address } => {
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
@ -270,6 +365,8 @@ async fn main() -> Result<()> {
|
||||
tracing::info!(%bitcoin_balance, %monero_balance, "Current balance");
|
||||
}
|
||||
Command::Cancel { swap_id } => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadWrite).await?;
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
let (txid, _) = cancel(swap_id, Arc::new(bitcoin_wallet), db).await?;
|
||||
@ -277,6 +374,8 @@ async fn main() -> Result<()> {
|
||||
tracing::info!("Cancel transaction successfully published with id {}", txid);
|
||||
}
|
||||
Command::Refund { swap_id } => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadWrite).await?;
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
let monero_wallet = init_monero_wallet(&config, env_config).await?;
|
||||
|
||||
@ -291,6 +390,8 @@ async fn main() -> Result<()> {
|
||||
tracing::info!("Monero successfully refunded");
|
||||
}
|
||||
Command::Punish { swap_id } => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadWrite).await?;
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
let (txid, _) = punish(swap_id, Arc::new(bitcoin_wallet), db).await?;
|
||||
@ -298,6 +399,8 @@ async fn main() -> Result<()> {
|
||||
tracing::info!("Punish transaction successfully published with id {}", txid);
|
||||
}
|
||||
Command::SafelyAbort { swap_id } => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadWrite).await?;
|
||||
|
||||
safely_abort(swap_id, db).await?;
|
||||
|
||||
tracing::info!("Swap safely aborted");
|
||||
@ -306,6 +409,8 @@ async fn main() -> Result<()> {
|
||||
swap_id,
|
||||
do_not_await_finality,
|
||||
} => {
|
||||
let db = open_db(config.data.dir.join("sqlite"), AccessMode::ReadWrite).await?;
|
||||
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
let (txid, _) = redeem(
|
||||
|
@ -468,6 +468,7 @@ where
|
||||
) -> Result<bitcoin::Amount> {
|
||||
let client = self.client.lock().await;
|
||||
let fee_rate = client.estimate_feerate(self.target_block)?;
|
||||
|
||||
let min_relay_fee = client.min_relay_fee()?;
|
||||
|
||||
estimate_fee(weight, transfer_amount, fee_rate, min_relay_fee)
|
||||
@ -871,6 +872,11 @@ impl EstimateFeeRate for Client {
|
||||
// https://github.com/romanz/electrs/blob/f9cf5386d1b5de6769ee271df5eef324aa9491bc/src/rpc.rs#L213
|
||||
// Returned estimated fees are per BTC/kb.
|
||||
let fee_per_byte = self.electrum.estimate_fee(target_block)?;
|
||||
|
||||
if fee_per_byte < 0.0 {
|
||||
bail!("Fee per byte returned by electrum server is negative: {}. This may indicate that fee estimation is not supported by this server", fee_per_byte);
|
||||
}
|
||||
|
||||
// we do not expect fees being that high.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
Ok(FeeRate::from_btc_per_kvb(fee_per_byte as f32))
|
||||
|
@ -3,7 +3,6 @@ pub mod cancel_and_refund;
|
||||
pub mod command;
|
||||
mod event_loop;
|
||||
mod list_sellers;
|
||||
pub mod tracing;
|
||||
pub mod transport;
|
||||
|
||||
pub use behaviour::{Behaviour, OutEvent};
|
||||
|
@ -1,7 +1,11 @@
|
||||
use crate::monero::Scalar;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::CooperativeXmrRedeemRejectReason;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::rendezvous::XmrBtcNamespace;
|
||||
use crate::network::swap_setup::bob;
|
||||
use crate::network::{encrypted_signature, quote, redial, transfer_proof};
|
||||
use crate::network::{
|
||||
cooperative_xmr_redeem_after_punish, encrypted_signature, quote, redial, transfer_proof,
|
||||
};
|
||||
use crate::protocol::bob::State2;
|
||||
use crate::{bitcoin, env};
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
@ -28,6 +32,16 @@ pub enum OutEvent {
|
||||
EncryptedSignatureAcknowledged {
|
||||
id: RequestId,
|
||||
},
|
||||
CooperativeXmrRedeemFulfilled {
|
||||
id: RequestId,
|
||||
s_a: Scalar,
|
||||
swap_id: uuid::Uuid,
|
||||
},
|
||||
CooperativeXmrRedeemRejected {
|
||||
id: RequestId,
|
||||
reason: CooperativeXmrRedeemRejectReason,
|
||||
swap_id: uuid::Uuid,
|
||||
},
|
||||
AllRedialAttemptsExhausted {
|
||||
peer: PeerId,
|
||||
},
|
||||
@ -64,6 +78,7 @@ pub struct Behaviour {
|
||||
pub quote: quote::Behaviour,
|
||||
pub swap_setup: bob::Behaviour,
|
||||
pub transfer_proof: transfer_proof::Behaviour,
|
||||
pub cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::Behaviour,
|
||||
pub encrypted_signature: encrypted_signature::Behaviour,
|
||||
pub redial: redial::Behaviour,
|
||||
pub identify: Identify,
|
||||
@ -91,6 +106,7 @@ impl Behaviour {
|
||||
swap_setup: bob::Behaviour::new(env_config, bitcoin_wallet),
|
||||
transfer_proof: transfer_proof::bob(),
|
||||
encrypted_signature: encrypted_signature::bob(),
|
||||
cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::bob(),
|
||||
redial: redial::Behaviour::new(alice, Duration::from_secs(2)),
|
||||
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
|
||||
identify: Identify::new(identifyConfig),
|
||||
|
@ -31,8 +31,16 @@ pub async fn cancel(
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
let state6 = match state {
|
||||
BobState::BtcLocked { state3, .. } => state3.cancel(),
|
||||
BobState::XmrLockProofReceived { state, .. } => state.cancel(),
|
||||
BobState::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
..
|
||||
} => state3.cancel(monero_wallet_restore_blockheight),
|
||||
BobState::XmrLockProofReceived {
|
||||
state,
|
||||
monero_wallet_restore_blockheight,
|
||||
..
|
||||
} => state.cancel(monero_wallet_restore_blockheight),
|
||||
BobState::XmrLocked(state4) => state4.cancel(),
|
||||
BobState::EncSigSent(state4) => state4.cancel(),
|
||||
BobState::CancelTimelockExpired(state6) => state6,
|
||||
@ -81,6 +89,7 @@ pub async fn cancel(
|
||||
// We cannot cancel because Alice has already cancelled and punished afterwards
|
||||
Ok(ExpiredTimelocks::Punish { .. }) => {
|
||||
let state = BobState::BtcPunished {
|
||||
state: state6.clone(),
|
||||
tx_lock_id: state6.tx_lock_id(),
|
||||
};
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
@ -118,8 +127,15 @@ pub async fn refund(
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
let state6 = match state {
|
||||
BobState::BtcLocked { state3, .. } => state3.cancel(),
|
||||
BobState::XmrLockProofReceived { state, .. } => state.cancel(),
|
||||
BobState::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => state3.cancel(monero_wallet_restore_blockheight),
|
||||
BobState::XmrLockProofReceived {
|
||||
state,
|
||||
monero_wallet_restore_blockheight,
|
||||
..
|
||||
} => state.cancel(monero_wallet_restore_blockheight),
|
||||
BobState::XmrLocked(state4) => state4.cancel(),
|
||||
BobState::EncSigSent(state4) => state4.cancel(),
|
||||
BobState::CancelTimelockExpired(state6) => state6,
|
||||
@ -157,6 +173,7 @@ pub async fn refund(
|
||||
// We have been punished
|
||||
Ok(ExpiredTimelocks::Punish { .. }) => {
|
||||
let state = BobState::BtcPunished {
|
||||
state: state6.clone(),
|
||||
tx_lock_id: state6.tx_lock_id(),
|
||||
};
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
@ -174,7 +191,7 @@ pub async fn refund(
|
||||
);
|
||||
}
|
||||
Ok(ExpiredTimelocks::Cancel { .. }) => {
|
||||
bail!(bitcoin_publication_err.context("Failed to refund swap even though cancel timelock has expired. This should is unexpected."));
|
||||
bail!(bitcoin_publication_err.context("Failed to refund swap even though cancel timelock has expired. This is unexpected."));
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(bitcoin_publication_err
|
||||
|
@ -69,18 +69,6 @@ where
|
||||
monero_receive_address,
|
||||
tor,
|
||||
} => {
|
||||
let monero_receive_address =
|
||||
monero_address::validate_is_testnet(monero_receive_address, is_testnet)?;
|
||||
let bitcoin_change_address =
|
||||
bitcoin_address::validate_is_testnet(bitcoin_change_address, 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),
|
||||
@ -90,22 +78,55 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let monero_receive_address =
|
||||
monero_address::validate_is_testnet(monero_receive_address, is_testnet)?;
|
||||
let bitcoin_change_address = bitcoin_change_address
|
||||
.map(|address| bitcoin_address::validate_is_testnet(address, is_testnet))
|
||||
.transpose()?;
|
||||
|
||||
let request = Request::new(Method::BuyXmr {
|
||||
seller,
|
||||
bitcoin_change_address,
|
||||
monero_receive_address,
|
||||
swap_id: Uuid::new_v4(),
|
||||
});
|
||||
|
||||
(context, request)
|
||||
}
|
||||
CliCommand::History => {
|
||||
let request = Request::new(Method::History);
|
||||
|
||||
let context =
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None, false)
|
||||
.await?;
|
||||
(context, request)
|
||||
}
|
||||
CliCommand::Logs {
|
||||
logs_dir,
|
||||
redact,
|
||||
swap_id,
|
||||
} => {
|
||||
let request = Request::new(Method::Logs {
|
||||
logs_dir,
|
||||
redact,
|
||||
swap_id,
|
||||
});
|
||||
let context =
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None, false)
|
||||
.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::build(None, None, None, data, is_testnet, debug, json, None, false)
|
||||
.await?;
|
||||
(context, request)
|
||||
}
|
||||
CliCommand::Balance { bitcoin } => {
|
||||
@ -122,6 +143,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -143,6 +165,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
server_address,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -164,6 +187,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -185,6 +209,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -205,6 +230,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -215,8 +241,18 @@ where
|
||||
} => {
|
||||
let request = Request::new(Method::ListSellers { rendezvous_point });
|
||||
|
||||
let context =
|
||||
Context::build(None, None, Some(tor), data, is_testnet, debug, json, None).await?;
|
||||
let context = Context::build(
|
||||
None,
|
||||
None,
|
||||
Some(tor),
|
||||
data,
|
||||
is_testnet,
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
(context, request)
|
||||
}
|
||||
@ -232,6 +268,7 @@ where
|
||||
debug,
|
||||
json,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(context, request)
|
||||
@ -242,7 +279,8 @@ where
|
||||
let request = Request::new(Method::MoneroRecovery { swap_id });
|
||||
|
||||
let context =
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None).await?;
|
||||
Context::build(None, None, None, data, is_testnet, debug, json, None, false)
|
||||
.await?;
|
||||
|
||||
(context, request)
|
||||
}
|
||||
@ -300,10 +338,10 @@ enum CliCommand {
|
||||
|
||||
#[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. If omitted they will be sent to the internal wallet.",
|
||||
parse(try_from_str = bitcoin_address::parse)
|
||||
)]
|
||||
bitcoin_change_address: bitcoin::Address,
|
||||
bitcoin_change_address: Option<bitcoin::Address>,
|
||||
|
||||
#[structopt(flatten)]
|
||||
monero: Monero,
|
||||
@ -319,6 +357,25 @@ enum CliCommand {
|
||||
},
|
||||
/// Show a list of past, ongoing and completed swaps
|
||||
History,
|
||||
/// Output all logging messages that have been issued.
|
||||
Logs {
|
||||
#[structopt(
|
||||
short = "d",
|
||||
help = "Print the logs from this directory instead of the default one."
|
||||
)]
|
||||
logs_dir: Option<PathBuf>,
|
||||
#[structopt(
|
||||
help = "Redact swap-ids, Bitcoin and Monero addresses.",
|
||||
long = "redact"
|
||||
)]
|
||||
redact: bool,
|
||||
#[structopt(
|
||||
long = "swap-id",
|
||||
help = "Filter for logs concerning this swap.",
|
||||
long_help = "This checks whether each logging message contains the swap id. Some messages might be skipped when they don't contain the swap id even though they're relevant."
|
||||
)]
|
||||
swap_id: Option<Uuid>,
|
||||
},
|
||||
#[structopt(about = "Prints the current config")]
|
||||
Config,
|
||||
#[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")]
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::bitcoin::EncryptedSignature;
|
||||
use crate::cli::behaviour::{Behaviour, OutEvent};
|
||||
use crate::monero;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::{Request, Response};
|
||||
use crate::network::encrypted_signature;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::swap_setup::bob::NewSwap;
|
||||
@ -27,6 +28,7 @@ pub struct EventLoop {
|
||||
|
||||
// these streams represents outgoing requests that we have to make
|
||||
quote_requests: bmrng::RequestReceiverStream<(), BidQuote>,
|
||||
cooperative_xmr_redeem_requests: bmrng::RequestReceiverStream<Uuid, Response>,
|
||||
encrypted_signatures: bmrng::RequestReceiverStream<EncryptedSignature, ()>,
|
||||
swap_setup_requests: bmrng::RequestReceiverStream<NewSwap, Result<State2>>,
|
||||
|
||||
@ -36,7 +38,7 @@ pub struct EventLoop {
|
||||
inflight_quote_requests: HashMap<RequestId, bmrng::Responder<BidQuote>>,
|
||||
inflight_encrypted_signature_requests: HashMap<RequestId, bmrng::Responder<()>>,
|
||||
inflight_swap_setup: Option<bmrng::Responder<Result<State2>>>,
|
||||
|
||||
inflight_cooperative_xmr_redeem_requests: HashMap<RequestId, bmrng::Responder<Response>>,
|
||||
/// The sender we will use to relay incoming transfer proofs.
|
||||
transfer_proof: bmrng::RequestSender<monero::TransferProof, ()>,
|
||||
/// The future representing the successful handling of an incoming transfer
|
||||
@ -60,7 +62,7 @@ impl EventLoop {
|
||||
let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
let encrypted_signature = bmrng::channel(1);
|
||||
let quote = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
|
||||
let cooperative_xmr_redeem = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
let event_loop = EventLoop {
|
||||
swap_id,
|
||||
swarm,
|
||||
@ -68,10 +70,12 @@ impl EventLoop {
|
||||
swap_setup_requests: execution_setup.1.into(),
|
||||
transfer_proof: transfer_proof.0,
|
||||
encrypted_signatures: encrypted_signature.1.into(),
|
||||
cooperative_xmr_redeem_requests: cooperative_xmr_redeem.1.into(),
|
||||
quote_requests: quote.1.into(),
|
||||
inflight_quote_requests: HashMap::default(),
|
||||
inflight_swap_setup: None,
|
||||
inflight_encrypted_signature_requests: HashMap::default(),
|
||||
inflight_cooperative_xmr_redeem_requests: HashMap::default(),
|
||||
pending_transfer_proof: OptionFuture::from(None),
|
||||
db,
|
||||
};
|
||||
@ -80,6 +84,7 @@ impl EventLoop {
|
||||
swap_setup: execution_setup.0,
|
||||
transfer_proof: transfer_proof.1,
|
||||
encrypted_signature: encrypted_signature.0,
|
||||
cooperative_xmr_redeem: cooperative_xmr_redeem.0,
|
||||
quote: quote.0,
|
||||
};
|
||||
|
||||
@ -176,6 +181,16 @@ impl EventLoop {
|
||||
let _ = responder.respond(());
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::CooperativeXmrRedeemFulfilled { id, swap_id, s_a }) => {
|
||||
if let Some(responder) = self.inflight_cooperative_xmr_redeem_requests.remove(&id) {
|
||||
let _ = responder.respond(Response::Fullfilled { s_a, swap_id });
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::CooperativeXmrRedeemRejected { id, swap_id, reason }) => {
|
||||
if let Some(responder) = self.inflight_cooperative_xmr_redeem_requests.remove(&id) {
|
||||
let _ = responder.respond(Response::Rejected { reason, swap_id });
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::AllRedialAttemptsExhausted { peer }) if peer == self.alice_peer_id => {
|
||||
tracing::error!("Exhausted all re-dial attempts to Alice");
|
||||
return;
|
||||
@ -234,7 +249,14 @@ impl EventLoop {
|
||||
let _ = self.swarm.behaviour_mut().transfer_proof.send_response(response_channel, ());
|
||||
|
||||
self.pending_transfer_proof = OptionFuture::from(None);
|
||||
}
|
||||
},
|
||||
|
||||
Some((swap_id, responder)) = self.cooperative_xmr_redeem_requests.next().fuse(), if self.is_connected_to_alice() => {
|
||||
let id = self.swarm.behaviour_mut().cooperative_xmr_redeem.send_request(&self.alice_peer_id, Request {
|
||||
swap_id
|
||||
});
|
||||
self.inflight_cooperative_xmr_redeem_requests.insert(id, responder);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,6 +272,7 @@ pub struct EventLoopHandle {
|
||||
transfer_proof: bmrng::RequestReceiver<monero::TransferProof, ()>,
|
||||
encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>,
|
||||
quote: bmrng::RequestSender<(), BidQuote>,
|
||||
cooperative_xmr_redeem: bmrng::RequestSender<Uuid, Response>,
|
||||
}
|
||||
|
||||
impl EventLoopHandle {
|
||||
@ -274,6 +297,9 @@ impl EventLoopHandle {
|
||||
tracing::debug!("Requesting quote");
|
||||
Ok(self.quote.send_receive(()).await?)
|
||||
}
|
||||
pub async fn request_cooperative_xmr_redeem(&mut self, swap_id: Uuid) -> Result<Response> {
|
||||
Ok(self.cooperative_xmr_redeem.send_receive(swap_id).await?)
|
||||
}
|
||||
|
||||
pub async fn send_encrypted_signature(
|
||||
&mut self,
|
||||
|
@ -1,112 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use tracing::subscriber::set_global_default;
|
||||
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, Layer, Registry};
|
||||
|
||||
pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>) -> Result<()> {
|
||||
let level_filter = EnvFilter::try_new("swap=debug")?;
|
||||
let registry = Registry::default().with(level_filter);
|
||||
|
||||
let appender = tracing_appender::rolling::never(dir.as_ref(), "swap-all.log");
|
||||
|
||||
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()))?;
|
||||
}
|
||||
|
||||
tracing::info!("Logging initialized to {}", dir.as_ref().display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct StdErrPrinter<L> {
|
||||
inner: L,
|
||||
level: Level,
|
||||
}
|
||||
|
||||
type StdErrLayer<S, T> =
|
||||
fmt::Layer<S, DefaultFields, Format<fmt::format::Full, T>, fn() -> std::io::Stderr>;
|
||||
|
||||
type StdErrJsonLayer<S, T> =
|
||||
fmt::Layer<S, JsonFields, Format<fmt::format::Json, T>, fn() -> std::io::Stderr>;
|
||||
|
||||
fn debug_terminal_printer<S>() -> StdErrPrinter<StdErrLayer<S, UtcTime<Rfc3339>>> {
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
StdErrPrinter {
|
||||
inner: fmt::layer()
|
||||
.with_ansi(is_terminal)
|
||||
.with_target(false)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_writer(std::io::stderr),
|
||||
level: Level::DEBUG,
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_json_terminal_printer<S>() -> StdErrPrinter<StdErrJsonLayer<S, UtcTime<Rfc3339>>> {
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
StdErrPrinter {
|
||||
inner: fmt::layer()
|
||||
.with_ansi(is_terminal)
|
||||
.with_target(false)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.json()
|
||||
.with_writer(std::io::stderr),
|
||||
level: Level::DEBUG,
|
||||
}
|
||||
}
|
||||
|
||||
fn info_terminal_printer<S>() -> StdErrPrinter<StdErrLayer<S, ()>> {
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
StdErrPrinter {
|
||||
inner: fmt::layer()
|
||||
.with_ansi(is_terminal)
|
||||
.with_target(false)
|
||||
.with_level(false)
|
||||
.without_time()
|
||||
.with_writer(std::io::stderr),
|
||||
level: Level::INFO,
|
||||
}
|
||||
}
|
||||
|
||||
fn info_json_terminal_printer<S>() -> StdErrPrinter<StdErrJsonLayer<S, ()>> {
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
StdErrPrinter {
|
||||
inner: fmt::layer()
|
||||
.with_ansi(is_terminal)
|
||||
.with_target(false)
|
||||
.with_level(false)
|
||||
.without_time()
|
||||
.json()
|
||||
.with_writer(std::io::stderr),
|
||||
level: Level::INFO,
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, S> Layer<S> for StdErrPrinter<L>
|
||||
where
|
||||
L: 'static + Layer<S>,
|
||||
S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
|
||||
{
|
||||
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
|
||||
if self.level.ge(event.metadata().level()) {
|
||||
self.inner.on_event(event, ctx);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
const LATEST_RELEASE_URL: &str = "https://github.com/comit-network/xmr-btc-swap/releases/latest";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Version {
|
||||
Current,
|
||||
Available,
|
||||
}
|
||||
|
||||
/// Check the latest release from GitHub API.
|
||||
pub async fn check_latest_version(current_version: &str) -> anyhow::Result<Version> {
|
||||
let response = reqwest::get(LATEST_RELEASE_URL).await?;
|
||||
let e = "Failed to get latest release.";
|
||||
let download_url = response.url();
|
||||
let segments = download_url.path_segments().ok_or_else(|| anyhow!(e))?;
|
||||
let latest_version = segments.last().ok_or_else(|| anyhow!(e))?;
|
||||
|
||||
let result = if is_latest_version(current_version, latest_version) {
|
||||
Version::Current
|
||||
} else {
|
||||
tracing::warn!(%current_version, %latest_version, %download_url,
|
||||
"You are not on the latest version",
|
||||
);
|
||||
Version::Available
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// todo: naive implementation can be improved using semver
|
||||
fn is_latest_version(current: &str, latest: &str) -> bool {
|
||||
current == latest
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_compares_versions() {
|
||||
assert!(is_latest_version("0.10.2", "0.10.2"));
|
||||
assert!(!is_latest_version("0.10.2", "0.10.3"));
|
||||
assert!(!is_latest_version("0.10.2", "0.11.0"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "For local testing, makes http requests to github."]
|
||||
async fn it_compares_with_github() {
|
||||
let result = check_latest_version("0.11.0").await.unwrap();
|
||||
assert_eq!(result, Version::Available);
|
||||
|
||||
let result = check_latest_version("0.11.1").await.unwrap();
|
||||
assert_eq!(result, Version::Current);
|
||||
}
|
||||
}
|
221
swap/src/common/mod.rs
Normal file
221
swap/src/common/mod.rs
Normal file
@ -0,0 +1,221 @@
|
||||
pub mod tracing_util;
|
||||
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use tokio::{
|
||||
fs::{read_dir, File},
|
||||
io::{AsyncBufReadExt, BufReader},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
const LATEST_RELEASE_URL: &str = "https://github.com/comit-network/xmr-btc-swap/releases/latest";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Version {
|
||||
Current,
|
||||
Available,
|
||||
}
|
||||
|
||||
/// Check the latest release from GitHub API.
|
||||
pub async fn check_latest_version(current_version: &str) -> anyhow::Result<Version> {
|
||||
let response = reqwest::get(LATEST_RELEASE_URL).await?;
|
||||
let e = "Failed to get latest release.";
|
||||
let download_url = response.url();
|
||||
let segments = download_url.path_segments().ok_or_else(|| anyhow!(e))?;
|
||||
let latest_version = segments.last().ok_or_else(|| anyhow!(e))?;
|
||||
|
||||
let result = if is_latest_version(current_version, latest_version) {
|
||||
Version::Current
|
||||
} else {
|
||||
tracing::warn!(%current_version, %latest_version, %download_url,
|
||||
"You are not on the latest version",
|
||||
);
|
||||
Version::Available
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// todo: naive implementation can be improved using semver
|
||||
fn is_latest_version(current: &str, latest: &str) -> bool {
|
||||
current == latest
|
||||
}
|
||||
|
||||
/// helper macro for [`redact`]... eldrich sorcery
|
||||
/// the macro does in essence the following:
|
||||
/// 1. create a static regex automaton for the pattern
|
||||
/// 2. find all matching patterns using regex
|
||||
/// 3. create a placeholder for each distinct matching pattern
|
||||
/// 4. add the placeholder to the hashmap
|
||||
macro_rules! regex_find_placeholders {
|
||||
($pattern:expr, $create_placeholder:expr, $replacements:expr, $input:expr) => {{
|
||||
// compile the regex pattern
|
||||
static REGEX: once_cell::sync::Lazy<regex::Regex> = once_cell::sync::Lazy::new(|| {
|
||||
tracing::debug!("initializing regex");
|
||||
regex::Regex::new($pattern).expect("invalid regex pattern")
|
||||
});
|
||||
|
||||
// keep count of count patterns to generate distinct placeholders
|
||||
let mut counter: usize = 0;
|
||||
|
||||
// for every matched address check whether we already found it
|
||||
// and if we didn't, generate a placeholder for it
|
||||
for address in REGEX.find_iter($input) {
|
||||
if !$replacements.contains_key(address.as_str()) {
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
$replacements.insert(address.as_str().to_owned(), $create_placeholder(counter));
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Print the logs from the specified logs or from the default location
|
||||
/// to the specified path or the terminal.
|
||||
///
|
||||
/// If specified, filter by swap id or redact addresses.
|
||||
pub async fn get_logs(
|
||||
logs_dir: PathBuf,
|
||||
swap_id: Option<Uuid>,
|
||||
redact_addresses: bool,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
tracing::debug!("reading logfiles from {}", logs_dir.display());
|
||||
|
||||
// get all files in the directory
|
||||
let mut log_files = read_dir(&logs_dir).await?;
|
||||
|
||||
let mut log_messages = Vec::new();
|
||||
// when we redact we need to store the placeholder
|
||||
let mut placeholders = HashMap::new();
|
||||
|
||||
// print all lines from every log file. TODO: sort files by date?
|
||||
while let Some(entry) = log_files.next_entry().await? {
|
||||
// get the file path
|
||||
let file_path = entry.path();
|
||||
|
||||
// filter for .log files
|
||||
let file_name = file_path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
if !file_name.ends_with(".log") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// use BufReader to stay easy on memory and then read line by line
|
||||
let buf_reader = BufReader::new(File::open(&file_path).await?);
|
||||
let mut lines = buf_reader.lines();
|
||||
|
||||
// print each line, redacted if the flag is set
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
// if we should filter by swap id, check if the line contains it
|
||||
if let Some(swap_id) = swap_id {
|
||||
// we only want lines which contain the swap id
|
||||
if !line.contains(&swap_id.to_string()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// redact if necessary
|
||||
let line = if redact_addresses {
|
||||
redact_with(&line, &mut placeholders)
|
||||
} else {
|
||||
line
|
||||
};
|
||||
// save redacted message
|
||||
log_messages.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(log_messages)
|
||||
}
|
||||
|
||||
/// Redact logs, etc. by replacing Bitcoin and Monero addresses
|
||||
/// with generic placeholders.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use swap::common::redact;
|
||||
///
|
||||
/// let redacted = redact("a9165a1e-d26d-4b56-bf6d-ca9658825c44");
|
||||
/// assert_eq!(redacted, "<swap_id_0>");
|
||||
/// ```
|
||||
pub fn redact(input: &str) -> String {
|
||||
let mut replacements = HashMap::new();
|
||||
redact_with(input, &mut replacements)
|
||||
}
|
||||
|
||||
/// Same as [`redact`] but retrieves palceholders from and stores them
|
||||
/// in a specified hashmap.
|
||||
pub fn redact_with(input: &str, replacements: &mut HashMap<String, String>) -> String {
|
||||
// TODO: verify regex patterns
|
||||
const MONERO_ADDR_REGEX: &str = r#"[48][1-9A-HJ-NP-Za-km-z]{94}"#;
|
||||
const BITCOIN_ADDR_REGEX: &str = r#"\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b"#;
|
||||
// Both XMR and BTC transactions have
|
||||
// a 64 bit hex id so they aren't distinguishible
|
||||
const TX_ID_REGEX: &str = r#"\b[a-fA-F0-9]{64}\b"#;
|
||||
const SWAP_ID_REGEX: &str =
|
||||
r#"\b[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}\b"#;
|
||||
|
||||
// use the macro to find all addresses and generate placeholders
|
||||
// has to be a macro in order to create the regex automata only once.
|
||||
regex_find_placeholders!(
|
||||
MONERO_ADDR_REGEX,
|
||||
|count| format!("<monero_address_{count}>"),
|
||||
replacements,
|
||||
input
|
||||
);
|
||||
regex_find_placeholders!(
|
||||
BITCOIN_ADDR_REGEX,
|
||||
|count| format!("<bitcoin_address_{count}>"),
|
||||
replacements,
|
||||
input
|
||||
);
|
||||
regex_find_placeholders!(
|
||||
TX_ID_REGEX,
|
||||
|count| format!("<tx_id_{count}>"),
|
||||
replacements,
|
||||
input
|
||||
);
|
||||
regex_find_placeholders!(
|
||||
SWAP_ID_REGEX,
|
||||
|count| format!("<swap_id_{count}>"),
|
||||
replacements,
|
||||
input
|
||||
);
|
||||
|
||||
// allocate string variable to operate on
|
||||
let mut redacted = input.to_owned();
|
||||
|
||||
// Finally we go through the input string and replace each occurance of an
|
||||
// address we want to redact with the corresponding placeholder
|
||||
for (address, placeholder) in replacements.iter() {
|
||||
redacted = redacted.replace(address, placeholder);
|
||||
}
|
||||
|
||||
redacted
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_compares_versions() {
|
||||
assert!(is_latest_version("0.10.2", "0.10.2"));
|
||||
assert!(!is_latest_version("0.10.2", "0.10.3"));
|
||||
assert!(!is_latest_version("0.10.2", "0.11.0"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "For local testing, makes http requests to github."]
|
||||
async fn it_compares_with_github() {
|
||||
let result = check_latest_version("0.11.0").await.unwrap();
|
||||
assert_eq!(result, Version::Available);
|
||||
|
||||
let result = check_latest_version("0.11.1").await.unwrap();
|
||||
assert_eq!(result, Version::Current);
|
||||
}
|
||||
}
|
64
swap/src/common/tracing_util.rs
Normal file
64
swap/src/common/tracing_util.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing_subscriber::filter::{Directive, LevelFilter};
|
||||
use tracing_subscriber::fmt::time::UtcTime;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{fmt, EnvFilter, Layer};
|
||||
|
||||
/// Output formats for logging messages.
|
||||
pub enum Format {
|
||||
/// Standard, human readable format.
|
||||
Raw,
|
||||
/// JSON, machine readable format.
|
||||
Json,
|
||||
}
|
||||
|
||||
/// Initialize tracing and enable logging messages according to these options.
|
||||
/// Besides printing to `stdout`, this will append to a log file.
|
||||
/// Said file will contain JSON-formatted logs of all levels,
|
||||
/// disregarding the arguments to this function.
|
||||
pub fn init(level_filter: LevelFilter, format: Format, dir: impl AsRef<Path>) -> Result<()> {
|
||||
let env_filter = EnvFilter::from_default_env()
|
||||
.add_directive(Directive::from_str(&format!("asb={}", &level_filter))?)
|
||||
.add_directive(Directive::from_str(&format!("swap={}", &level_filter))?);
|
||||
|
||||
// file logger will always write in JSON format and with timestamps
|
||||
let file_appender = tracing_appender::rolling::never(&dir, "swap-all.log");
|
||||
|
||||
let file_layer = fmt::layer()
|
||||
.with_writer(file_appender)
|
||||
.with_ansi(false)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false)
|
||||
.json()
|
||||
.with_filter(env_filter);
|
||||
|
||||
// terminal logger
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
let terminal_layer = fmt::layer()
|
||||
.with_writer(std::io::stdout)
|
||||
.with_ansi(is_terminal)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false);
|
||||
|
||||
// combine the layers and start logging, format with json if specified
|
||||
if let Format::Json = format {
|
||||
tracing_subscriber::registry()
|
||||
.with(file_layer)
|
||||
.with(terminal_layer.json().with_filter(level_filter))
|
||||
.init();
|
||||
} else {
|
||||
tracing_subscriber::registry()
|
||||
.with(file_layer)
|
||||
.with(terminal_layer.with_filter(level_filter))
|
||||
.init();
|
||||
}
|
||||
|
||||
// now we can use the tracing macros to log messages
|
||||
tracing::info!(%level_filter, logs_dir=%dir.as_ref().display(), "Initialized tracing");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -83,16 +83,25 @@ impl Swap {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_db(sqlite_path: impl AsRef<Path>) -> Result<Arc<dyn Database + Send + Sync>> {
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Hash, PartialEq)]
|
||||
pub enum AccessMode {
|
||||
ReadWrite,
|
||||
ReadOnly,
|
||||
}
|
||||
|
||||
pub async fn open_db(
|
||||
sqlite_path: impl AsRef<Path>,
|
||||
access_mode: AccessMode,
|
||||
) -> Result<Arc<dyn Database + Send + Sync>> {
|
||||
if sqlite_path.as_ref().exists() {
|
||||
tracing::debug!("Using existing sqlite database.");
|
||||
let sqlite = SqliteDatabase::open(sqlite_path).await?;
|
||||
let sqlite = SqliteDatabase::open(sqlite_path, access_mode).await?;
|
||||
Ok(Arc::new(sqlite))
|
||||
} else {
|
||||
tracing::debug!("Creating and using new sqlite database.");
|
||||
ensure_directory_exists(sqlite_path.as_ref())?;
|
||||
tokio::fs::File::create(&sqlite_path).await?;
|
||||
let sqlite = SqliteDatabase::open(sqlite_path).await?;
|
||||
let sqlite = SqliteDatabase::open(sqlite_path, access_mode).await?;
|
||||
Ok(Arc::new(sqlite))
|
||||
}
|
||||
}
|
||||
|
@ -70,12 +70,12 @@ pub enum Alice {
|
||||
Done(AliceEndState),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum AliceEndState {
|
||||
SafelyAborted,
|
||||
BtcRedeemed,
|
||||
XmrRefunded,
|
||||
BtcPunished,
|
||||
BtcPunished { state3: alice::State3 },
|
||||
}
|
||||
|
||||
impl From<AliceState> for Alice {
|
||||
@ -173,7 +173,9 @@ impl From<AliceState> for Alice {
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
|
||||
AliceState::BtcPunished { state3 } => Alice::Done(AliceEndState::BtcPunished {
|
||||
state3: state3.as_ref().clone(),
|
||||
}),
|
||||
AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted),
|
||||
}
|
||||
}
|
||||
@ -277,7 +279,9 @@ impl From<Alice> for AliceState {
|
||||
AliceEndState::SafelyAborted => AliceState::SafelyAborted,
|
||||
AliceEndState::BtcRedeemed => AliceState::BtcRedeemed,
|
||||
AliceEndState::XmrRefunded => AliceState::XmrRefunded,
|
||||
AliceEndState::BtcPunished => AliceState::BtcPunished,
|
||||
AliceEndState::BtcPunished { state3 } => AliceState::BtcPunished {
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,10 @@ pub enum Bob {
|
||||
EncSigSent {
|
||||
state4: bob::State4,
|
||||
},
|
||||
BtcPunished {
|
||||
state: bob::State6,
|
||||
tx_lock_id: bitcoin::Txid,
|
||||
},
|
||||
BtcRedeemed(bob::State5),
|
||||
CancelTimelockExpired(bob::State6),
|
||||
BtcCancelled(bob::State6),
|
||||
@ -44,7 +48,6 @@ pub enum BobEndState {
|
||||
SafelyAborted,
|
||||
XmrRedeemed { tx_lock_id: bitcoin::Txid },
|
||||
BtcRefunded(Box<bob::State6>),
|
||||
BtcPunished { tx_lock_id: bitcoin::Txid },
|
||||
}
|
||||
|
||||
impl From<BobState> for Bob {
|
||||
@ -79,13 +82,11 @@ impl From<BobState> for Bob {
|
||||
BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5),
|
||||
BobState::CancelTimelockExpired(state6) => Bob::CancelTimelockExpired(state6),
|
||||
BobState::BtcCancelled(state6) => Bob::BtcCancelled(state6),
|
||||
BobState::BtcPunished { state, tx_lock_id } => Bob::BtcPunished { state, tx_lock_id },
|
||||
BobState::BtcRefunded(state6) => Bob::Done(BobEndState::BtcRefunded(Box::new(state6))),
|
||||
BobState::XmrRedeemed { tx_lock_id } => {
|
||||
Bob::Done(BobEndState::XmrRedeemed { tx_lock_id })
|
||||
}
|
||||
BobState::BtcPunished { tx_lock_id } => {
|
||||
Bob::Done(BobEndState::BtcPunished { tx_lock_id })
|
||||
}
|
||||
BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted),
|
||||
}
|
||||
}
|
||||
@ -123,11 +124,11 @@ impl From<Bob> for BobState {
|
||||
Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5),
|
||||
Bob::CancelTimelockExpired(state6) => BobState::CancelTimelockExpired(state6),
|
||||
Bob::BtcCancelled(state6) => BobState::BtcCancelled(state6),
|
||||
Bob::BtcPunished { state, tx_lock_id } => BobState::BtcPunished { state, tx_lock_id },
|
||||
Bob::Done(end_state) => match end_state {
|
||||
BobEndState::SafelyAborted => BobState::SafelyAborted,
|
||||
BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id },
|
||||
BobEndState::BtcRefunded(state6) => BobState::BtcRefunded(*state6),
|
||||
BobEndState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id },
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -148,6 +149,7 @@ impl fmt::Display for Bob {
|
||||
Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"),
|
||||
Bob::Done(end_state) => write!(f, "Done: {}", end_state),
|
||||
Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"),
|
||||
Bob::BtcPunished { .. } => f.write_str("Bitcoin punished"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,27 +4,38 @@ use crate::protocol::{Database, State};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use sqlx::sqlite::Sqlite;
|
||||
use sqlx::{Pool, SqlitePool};
|
||||
use sqlx::sqlite::{Sqlite, SqliteConnectOptions};
|
||||
use sqlx::{ConnectOptions, Pool, SqlitePool};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::AccessMode;
|
||||
|
||||
pub struct SqliteDatabase {
|
||||
pool: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl SqliteDatabase {
|
||||
pub async fn open(path: impl AsRef<Path>) -> Result<Self>
|
||||
pub async fn open(path: impl AsRef<Path>, access_mode: AccessMode) -> Result<Self>
|
||||
where
|
||||
Self: std::marker::Sized,
|
||||
{
|
||||
let read_only = matches!(access_mode, AccessMode::ReadOnly);
|
||||
|
||||
let path_str = format!("sqlite:{}", path.as_ref().display());
|
||||
let pool = SqlitePool::connect(&path_str).await?;
|
||||
let mut options = SqliteConnectOptions::from_str(&path_str)?.read_only(read_only);
|
||||
options.disable_statement_logging();
|
||||
|
||||
let pool = SqlitePool::connect_with(options).await?;
|
||||
let mut sqlite = Self { pool };
|
||||
sqlite.run_migrations().await?;
|
||||
|
||||
if !read_only {
|
||||
sqlite.run_migrations().await?;
|
||||
}
|
||||
|
||||
Ok(sqlite)
|
||||
}
|
||||
|
||||
@ -417,9 +428,8 @@ mod tests {
|
||||
let db = setup_test_db().await.unwrap();
|
||||
|
||||
let state_1 = State::Alice(AliceState::BtcRedeemed);
|
||||
let state_2 = State::Alice(AliceState::BtcPunished);
|
||||
let state_3 = State::Alice(AliceState::SafelyAborted);
|
||||
let state_4 = State::Bob(BobState::SafelyAborted);
|
||||
let state_2 = State::Alice(AliceState::SafelyAborted);
|
||||
let state_3 = State::Bob(BobState::SafelyAborted);
|
||||
let swap_id_1 = Uuid::new_v4();
|
||||
let swap_id_2 = Uuid::new_v4();
|
||||
|
||||
@ -429,10 +439,7 @@ mod tests {
|
||||
db.insert_latest_state(swap_id_1, state_2.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
db.insert_latest_state(swap_id_1, state_3.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
db.insert_latest_state(swap_id_2, state_4.clone())
|
||||
db.insert_latest_state(swap_id_2, state_3.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -440,11 +447,10 @@ mod tests {
|
||||
|
||||
assert_eq!(latest_loaded.len(), 2);
|
||||
|
||||
assert!(latest_loaded.contains(&(swap_id_1, state_3)));
|
||||
assert!(latest_loaded.contains(&(swap_id_2, state_4)));
|
||||
assert!(latest_loaded.contains(&(swap_id_1, state_2)));
|
||||
assert!(latest_loaded.contains(&(swap_id_2, state_3)));
|
||||
|
||||
assert!(!latest_loaded.contains(&(swap_id_1, state_1)));
|
||||
assert!(!latest_loaded.contains(&(swap_id_1, state_2)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -509,7 +515,7 @@ mod tests {
|
||||
// file has to exist in order to connect with sqlite
|
||||
File::create(temp_db.clone()).unwrap();
|
||||
|
||||
let db = SqliteDatabase::open(temp_db).await?;
|
||||
let db = SqliteDatabase::open(temp_db, AccessMode::ReadWrite).await?;
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
@ -142,6 +142,14 @@ impl Amount {
|
||||
Decimal::from(self.as_piconero())
|
||||
}
|
||||
|
||||
pub fn as_xmr(&self) -> Decimal {
|
||||
let mut decimal = Decimal::from(self.0);
|
||||
decimal
|
||||
.set_scale(12)
|
||||
.expect("12 is smaller than max precision of 28");
|
||||
decimal
|
||||
}
|
||||
|
||||
fn from_decimal(amount: Decimal) -> Result<Self> {
|
||||
let piconeros_dec =
|
||||
amount.mul(Decimal::from_u64(PICONERO_OFFSET).expect("constant to fit into u64"));
|
||||
@ -184,11 +192,8 @@ impl From<Amount> for u64 {
|
||||
|
||||
impl fmt::Display for Amount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut decimal = Decimal::from(self.0);
|
||||
decimal
|
||||
.set_scale(12)
|
||||
.expect("12 is smaller than max precision of 28");
|
||||
write!(f, "{} XMR", decimal)
|
||||
let xmr_value = self.as_xmr();
|
||||
write!(f, "{} XMR", xmr_value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
mod impl_from_rr_event;
|
||||
|
||||
pub mod cbor_request_response;
|
||||
pub mod cooperative_xmr_redeem_after_punish;
|
||||
pub mod encrypted_signature;
|
||||
pub mod json_pull_codec;
|
||||
pub mod quote;
|
||||
|
113
swap/src/network/cooperative_xmr_redeem_after_punish.rs
Normal file
113
swap/src/network/cooperative_xmr_redeem_after_punish.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use crate::monero::Scalar;
|
||||
use crate::network::cbor_request_response::CborCodec;
|
||||
use crate::{asb, cli};
|
||||
use libp2p::core::ProtocolName;
|
||||
use libp2p::request_response::{
|
||||
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
||||
RequestResponseMessage,
|
||||
};
|
||||
use libp2p::PeerId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
const PROTOCOL: &str = "/comit/xmr/btc/cooperative_xmr_redeem_after_punish/1.0.0";
|
||||
type OutEvent = RequestResponseEvent<Request, Response>;
|
||||
type Message = RequestResponseMessage<Request, Response>;
|
||||
|
||||
pub type Behaviour = RequestResponse<CborCodec<CooperativeXmrRedeemProtocol, Request, Response>>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct CooperativeXmrRedeemProtocol;
|
||||
|
||||
impl ProtocolName for CooperativeXmrRedeemProtocol {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
PROTOCOL.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, Serialize, Deserialize)]
|
||||
pub enum CooperativeXmrRedeemRejectReason {
|
||||
#[error("Alice does not have a record of the swap")]
|
||||
UnknownSwap,
|
||||
#[error("Alice rejected the request because it deemed it malicious")]
|
||||
MaliciousRequest,
|
||||
#[error("Alice is in a state where a cooperative redeem is not possible")]
|
||||
SwapInvalidState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Request {
|
||||
pub swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Response {
|
||||
Fullfilled {
|
||||
swap_id: Uuid,
|
||||
s_a: Scalar,
|
||||
},
|
||||
Rejected {
|
||||
swap_id: Uuid,
|
||||
reason: CooperativeXmrRedeemRejectReason,
|
||||
},
|
||||
}
|
||||
pub fn alice() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(CooperativeXmrRedeemProtocol, ProtocolSupport::Inbound)],
|
||||
RequestResponseConfig::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bob() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(CooperativeXmrRedeemProtocol, ProtocolSupport::Outbound)],
|
||||
RequestResponseConfig::default(),
|
||||
)
|
||||
}
|
||||
|
||||
impl From<(PeerId, Message)> for asb::OutEvent {
|
||||
fn from((peer, message): (PeerId, Message)) -> Self {
|
||||
match message {
|
||||
Message::Request {
|
||||
request, channel, ..
|
||||
} => Self::CooperativeXmrRedeemRequested {
|
||||
swap_id: request.swap_id,
|
||||
channel,
|
||||
peer,
|
||||
},
|
||||
Message::Response { .. } => Self::unexpected_response(peer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL);
|
||||
|
||||
impl From<(PeerId, Message)> for cli::OutEvent {
|
||||
fn from((peer, message): (PeerId, Message)) -> Self {
|
||||
match message {
|
||||
Message::Request { .. } => Self::unexpected_request(peer),
|
||||
Message::Response {
|
||||
response,
|
||||
request_id,
|
||||
} => match response {
|
||||
Response::Fullfilled { swap_id, s_a } => Self::CooperativeXmrRedeemFulfilled {
|
||||
id: request_id,
|
||||
swap_id,
|
||||
s_a,
|
||||
},
|
||||
Response::Rejected {
|
||||
swap_id,
|
||||
reason: error,
|
||||
} => Self::CooperativeXmrRedeemRejected {
|
||||
id: request_id,
|
||||
swap_id,
|
||||
reason: error,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::impl_from_rr_event!(OutEvent, cli::OutEvent, PROTOCOL);
|
@ -74,7 +74,9 @@ pub enum AliceState {
|
||||
transfer_proof: TransferProof,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
BtcPunished,
|
||||
BtcPunished {
|
||||
state3: Box<State3>,
|
||||
},
|
||||
SafelyAborted,
|
||||
}
|
||||
|
||||
@ -98,7 +100,7 @@ impl fmt::Display for AliceState {
|
||||
AliceState::BtcRedeemed => write!(f, "btc is redeemed"),
|
||||
AliceState::BtcCancelled { .. } => write!(f, "btc is cancelled"),
|
||||
AliceState::BtcRefunded { .. } => write!(f, "btc is refunded"),
|
||||
AliceState::BtcPunished => write!(f, "btc is punished"),
|
||||
AliceState::BtcPunished { .. } => write!(f, "btc is punished"),
|
||||
AliceState::SafelyAborted => write!(f, "safely aborted"),
|
||||
AliceState::BtcPunishable { .. } => write!(f, "btc is punishable"),
|
||||
AliceState::XmrRefunded => write!(f, "xmr is refunded"),
|
||||
@ -377,13 +379,13 @@ impl State2 {
|
||||
pub struct State3 {
|
||||
a: bitcoin::SecretKey,
|
||||
B: bitcoin::PublicKey,
|
||||
s_a: monero::Scalar,
|
||||
pub s_a: monero::Scalar,
|
||||
S_b_monero: monero::PublicKey,
|
||||
S_b_bitcoin: bitcoin::PublicKey,
|
||||
pub v: monero::PrivateViewKey,
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
btc: bitcoin::Amount,
|
||||
xmr: monero::Amount,
|
||||
pub btc: bitcoin::Amount,
|
||||
pub xmr: monero::Amount,
|
||||
pub cancel_timelock: CancelTimelock,
|
||||
pub punish_timelock: PunishTimelock,
|
||||
refund_address: bitcoin::Address,
|
||||
|
@ -1,11 +1,14 @@
|
||||
//! Run an XMR/BTC swap in the role of Alice.
|
||||
//! Alice holds XMR and wishes receive BTC.
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::asb::{EventLoopHandle, LatestRate};
|
||||
use crate::bitcoin::ExpiredTimelocks;
|
||||
use crate::env::Config;
|
||||
use crate::protocol::alice::{AliceState, Swap};
|
||||
use crate::{bitcoin, monero};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use backoff::ExponentialBackoffBuilder;
|
||||
use tokio::select;
|
||||
use tokio::time::timeout;
|
||||
use uuid::Uuid;
|
||||
@ -111,23 +114,63 @@ where
|
||||
}
|
||||
}
|
||||
AliceState::BtcLocked { state3 } => {
|
||||
match state3.expired_timelocks(bitcoin_wallet).await? {
|
||||
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?;
|
||||
// We retry to lock the Monero wallet until we succeed or until the cancel timelock expires.
|
||||
//
|
||||
// This is necessary because the monero-wallet-rpc can sometimes error out due to various reasons, such as
|
||||
// - no connection to the daemon
|
||||
// - "failed to get output distribution"
|
||||
// See https://github.com/comit-network/xmr-btc-swap/issues/1726
|
||||
let backoff = ExponentialBackoffBuilder::new()
|
||||
.with_initial_interval(Duration::from_secs(5))
|
||||
.with_max_interval(Duration::from_secs(60 * 3))
|
||||
.with_max_elapsed_time(None)
|
||||
.build();
|
||||
|
||||
let transfer_proof = monero_wallet
|
||||
.transfer(state3.lock_xmr_transfer_request())
|
||||
.await?;
|
||||
let result = backoff::future::retry_notify(
|
||||
backoff,
|
||||
|| async {
|
||||
match state3.expired_timelocks(bitcoin_wallet).await {
|
||||
Ok(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
|
||||
.map_err(backoff::Error::transient)?;
|
||||
|
||||
let transfer_proof = monero_wallet
|
||||
.transfer(state3.lock_xmr_transfer_request())
|
||||
.await
|
||||
.map_err(backoff::Error::transient)?;
|
||||
|
||||
Ok(Some((monero_wallet_restore_blockheight, transfer_proof)))
|
||||
}
|
||||
Ok(_) => Ok(None),
|
||||
Err(e) => Err(backoff::Error::transient(e)),
|
||||
}
|
||||
},
|
||||
|err, delay: Duration| {
|
||||
tracing::warn!(
|
||||
%err,
|
||||
delay_secs = delay.as_secs(),
|
||||
"Failed to lock XMR. We will retry after a delay"
|
||||
);
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Some((monero_wallet_restore_blockheight, transfer_proof))) => {
|
||||
AliceState::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
_ => AliceState::SafelyAborted,
|
||||
Ok(None) => AliceState::SafelyAborted,
|
||||
Err(e) => {
|
||||
unreachable!("We should retry forever until the cancel timelock expires. But we got an error: {:#}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
AliceState::XmrLockTransactionSent {
|
||||
@ -362,7 +405,7 @@ where
|
||||
let punish = state3.punish_btc(bitcoin_wallet).await;
|
||||
|
||||
match punish {
|
||||
Ok(_) => AliceState::BtcPunished,
|
||||
Ok(_) => AliceState::BtcPunished { state3 },
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to publish punish transaction: {:#}", error);
|
||||
|
||||
@ -392,17 +435,17 @@ where
|
||||
}
|
||||
AliceState::XmrRefunded => AliceState::XmrRefunded,
|
||||
AliceState::BtcRedeemed => AliceState::BtcRedeemed,
|
||||
AliceState::BtcPunished => AliceState::BtcPunished,
|
||||
AliceState::BtcPunished { state3 } => AliceState::BtcPunished { state3 },
|
||||
AliceState::SafelyAborted => AliceState::SafelyAborted,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_complete(state: &AliceState) -> bool {
|
||||
pub fn is_complete(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::XmrRefunded
|
||||
| AliceState::BtcRedeemed
|
||||
| AliceState::BtcPunished
|
||||
| AliceState::BtcPunished { .. }
|
||||
| AliceState::SafelyAborted
|
||||
)
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ pub enum BobState {
|
||||
tx_lock_id: bitcoin::Txid,
|
||||
},
|
||||
BtcPunished {
|
||||
state: State6,
|
||||
tx_lock_id: bitcoin::Txid,
|
||||
},
|
||||
SafelyAborted,
|
||||
@ -368,7 +369,7 @@ pub struct State3 {
|
||||
S_a_monero: monero::PublicKey,
|
||||
S_a_bitcoin: bitcoin::PublicKey,
|
||||
v: monero::PrivateViewKey,
|
||||
xmr: monero::Amount,
|
||||
pub xmr: monero::Amount,
|
||||
pub cancel_timelock: CancelTimelock,
|
||||
punish_timelock: PunishTimelock,
|
||||
refund_address: bitcoin::Address,
|
||||
@ -421,11 +422,13 @@ impl State3 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancel(&self) -> State6 {
|
||||
pub fn cancel(&self, monero_wallet_restore_blockheight: BlockHeight) -> State6 {
|
||||
State6 {
|
||||
A: self.A,
|
||||
b: self.b.clone(),
|
||||
s_b: self.s_b,
|
||||
v: self.v,
|
||||
monero_wallet_restore_blockheight,
|
||||
cancel_timelock: self.cancel_timelock,
|
||||
punish_timelock: self.punish_timelock,
|
||||
refund_address: self.refund_address.clone(),
|
||||
@ -463,6 +466,19 @@ impl State3 {
|
||||
tx_cancel_status,
|
||||
))
|
||||
}
|
||||
pub fn attempt_cooperative_redeem(
|
||||
&self,
|
||||
s_a: monero::PrivateKey,
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
) -> State5 {
|
||||
State5 {
|
||||
s_a,
|
||||
s_b: self.s_b,
|
||||
v: self.v,
|
||||
tx_lock: self.tx_lock.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
@ -571,6 +587,8 @@ impl State4 {
|
||||
A: self.A,
|
||||
b: self.b,
|
||||
s_b: self.s_b,
|
||||
v: self.v,
|
||||
monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight,
|
||||
cancel_timelock: self.cancel_timelock,
|
||||
punish_timelock: self.punish_timelock,
|
||||
refund_address: self.refund_address,
|
||||
@ -604,6 +622,43 @@ impl State5 {
|
||||
pub fn tx_lock_id(&self) -> bitcoin::Txid {
|
||||
self.tx_lock.txid()
|
||||
}
|
||||
pub async fn redeem_xmr(
|
||||
&self,
|
||||
monero_wallet: &monero::Wallet,
|
||||
wallet_file_name: std::string::String,
|
||||
monero_receive_address: monero::Address,
|
||||
) -> Result<()> {
|
||||
let (spend_key, view_key) = self.xmr_keys();
|
||||
|
||||
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(),
|
||||
spend_key,
|
||||
view_key,
|
||||
self.monero_wallet_restore_blockheight,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// In case we failed to refresh/sweep, when resuming the wallet might already
|
||||
// exist! This is a very unlikely scenario, but if we don't take care of it we
|
||||
// might not be able to ever transfer the Monero.
|
||||
tracing::warn!("Failed to generate monero wallet from keys: {:#}", e);
|
||||
tracing::info!(%wallet_file_name,
|
||||
"Falling back to trying to open the wallet if it already exists",
|
||||
);
|
||||
monero_wallet.open(wallet_file_name).await?;
|
||||
}
|
||||
|
||||
// Ensure that the generated wallet is synced so we have a proper balance
|
||||
monero_wallet.refresh(20).await?;
|
||||
// Sweep (transfer all funds) to the given address
|
||||
let tx_hashes = monero_wallet.sweep_all(monero_receive_address).await?;
|
||||
for tx_hash in tx_hashes {
|
||||
tracing::info!(%monero_receive_address, txid=%tx_hash.0, "Successfully transferred XMR to wallet");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
@ -611,6 +666,8 @@ pub struct State6 {
|
||||
A: bitcoin::PublicKey,
|
||||
b: bitcoin::SecretKey,
|
||||
s_b: monero::Scalar,
|
||||
v: monero::PrivateViewKey,
|
||||
pub monero_wallet_restore_blockheight: BlockHeight,
|
||||
cancel_timelock: CancelTimelock,
|
||||
punish_timelock: PunishTimelock,
|
||||
refund_address: bitcoin::Address,
|
||||
@ -706,4 +763,13 @@ impl State6 {
|
||||
pub fn tx_lock_id(&self) -> bitcoin::Txid {
|
||||
self.tx_lock.txid()
|
||||
}
|
||||
pub fn attempt_cooperative_redeem(&self, s_a: monero::PrivateKey) -> State5 {
|
||||
State5 {
|
||||
s_a,
|
||||
s_b: self.s_b,
|
||||
v: self.v,
|
||||
tx_lock: self.tx_lock.clone(),
|
||||
monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund};
|
||||
use crate::cli::EventLoopHandle;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, Rejected};
|
||||
use crate::network::swap_setup::bob::NewSwap;
|
||||
use crate::protocol::bob::state::*;
|
||||
use crate::protocol::{bob, Database};
|
||||
@ -12,13 +13,21 @@ use uuid::Uuid;
|
||||
pub fn is_complete(state: &BobState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
BobState::BtcRefunded(..)
|
||||
| BobState::XmrRedeemed { .. }
|
||||
| BobState::BtcPunished { .. }
|
||||
| BobState::SafelyAborted
|
||||
BobState::BtcRefunded(..) | BobState::XmrRedeemed { .. } | BobState::SafelyAborted
|
||||
)
|
||||
}
|
||||
|
||||
// Identifies states that should be run at most once before exiting.
|
||||
// This is used to prevent infinite retry loops while still allowing manual resumption.
|
||||
//
|
||||
// Currently, this applies to the BtcPunished state:
|
||||
// - We want to attempt recovery via cooperative XMR redeem once.
|
||||
// - If unsuccessful, we exit to avoid an infinite retry loop.
|
||||
// - The swap can still be manually resumed later and retried if desired.
|
||||
pub fn is_run_at_most_once(state: &BobState) -> bool {
|
||||
matches!(state, BobState::BtcPunished { .. })
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run(swap: bob::Swap) -> Result<BobState> {
|
||||
run_until(swap, is_complete).await
|
||||
@ -28,10 +37,10 @@ pub async fn run_until(
|
||||
mut swap: bob::Swap,
|
||||
is_target_state: fn(&BobState) -> bool,
|
||||
) -> Result<BobState> {
|
||||
let mut current_state = swap.state;
|
||||
let mut current_state = swap.state.clone();
|
||||
|
||||
while !is_target_state(¤t_state) {
|
||||
current_state = next_state(
|
||||
let next_state = next_state(
|
||||
swap.id,
|
||||
current_state.clone(),
|
||||
&mut swap.event_loop_handle,
|
||||
@ -43,8 +52,14 @@ pub async fn run_until(
|
||||
.await?;
|
||||
|
||||
swap.db
|
||||
.insert_latest_state(swap.id, current_state.clone().into())
|
||||
.insert_latest_state(swap.id, next_state.clone().into())
|
||||
.await?;
|
||||
|
||||
if is_run_at_most_once(¤t_state) && next_state == current_state {
|
||||
break;
|
||||
}
|
||||
|
||||
current_state = next_state;
|
||||
}
|
||||
|
||||
Ok(current_state)
|
||||
@ -159,12 +174,12 @@ async fn next_state(
|
||||
result?;
|
||||
tracing::info!("Alice took too long to lock Monero, cancelling the swap");
|
||||
|
||||
let state4 = state3.cancel();
|
||||
let state4 = state3.cancel(monero_wallet_restore_blockheight);
|
||||
BobState::CancelTimelockExpired(state4)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let state4 = state3.cancel();
|
||||
let state4 = state3.cancel(monero_wallet_restore_blockheight);
|
||||
BobState::CancelTimelockExpired(state4)
|
||||
}
|
||||
}
|
||||
@ -188,17 +203,17 @@ async fn next_state(
|
||||
|
||||
tx_lock_status.wait_until_confirmed_with(state.cancel_timelock).await?;
|
||||
|
||||
BobState::CancelTimelockExpired(state.cancel())
|
||||
BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight))
|
||||
},
|
||||
}
|
||||
}
|
||||
result = tx_lock_status.wait_until_confirmed_with(state.cancel_timelock) => {
|
||||
result?;
|
||||
BobState::CancelTimelockExpired(state.cancel())
|
||||
BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BobState::CancelTimelockExpired(state.cancel())
|
||||
BobState::CancelTimelockExpired(state.cancel(monero_wallet_restore_blockheight))
|
||||
}
|
||||
}
|
||||
BobState::XmrLocked(state) => {
|
||||
@ -257,39 +272,9 @@ async fn next_state(
|
||||
}
|
||||
}
|
||||
BobState::BtcRedeemed(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(),
|
||||
spend_key,
|
||||
view_key,
|
||||
state.monero_wallet_restore_blockheight,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// In case we failed to refresh/sweep, when resuming the wallet might already
|
||||
// exist! This is a very unlikely scenario, but if we don't take care of it we
|
||||
// might not be able to ever transfer the Monero.
|
||||
tracing::warn!("Failed to generate monero wallet from keys: {:#}", e);
|
||||
tracing::info!(%wallet_file_name,
|
||||
"Falling back to trying to open the wallet if it already exists",
|
||||
);
|
||||
monero_wallet.open(wallet_file_name).await?;
|
||||
}
|
||||
|
||||
// Ensure that the generated wallet is synced so we have a proper balance
|
||||
monero_wallet.refresh(20).await?;
|
||||
// Sweep (transfer all funds) to the given address
|
||||
let tx_hashes = monero_wallet.sweep_all(monero_receive_address).await?;
|
||||
|
||||
for tx_hash in tx_hashes {
|
||||
tracing::info!(%monero_receive_address, txid=%tx_hash.0, "Successfully transferred XMR to wallet");
|
||||
}
|
||||
state
|
||||
.redeem_xmr(monero_wallet, swap_id.to_string(), monero_receive_address)
|
||||
.await?;
|
||||
|
||||
BobState::XmrRedeemed {
|
||||
tx_lock_id: state.tx_lock_id(),
|
||||
@ -318,12 +303,58 @@ async fn next_state(
|
||||
tracing::info!("You have been punished for not refunding in time");
|
||||
BobState::BtcPunished {
|
||||
tx_lock_id: state.tx_lock_id(),
|
||||
state,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BobState::BtcRefunded(state4) => BobState::BtcRefunded(state4),
|
||||
BobState::BtcPunished { tx_lock_id } => BobState::BtcPunished { tx_lock_id },
|
||||
BobState::BtcPunished { state, tx_lock_id } => {
|
||||
tracing::info!("Attempting to cooperatively redeem XMR after being punished");
|
||||
let response = event_loop_handle
|
||||
.request_cooperative_xmr_redeem(swap_id)
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(Fullfilled { s_a, .. }) => {
|
||||
tracing::info!(
|
||||
"Alice has accepted our request to cooperatively redeem the XMR"
|
||||
);
|
||||
|
||||
let s_a = monero::PrivateKey { scalar: s_a };
|
||||
let state5 = state.attempt_cooperative_redeem(s_a);
|
||||
|
||||
match state5
|
||||
.redeem_xmr(monero_wallet, swap_id.to_string(), monero_receive_address)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
return Ok(BobState::XmrRedeemed { tx_lock_id });
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(error)
|
||||
.context("Failed to redeem XMR with revealed XMR key");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Rejected { reason, .. }) => {
|
||||
tracing::error!(
|
||||
%reason,
|
||||
"Alice rejected our request for cooperative XMR redeem"
|
||||
);
|
||||
return Err(reason)
|
||||
.context("Alice rejected our request for cooperative XMR redeem");
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(
|
||||
%error,
|
||||
"Failed to request cooperative XMR redeem from Alice"
|
||||
);
|
||||
return Err(error)
|
||||
.context("Failed to request cooperative XMR redeem from Alice");
|
||||
}
|
||||
};
|
||||
}
|
||||
BobState::SafelyAborted => BobState::SafelyAborted,
|
||||
BobState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id },
|
||||
})
|
||||
|
@ -7,7 +7,9 @@ use anyhow::Result;
|
||||
use jsonrpsee::server::RpcModule;
|
||||
use jsonrpsee::types::Params;
|
||||
use libp2p::core::Multiaddr;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
@ -48,8 +50,30 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
|
||||
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_history", |params_raw, context| async move {
|
||||
execute_request(params_raw, Method::History, &context).await
|
||||
})?;
|
||||
|
||||
module.register_async_method("get_logs", |params_raw, context| async move {
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct Params {
|
||||
swap_id: Option<Uuid>,
|
||||
logs_dir: Option<PathBuf>,
|
||||
redact: bool,
|
||||
}
|
||||
|
||||
let params: Params = params_raw.parse()?;
|
||||
|
||||
execute_request(
|
||||
params_raw,
|
||||
Method::Logs {
|
||||
swap_id: params.swap_id,
|
||||
logs_dir: params.logs_dir,
|
||||
redact: params.redact,
|
||||
},
|
||||
&context,
|
||||
)
|
||||
.await
|
||||
})?;
|
||||
|
||||
module.register_async_method("get_raw_states", |params, context| async move {
|
||||
@ -135,16 +159,25 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
|
||||
module.register_async_method("buy_xmr", |params_raw, context| async move {
|
||||
let params: HashMap<String, String> = 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 bitcoin_change_address = params
|
||||
.get("bitcoin_change_address")
|
||||
.map(|addr_str| {
|
||||
bitcoin::Address::from_str(addr_str)
|
||||
.map_err(|err| {
|
||||
jsonrpsee_core::Error::Custom(format!(
|
||||
"Could not parse bitcoin address: {}",
|
||||
err
|
||||
))
|
||||
})
|
||||
.and_then(|address| {
|
||||
bitcoin_address::validate(
|
||||
address,
|
||||
context.config.env_config.bitcoin_network,
|
||||
)
|
||||
.map_err(|err| jsonrpsee_core::Error::Custom(err.to_string()))
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let monero_receive_address =
|
||||
monero::Address::from_str(params.get("monero_receive_address").ok_or_else(|| {
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::fs::ensure_directory_exists;
|
||||
use ::bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
||||
use ::bitcoin::secp256k1::{self, SecretKey};
|
||||
use anyhow::{Context, Result};
|
||||
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
|
||||
use bitcoin::hashes::{sha256, Hash, HashEngine};
|
||||
use bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
||||
use bitcoin::secp256k1::{self, SecretKey};
|
||||
use libp2p::identity;
|
||||
use pem::{encode, Pem};
|
||||
use rand::prelude::*;
|
||||
|
@ -11,7 +11,7 @@ use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||
/// the encsig and fail to refund or redeem. Alice punishes using the cancel and
|
||||
/// punish command.
|
||||
/// punish command. Bob then cooperates with Alice and redeems XMR with her key.
|
||||
#[tokio::test]
|
||||
async fn alice_manually_punishes_after_bob_dead() {
|
||||
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||
@ -78,9 +78,7 @@ async fn alice_manually_punishes_after_bob_dead() {
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
|
||||
ctx.assert_bob_punished(bob_state).await;
|
||||
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
|
@ -9,7 +9,7 @@ use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||
/// the encsig and fail to refund or redeem. Alice cancels and punishes.
|
||||
/// the encsig and fail to refund or redeem. Alice cancels and punishes. Bob then cooperates with Alice and redeems XMR with her key.
|
||||
#[tokio::test]
|
||||
async fn alice_punishes_after_restart_if_bob_dead() {
|
||||
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||
@ -58,9 +58,7 @@ async fn alice_punishes_after_restart_if_bob_dead() {
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
|
||||
ctx.assert_bob_punished(bob_state).await;
|
||||
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
|
@ -16,7 +16,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund};
|
||||
use swap::database::SqliteDatabase;
|
||||
use swap::database::{AccessMode, SqliteDatabase};
|
||||
use swap::env::{Config, GetConfig};
|
||||
use swap::fs::ensure_directory_exists;
|
||||
use swap::network::rendezvous::XmrBtcNamespace;
|
||||
@ -231,7 +231,11 @@ async fn start_alice(
|
||||
if !&db_path.exists() {
|
||||
tokio::fs::File::create(&db_path).await.unwrap();
|
||||
}
|
||||
let db = Arc::new(SqliteDatabase::open(db_path.as_path()).await.unwrap());
|
||||
let db = Arc::new(
|
||||
SqliteDatabase::open(db_path.as_path(), AccessMode::ReadWrite)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let min_buy = bitcoin::Amount::from_sat(u64::MIN);
|
||||
let max_buy = bitcoin::Amount::from_sat(u64::MAX);
|
||||
@ -433,7 +437,7 @@ impl BobParams {
|
||||
if !self.db_path.exists() {
|
||||
tokio::fs::File::create(&self.db_path).await?;
|
||||
}
|
||||
let db = Arc::new(SqliteDatabase::open(&self.db_path).await?);
|
||||
let db = Arc::new(SqliteDatabase::open(&self.db_path, AccessMode::ReadWrite).await?);
|
||||
|
||||
let (event_loop, handle) = self.new_eventloop(swap_id, db.clone()).await?;
|
||||
|
||||
@ -463,7 +467,7 @@ impl BobParams {
|
||||
if !self.db_path.exists() {
|
||||
tokio::fs::File::create(&self.db_path).await?;
|
||||
}
|
||||
let db = Arc::new(SqliteDatabase::open(&self.db_path).await?);
|
||||
let db = Arc::new(SqliteDatabase::open(&self.db_path, AccessMode::ReadWrite).await?);
|
||||
|
||||
let (event_loop, handle) = self.new_eventloop(swap_id, db.clone()).await?;
|
||||
|
||||
@ -652,7 +656,7 @@ impl TestContext {
|
||||
}
|
||||
|
||||
pub async fn assert_alice_punished(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcPunished));
|
||||
assert!(matches!(state, AliceState::BtcPunished { .. }));
|
||||
|
||||
assert_eventual_balance(
|
||||
self.alice_bitcoin_wallet.as_ref(),
|
||||
@ -698,7 +702,7 @@ impl TestContext {
|
||||
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
|
||||
state4.tx_lock_id()
|
||||
} else {
|
||||
panic!("Bob in not in btc refunded state: {:?}", state);
|
||||
panic!("Bob is not in btc refunded state: {:?}", state);
|
||||
};
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bob_bitcoin_wallet
|
||||
@ -819,7 +823,7 @@ impl TestContext {
|
||||
async fn bob_punished_btc_balance(&self, state: BobState) -> Result<bitcoin::Amount> {
|
||||
self.bob_bitcoin_wallet.sync().await?;
|
||||
|
||||
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
||||
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id, .. } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
bail!("Bob in not in btc punished state: {:?}", state);
|
||||
|
@ -7,7 +7,7 @@ use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
|
||||
/// Bob locks Btc and Alice locks Xmr. Bob does not act; he fails to send Alice
|
||||
/// the encsig and fail to refund or redeem. Alice punishes.
|
||||
/// the encsig and fail to refund or redeem. Alice punishes. Bob then cooperates with Alice and redeems XMR with her key.
|
||||
#[tokio::test]
|
||||
async fn alice_punishes_if_bob_never_acts_after_fund() {
|
||||
harness::setup_test(FastPunishConfig, |mut ctx| async move {
|
||||
@ -32,9 +32,7 @@ async fn alice_punishes_if_bob_never_acts_after_fund() {
|
||||
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
|
||||
|
||||
let bob_state = bob::run(bob_swap).await?;
|
||||
|
||||
ctx.assert_bob_punished(bob_state).await;
|
||||
|
||||
ctx.assert_bob_redeemed(bob_state).await;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
|
@ -103,13 +103,26 @@ mod test {
|
||||
|
||||
let (client, _, _) = setup_daemon(harness_ctx).await;
|
||||
|
||||
let response: HashMap<String, Vec<(Uuid, String)>> = client
|
||||
let response: HashMap<String, Vec<Value>> = 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 swaps = response.get("swaps").unwrap();
|
||||
assert_eq!(swaps.len(), 1);
|
||||
|
||||
assert_has_keys_serde(
|
||||
swaps[0].as_object().unwrap(),
|
||||
&[
|
||||
"swapId",
|
||||
"startDate",
|
||||
"state",
|
||||
"btcAmount",
|
||||
"xmrAmount",
|
||||
"exchangeRate",
|
||||
"tradingPartnerPeerId",
|
||||
],
|
||||
);
|
||||
|
||||
let response: HashMap<String, HashMap<Uuid, Vec<Value>>> = client
|
||||
.request("get_raw_states", ObjectParams::new())
|
||||
|
13
utils/gpg_keys/binarybaron.asc
Normal file
13
utils/gpg_keys/binarybaron.asc
Normal file
@ -0,0 +1,13 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEZp+avRYJKwYBBAHaRw8BAQdAD99LhR+cHlXDsYPjRJr0Ag7BXsjGZKfdWCtx
|
||||
CPA0fwG0LWJpbmFyeWJhcm9uIDxiaW5hcnliYXJvbkB1bnN0b3BwYWJsZXN3YXAu
|
||||
bmV0PoiTBBMWCgA7FiEENahE1/S1W8ROGA/xmbddPhR2om4FAmafmr0CGwMFCwkI
|
||||
BwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQmbddPhR2om5IQQD/d/EmD/yKMKRl
|
||||
Hw9RSP4bhcALmrZPri8sYkPteus8OhIA+wWTaIxXZJgydpXv95yECTfUXZ0UhuJq
|
||||
6UH0FQL8mosJuDgEZp+avRIKKwYBBAGXVQEFAQEHQOd1tQ46YVKxyUKluPAvGJLY
|
||||
LQ+3UWFWQJavLblkrYE2AwEIB4h4BBgWCgAgFiEENahE1/S1W8ROGA/xmbddPhR2
|
||||
om4FAmafmr0CGwwACgkQmbddPhR2om6mmQEAn7vufrOp/HSYgn9l5tmJxMkyxJ3W
|
||||
2WNo9u+JdnSik1IBAMsNcc4zm5ewfFr/qAnTHzHRId7dWR2+hs1oH7JOlf8L
|
||||
=Rxij
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
Loading…
x
Reference in New Issue
Block a user