Compare commits

...

189 Commits

Author SHA1 Message Date
binarybaron
81b89d63c2
docs: Add notice about breaking backwards compatability 2024-11-20 15:24:06 +01:00
Binarybaron
a2cc3591b1 fix: do not panic if electrum server does not support fee estimation 2024-11-11 16:43:43 +01:00
Binarybaron
44fafc792e feat(asb): Add Dockerfile 2024-11-11 16:02:55 +01:00
Byron Hambly
050d4f5f48
Merge pull request #1801 from comit-network/dependabot/cargo/anyhow-1.0.91
build(deps): bump anyhow from 1.0.90 to 1.0.91
2024-10-24 08:26:23 +02:00
Byron Hambly
0fb411c8a6
Merge pull request #1803 from comit-network/dependabot/cargo/thiserror-1.0.65
build(deps): bump thiserror from 1.0.64 to 1.0.65
2024-10-24 08:25:50 +02:00
Byron Hambly
d69cf30efe
Merge pull request #1794 from comit-network/dependabot/cargo/uuid-1.11.0
build(deps): bump uuid from 1.10.0 to 1.11.0
2024-10-24 08:25:41 +02:00
dependabot[bot]
fac5df93fa
build(deps): bump thiserror from 1.0.64 to 1.0.65
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.64 to 1.0.65.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...1.0.65)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 11:46:56 +00:00
dependabot[bot]
02a3711cc9
build(deps): bump anyhow from 1.0.90 to 1.0.91
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.90 to 1.0.91.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.90...1.0.91)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 11:45:51 +00:00
Byron Hambly
4afd8a44e4
Merge pull request #1793 from comit-network/dependabot/cargo/futures-0.3.31
build(deps): bump futures from 0.3.30 to 0.3.31
2024-10-22 09:44:27 +02:00
Byron Hambly
8e6327a2e6
Merge pull request #1795 from comit-network/dependabot/cargo/hyper-1.5.0
build(deps): bump hyper from 1.4.1 to 1.5.0
2024-10-22 09:43:57 +02:00
Byron Hambly
cb42f7ae27
Merge pull request #1799 from comit-network/dependabot/cargo/serde_json-1.0.132
build(deps): bump serde_json from 1.0.128 to 1.0.132
2024-10-22 09:43:26 +02:00
Byron Hambly
d5d2663442
Merge pull request #1798 from comit-network/dependabot/cargo/anyhow-1.0.90
build(deps): bump anyhow from 1.0.89 to 1.0.90
2024-10-22 09:43:16 +02:00
dependabot[bot]
7b1b5ec3a5
build(deps): bump serde_json from 1.0.128 to 1.0.132
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.128 to 1.0.132.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.128...1.0.132)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 11:45:16 +00:00
dependabot[bot]
de7c8aec0a
build(deps): bump anyhow from 1.0.89 to 1.0.90
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.89 to 1.0.90.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.89...1.0.90)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 11:44:43 +00:00
dependabot[bot]
d521815e91
build(deps): bump hyper from 1.4.1 to 1.5.0
Bumps [hyper](https://github.com/hyperium/hyper) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/hyperium/hyper/releases)
- [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper/compare/v1.4.1...v1.5.0)

---
updated-dependencies:
- dependency-name: hyper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-16 11:58:25 +00:00
dependabot[bot]
ec567ccba1
build(deps): bump uuid from 1.10.0 to 1.11.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.10.0...1.11.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-16 11:55:59 +00:00
dependabot[bot]
61c7afb358
build(deps): bump futures from 0.3.30 to 0.3.31
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.30 to 0.3.31.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.30...0.3.31)

---
updated-dependencies:
- dependency-name: futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-15 11:32:11 +00:00
Byron Hambly
d841741d3c
Merge pull request #1779 from comit-network/dependabot/cargo/async-trait-0.1.83
build(deps): bump async-trait from 0.1.82 to 0.1.83
2024-10-14 13:52:16 +02:00
Byron Hambly
f2ee1004e1
Merge pull request #1783 from comit-network/dependabot/cargo/regex-1.11.0
build(deps): bump regex from 1.10.6 to 1.11.0
2024-10-14 13:52:06 +02:00
Byron Hambly
f151653c65
Merge pull request #1785 from comit-network/dependabot/cargo/tempfile-3.13.0
build(deps): bump tempfile from 3.12.0 to 3.13.0
2024-10-14 13:51:56 +02:00
Byron Hambly
9e68fd0d9f
Merge pull request #1786 from comit-network/dependabot/cargo/reqwest-0.12.8
build(deps): bump reqwest from 0.12.7 to 0.12.8
2024-10-14 13:51:49 +02:00
Byron Hambly
5d88d33243
Merge pull request #1787 from comit-network/dependabot/cargo/once_cell-1.20.2
build(deps): bump once_cell from 1.20.0 to 1.20.2
2024-10-14 13:51:39 +02:00
Byron Hambly
23980df163
Merge pull request #1789 from comit-network/dependabot/github_actions/actions/checkout-4.2.1
build(deps): bump actions/checkout from 4.1.7 to 4.2.1
2024-10-14 13:51:30 +02:00
Byron Hambly
a837d1c22b
Merge pull request #1792 from comit-network/dependabot/github_actions/Swatinem/rust-cache-2.7.5
build(deps): bump Swatinem/rust-cache from 2.7.3 to 2.7.5
2024-10-14 13:51:01 +02:00
dependabot[bot]
3d4bc971a2
build(deps): bump Swatinem/rust-cache from 2.7.3 to 2.7.5
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from 2.7.3 to 2.7.5.
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/swatinem/rust-cache/compare/v2.7.3...v2.7.5)

---
updated-dependencies:
- dependency-name: Swatinem/rust-cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 11:07:29 +00:00
binarybaron
19710e296d
docs: Add notice to ReadMe about status of maintenance (#1790)
docs: Add notice to ReadMe about status of maintenance and reference community fork at UnstoppableSwap/core
2024-10-09 14:58:27 +02:00
dependabot[bot]
851086c5a2
build(deps): bump actions/checkout from 4.1.7 to 4.2.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.7...v4.2.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-08 11:47:11 +00:00
dependabot[bot]
88e6112736
build(deps): bump once_cell from 1.20.0 to 1.20.2
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.20.0 to 1.20.2.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.0...v1.20.2)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 11:39:41 +00:00
dependabot[bot]
a3ffd35fc0
build(deps): bump reqwest from 0.12.7 to 0.12.8
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.7 to 0.12.8.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.7...v0.12.8)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 11:09:42 +00:00
dependabot[bot]
12c6974458
build(deps): bump tempfile from 3.12.0 to 3.13.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.12.0 to 3.13.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.12.0...v3.13.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 11:37:04 +00:00
dependabot[bot]
6abd951b46
build(deps): bump regex from 1.10.6 to 1.11.0
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.6 to 1.11.0.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.6...1.11.0)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 11:36:32 +00:00
Byron Hambly
bfb0053919
Merge pull request #1778 from comit-network/dependabot/cargo/thiserror-1.0.64
build(deps): bump thiserror from 1.0.63 to 1.0.64
2024-09-25 14:34:15 +02:00
Byron Hambly
c78ffa7351
Merge pull request #1776 from comit-network/dependabot/cargo/once_cell-1.20.0
build(deps): bump once_cell from 1.19.0 to 1.20.0
2024-09-25 14:34:06 +02:00
dependabot[bot]
523adc6d26
build(deps): bump async-trait from 0.1.82 to 0.1.83
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.82 to 0.1.83.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.82...0.1.83)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-25 11:18:25 +00:00
dependabot[bot]
e01986bb50
build(deps): bump thiserror from 1.0.63 to 1.0.64
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.63 to 1.0.64.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 11:42:15 +00:00
Byron Hambly
a979871610
Merge pull request #1775 from comit-network/dependabot/cargo/anyhow-1.0.89
build(deps): bump anyhow from 1.0.88 to 1.0.89
2024-09-16 13:30:09 +02:00
dependabot[bot]
e3f31af88a
build(deps): bump once_cell from 1.19.0 to 1.20.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.19.0 to 1.20.0.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.19.0...v1.20.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 11:15:38 +00:00
dependabot[bot]
fe77b5af95
build(deps): bump anyhow from 1.0.88 to 1.0.89
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.88 to 1.0.89.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.88...1.0.89)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 11:14:20 +00:00
Byron Hambly
f20d71f74b
Merge pull request #1771 from comit-network/dependabot/cargo/serde-1.0.210
build(deps): bump serde from 1.0.208 to 1.0.210
2024-09-12 13:55:19 +02:00
Byron Hambly
0584256622
Merge pull request #1768 from comit-network/dependabot/cargo/tokio-util-0.7.12
build(deps): bump tokio-util from 0.7.11 to 0.7.12
2024-09-12 13:55:09 +02:00
Byron Hambly
dcb4d02988
Merge pull request #1767 from comit-network/dependabot/cargo/serde_json-1.0.128
build(deps): bump serde_json from 1.0.124 to 1.0.128
2024-09-12 13:54:57 +02:00
Byron Hambly
545ef8fb30
Merge pull request #1766 from comit-network/dependabot/cargo/async-trait-0.1.82
build(deps): bump async-trait from 0.1.81 to 0.1.82
2024-09-12 13:54:47 +02:00
Byron Hambly
433ed87263
Merge pull request #1773 from comit-network/dependabot/cargo/anyhow-1.0.88
build(deps): bump anyhow from 1.0.86 to 1.0.88
2024-09-12 13:53:46 +02:00
dependabot[bot]
8022d8b423
build(deps): bump anyhow from 1.0.86 to 1.0.88
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.86 to 1.0.88.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.86...1.0.88)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-12 11:08:00 +00:00
dependabot[bot]
4114772bb7
build(deps): bump serde from 1.0.208 to 1.0.210
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.208 to 1.0.210.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.210)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 12:02:58 +00:00
dependabot[bot]
ff9f349889
build(deps): bump tokio-util from 0.7.11 to 0.7.12
Bumps [tokio-util](https://github.com/tokio-rs/tokio) from 0.7.11 to 0.7.12.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.11...tokio-util-0.7.12)

---
updated-dependencies:
- dependency-name: tokio-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-05 11:47:31 +00:00
dependabot[bot]
a8a99db738
build(deps): bump serde_json from 1.0.124 to 1.0.128
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.124 to 1.0.128.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.124...1.0.128)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-05 11:47:01 +00:00
dependabot[bot]
29ee6acb95
build(deps): bump async-trait from 0.1.81 to 0.1.82
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.81 to 0.1.82.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.81...0.1.82)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 11:31:16 +00:00
Byron Hambly
aef729331f
Merge pull request #1760 from comit-network/dependabot/cargo/regex-1.10.6
build(deps): bump regex from 1.10.5 to 1.10.6
2024-08-22 15:18:09 +02:00
dependabot[bot]
2faf7561bd
build(deps): bump regex from 1.10.5 to 1.10.6
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.5 to 1.10.6.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.5...1.10.6)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-22 11:57:24 +00:00
Einliterflasche
7871cf256b
feat(asb + cli): Redact logs + unify tracing infrastructure (#1733) 2024-08-21 16:33:04 +02:00
Byron Hambly
55bfd95694
Merge pull request #1759 from comit-network/dependabot/cargo/reqwest-0.12.7
build(deps): bump reqwest from 0.12.5 to 0.12.7
2024-08-20 15:43:11 +02:00
dependabot[bot]
3cebddf593
build(deps): bump reqwest from 0.12.5 to 0.12.7
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.5 to 0.12.7.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.5...v0.12.7)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-20 11:31:40 +00:00
Byron Hambly
c3831e93cc
Merge pull request #1758 from comit-network/dependabot/cargo/serde-1.0.208
build(deps): bump serde from 1.0.206 to 1.0.208
2024-08-16 13:50:10 +02:00
dependabot[bot]
4eff6fe503
build(deps): bump serde from 1.0.206 to 1.0.208
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.206 to 1.0.208.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.206...v1.0.208)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-16 11:04:29 +00:00
Byron Hambly
018e803f88
Merge pull request #1754 from comit-network/dependabot/cargo/serde-1.0.206
build(deps): bump serde from 1.0.205 to 1.0.206
2024-08-12 15:19:57 +02:00
Byron Hambly
504d05bfad
Merge pull request #1753 from comit-network/dependabot/cargo/serde_json-1.0.124
build(deps): bump serde_json from 1.0.122 to 1.0.124
2024-08-12 15:19:47 +02:00
dependabot[bot]
33901a2ea9
build(deps): bump serde from 1.0.205 to 1.0.206
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.205 to 1.0.206.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.205...v1.0.206)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 11:07:58 +00:00
dependabot[bot]
c8cbd27b79
build(deps): bump serde_json from 1.0.122 to 1.0.124
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.122 to 1.0.124.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.122...v1.0.124)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 11:07:36 +00:00
Byron Hambly
f755993fc6
Merge pull request #1748 from comit-network/dependabot/cargo/tempfile-3.12.0
build(deps): bump tempfile from 3.11.0 to 3.12.0
2024-08-08 15:08:23 +02:00
Byron Hambly
ae614b5567
Merge pull request #1750 from comit-network/dependabot/cargo/serde-1.0.205
build(deps): bump serde from 1.0.204 to 1.0.205
2024-08-08 15:07:57 +02:00
dependabot[bot]
f799da5203
build(deps): bump serde from 1.0.204 to 1.0.205
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.204 to 1.0.205.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.204...v1.0.205)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 11:50:16 +00:00
dependabot[bot]
952fb71a6a
build(deps): bump tempfile from 3.11.0 to 3.12.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.11.0 to 3.12.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 11:15:11 +00:00
Byron Hambly
180458d587
Merge pull request #1746 from comit-network/dependabot/cargo/tempfile-3.11.0
build(deps): bump tempfile from 3.10.1 to 3.11.0
2024-08-05 14:15:21 +02:00
dependabot[bot]
6a76e9efbe
build(deps): bump tempfile from 3.10.1 to 3.11.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.10.1 to 3.11.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.10.1...v3.11.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 11:18:44 +00:00
Byron Hambly
7634fe1702
Merge pull request #1744 from comit-network/dependabot/cargo/serde_json-1.0.122
build(deps): bump serde_json from 1.0.121 to 1.0.122
2024-08-05 09:47:37 +02:00
dependabot[bot]
9aaa7d358f
build(deps): bump serde_json from 1.0.121 to 1.0.122
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.121 to 1.0.122.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.121...v1.0.122)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-02 11:39:10 +00:00
Byron Hambly
204aa85e59
Merge pull request #1741 from comit-network/dependabot/cargo/mockito-1.5.0
build(deps): bump mockito from 1.4.0 to 1.5.0
2024-08-01 20:26:23 +02:00
Byron Hambly
7c509d3ea3
Merge pull request #1742 from comit-network/dependabot/cargo/toml-0.8.19
build(deps): bump toml from 0.8.17 to 0.8.19
2024-08-01 20:24:43 +02:00
binarybaron
cc854be8f4
feat: Enhance history command with more swap details (#1725) 2024-08-01 18:35:03 +02:00
dependabot[bot]
af6bc47ed3
build(deps): bump toml from 0.8.17 to 0.8.19
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.17 to 0.8.19.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.17...toml-v0.8.19)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 11:39:35 +00:00
dependabot[bot]
587212abc7
build(deps): bump mockito from 1.4.0 to 1.5.0
Bumps [mockito](https://github.com/lipanski/mockito) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/lipanski/mockito/releases)
- [Commits](https://github.com/lipanski/mockito/compare/1.4.0...1.5.0)

---
updated-dependencies:
- dependency-name: mockito
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 11:38:09 +00:00
Byron Hambly
b18ba95e8c
Merge pull request #1740 from comit-network/dependabot/cargo/toml-0.8.17
build(deps): bump toml from 0.8.16 to 0.8.17
2024-07-31 17:40:25 +02:00
dependabot[bot]
6e09c73cf3
build(deps): bump toml from 0.8.16 to 0.8.17
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.16 to 0.8.17.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.16...toml-v0.8.17)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 11:39:26 +00:00
Byron Hambly
132cfe782c
Merge pull request #1736 from comit-network/dependabot/github_actions/thomaseizinger/keep-a-changelog-new-release-3.1.0
build(deps): bump thomaseizinger/keep-a-changelog-new-release from 3.0.0 to 3.1.0
2024-07-29 19:38:20 +02:00
Byron Hambly
3611a1aced
Merge pull request #1737 from comit-network/dependabot/cargo/serde_json-1.0.121
build(deps): bump serde_json from 1.0.118 to 1.0.121
2024-07-29 19:37:51 +02:00
Byron Hambly
21f26a5b44
Merge pull request #1738 from comit-network/dependabot/cargo/tokio-socks-0.5.2
build(deps): bump tokio-socks from 0.5.1 to 0.5.2
2024-07-29 19:37:28 +02:00
dependabot[bot]
9700d192b2
build(deps): bump tokio-socks from 0.5.1 to 0.5.2
Bumps [tokio-socks](https://github.com/sticnarf/tokio-socks) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/sticnarf/tokio-socks/releases)
- [Changelog](https://github.com/sticnarf/tokio-socks/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sticnarf/tokio-socks/compare/v0.5.1...v0.5.2)

---
updated-dependencies:
- dependency-name: tokio-socks
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 12:02:43 +00:00
dependabot[bot]
254874276c
build(deps): bump serde_json from 1.0.118 to 1.0.121
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.118 to 1.0.121.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.118...v1.0.121)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 12:00:29 +00:00
dependabot[bot]
011aa0cb9c
build(deps): bump thomaseizinger/keep-a-changelog-new-release
Bumps [thomaseizinger/keep-a-changelog-new-release](https://github.com/thomaseizinger/keep-a-changelog-new-release) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/thomaseizinger/keep-a-changelog-new-release/releases)
- [Changelog](https://github.com/thomaseizinger/keep-a-changelog-new-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/thomaseizinger/keep-a-changelog-new-release/compare/3.0.0...3.1.0)

---
updated-dependencies:
- dependency-name: thomaseizinger/keep-a-changelog-new-release
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 11:25:26 +00:00
Byron Hambly
8bf4ea08be
Merge pull request #1735 from comit-network/dependabot/cargo/toml-0.8.16
build(deps): bump toml from 0.8.15 to 0.8.16
2024-07-26 14:57:26 +02:00
dependabot[bot]
f3640aceb2
build(deps): bump toml from 0.8.15 to 0.8.16
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.15 to 0.8.16.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.15...toml-v0.8.16)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-26 12:23:43 +00:00
binarybaron
45e14c5a1e
Revert: 8284bea778fb4003d339d6fc4ca93f6d8299e635 2024-07-26 11:51:33 +02:00
binarybaron
f29bf20e8d
Revert "fix: Reintroduce Dockerfile"
This reverts commit 77a43ba28cca49d269f22ba7dce13c19c5b435a7.
2024-07-26 11:46:27 +02:00
binarybaron
b52e07ace9
Revert "fix: Reintroduce docker build action"
This reverts commit 49cae19059f09b5eadb72b4f73fef7c82c9913e0.
2024-07-26 11:46:19 +02:00
binarybaron
77a43ba28c
fix: Reintroduce Dockerfile 2024-07-26 11:45:48 +02:00
binarybaron
49cae19059
fix: Reintroduce docker build action 2024-07-26 11:45:22 +02:00
binarybaron
7b5929deb5
Merge pull request #9 from UnstoppableSwap/release/0.13.4
Release version 0.13.4
2024-07-26 00:38:02 +02:00
UnstoppableSwap Botty
8284bea778 Prepare release 0.13.4 2024-07-25 22:31:14 +00:00
binarybaron
0ad78e4f30
revert: Update CHANGELOG.md 2024-07-26 00:20:17 +02:00
binarybaron
fc6bb336c8
Merge branch 'comit-network:master' into master 2024-07-26 00:19:41 +02:00
COMIT Botty McBotface
33ad3c374a
Prepare release 0.13.4 (#1734) 2024-07-25 23:14:31 +02:00
Einliterflasche
2fe428779a
feat(asb): Retry locking Monero (#1731)
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
2024-07-25 23:06:50 +02:00
binarybaron
2eda2476eb
Revert "Merge branch 'master' into master" (#1730)
This reverts commit ce8d3afe60f520523fe6ab6b8d1b3de3d07e2027, reversing
changes made to 75cfc6b0d4169d72f136266af122381a05bb9efe.
2024-07-25 15:01:12 +02:00
binarybaron
aaa52e9559
Merge pull request #8 from comit-network/master
Merge parent repository
2024-07-25 14:04:14 +02:00
binarybaron
ce8d3afe60
Merge branch 'master' into master 2024-07-25 14:03:47 +02:00
binarybaron
75cfc6b0d4
fix(cli): allow bitcoin-change-address to be omitted when making request to rpc server (#1728) 2024-07-25 00:13:15 +02:00
binarybaron
c80bdb2d8c
fix(asb): Allow history command to be run while asb is running (#1724)
Co-authored-by: einliterflasche <einliterflasche@pm.me>
2024-07-23 14:52:01 +02:00
binarybaron
ef49b471d8 docs: add binarybaron.asc 2024-07-23 14:12:44 +02:00
binarybaron
cf87f19e82
Merge pull request #7 from UnstoppableSwap/release/0.13.3
Release version 0.13.3
2024-07-22 16:57:18 +02:00
UnstoppableSwap Botty
410fcf2117 Prepare release 0.13.3 2024-07-22 14:40:59 +00:00
binarybaron
8ba8e3bd5c
fix: Revert 0.13.3 release 2024-07-22 16:32:04 +02:00
binarybaron
eef78cddb2
CI: Exclude preview releases from "latest" docker release tag 2024-07-22 16:27:56 +02:00
binarybaron
f24c11f0a3
Merge branch 'comit-network:master' into master 2024-07-22 16:18:41 +02:00
Byron Hambly
8e6d1e37e8
Merge pull request #1719 from comit-network/dependabot/cargo/toml-0.8.15
build(deps): bump toml from 0.8.14 to 0.8.15
2024-07-18 13:54:04 +02:00
Byron Hambly
f4630fa9d1
Merge pull request #1720 from comit-network/dependabot/cargo/thiserror-1.0.63
build(deps): bump thiserror from 1.0.62 to 1.0.63
2024-07-18 13:53:56 +02:00
dependabot[bot]
cb85439905
build(deps): bump thiserror from 1.0.62 to 1.0.63
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.62 to 1.0.63.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.62...1.0.63)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-18 11:24:34 +00:00
dependabot[bot]
74af379f80
build(deps): bump toml from 0.8.14 to 0.8.15
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.14 to 0.8.15.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.14...toml-v0.8.15)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-18 11:24:12 +00:00
Byron Hambly
b3813f3769
Merge pull request #1718 from comit-network/dependabot/cargo/tokio-1.38.1
build(deps): bump tokio from 1.38.0 to 1.38.1
2024-07-18 09:57:48 +02:00
dependabot[bot]
7c9af191bc
build(deps): bump tokio from 1.38.0 to 1.38.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.38.0 to 1.38.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.0...tokio-1.38.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 11:08:57 +00:00
binarybaron
97788e6f54
Merge branch 'comit-network:master' into master 2024-07-17 00:08:37 +02:00
binarybaron
85c4db1d75
fix: Format cooperative redeem reject using thiserror (#1716) 2024-07-16 20:13:58 +02:00
binarybaron
612784b3e0
Merge pull request #6 from UnstoppableSwap/release/0.13.3
Release version 0.13.3
2024-07-16 11:45:00 +02:00
UnstoppableSwap Botty
7c54fb2848 Prepare release 0.13.3 2024-07-16 09:40:03 +00:00
binarybaron
f5bda640a0
fix: Remove duplicate entry from CHANGELOG.md 2024-07-16 11:31:18 +02:00
binarybaron
d8f84cbad9
Merge pull request #5 from UnstoppableSwap/release/0.13.3
Release version 0.13.3
2024-07-16 11:25:43 +02:00
UnstoppableSwap Botty
c430e89502 Prepare release 0.13.3 2024-07-16 09:25:28 +00:00
binarybaron
eab4b00478
feat: Release Docker image on build 2024-07-16 11:19:56 +02:00
binarybaron
d55c6e8c1a
fix(Dockerfile): Use local repo 2024-07-16 11:11:40 +02:00
binarybaron
60a3177710
feat: Dockerfile for asb 2024-07-16 10:43:47 +02:00
COMIT Botty McBotface
1f322b78c8
Prepare release 0.13.3 (#1714) 2024-07-15 16:45:19 +02:00
Byron Hambly
12b9ceebcf
Merge pull request #1676 from patrini32/cooperative-release-of-funds
Allow for cooperative release of funds
2024-07-15 10:00:44 +02:00
Byron Hambly
77f7222f71
Merge pull request #1712 from comit-network/dependabot/cargo/thiserror-1.0.62
build(deps): bump thiserror from 1.0.61 to 1.0.62
2024-07-15 09:38:24 +02:00
dependabot[bot]
31d76cbdf4
build(deps): bump thiserror from 1.0.61 to 1.0.62
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.61 to 1.0.62.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.62)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 11:25:56 +00:00
patrini32
845b9428b7
remove whitespaces 2024-07-11 11:02:23 +03:00
patrini32
ee04ff8a3b
fix formatting 2024-07-11 10:58:07 +03:00
binarybaron
f8c3276642 Merge branch 'cooperative-release-of-funds' of https://github.com/patrini32/xmr-btc-swap into pr/1676 2024-07-11 09:30:49 +02:00
binarybaron
073baa9752 Update swap.rs 2024-07-11 09:30:48 +02:00
binarybaron
f72005312c
Merge branch 'master' into cooperative-release-of-funds 2024-07-11 09:23:38 +02:00
Einliterflasche
4115a452e3
allow --change-address to be omitted and default to internal wallet address (#1709)
allow --change-address to be omitted and default to internal wallet address (#1709)

Co-authored-by: binarybaron <86064887+binarybaron@users.noreply.github.com>
Co-authored-by: Byron Hambly <byron@hambly.dev>
2024-07-11 09:19:41 +02:00
Byron Hambly
c385059138
Merge pull request #1710 from comit-network/dependabot/cargo/hyper-1.4.1
build(deps): bump hyper from 1.3.1 to 1.4.1
2024-07-10 15:25:03 +02:00
Byron Hambly
c283d6a911
Merge pull request #1711 from comit-network/dependabot/cargo/vergen-8.3.2
build(deps): bump vergen from 8.3.1 to 8.3.2
2024-07-10 15:24:45 +02:00
dependabot[bot]
dcb4edf585
build(deps): bump vergen from 8.3.1 to 8.3.2
Bumps [vergen](https://github.com/rustyhorde/vergen) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/rustyhorde/vergen/releases)
- [Commits](https://github.com/rustyhorde/vergen/commits)

---
updated-dependencies:
- dependency-name: vergen
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 11:19:33 +00:00
dependabot[bot]
47f8a65209
build(deps): bump hyper from 1.3.1 to 1.4.1
Bumps [hyper](https://github.com/hyperium/hyper) from 1.3.1 to 1.4.1.
- [Release notes](https://github.com/hyperium/hyper/releases)
- [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper/compare/v1.3.1...v1.4.1)

---
updated-dependencies:
- dependency-name: hyper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 11:19:16 +00:00
Byron Hambly
bf9abd4101
Merge pull request #1707 from comit-network/dependabot/cargo/uuid-1.10.0
build(deps): bump uuid from 1.9.1 to 1.10.0
2024-07-10 09:23:25 +02:00
dependabot[bot]
2af22c1aba
build(deps): bump uuid from 1.9.1 to 1.10.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.9.1 to 1.10.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.9.1...1.10.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-09 11:08:29 +00:00
Byron Hambly
e2198d49a0
Merge pull request #1704 from comit-network/dependabot/cargo/qrcode-0.14.1
build(deps): bump qrcode from 0.14.0 to 0.14.1
2024-07-09 09:03:58 +02:00
Byron Hambly
f0abb4396d
Merge pull request #1705 from comit-network/dependabot/cargo/serde-1.0.204
build(deps): bump serde from 1.0.203 to 1.0.204
2024-07-09 09:03:48 +02:00
Byron Hambly
537672df08
Merge pull request #1706 from comit-network/dependabot/cargo/async-trait-0.1.81
build(deps): bump async-trait from 0.1.80 to 0.1.81
2024-07-09 09:03:37 +02:00
dependabot[bot]
cd29870e11
build(deps): bump async-trait from 0.1.80 to 0.1.81
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.80 to 0.1.81.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.80...0.1.81)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 11:04:55 +00:00
dependabot[bot]
ab1001a18c
build(deps): bump serde from 1.0.203 to 1.0.204
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.203 to 1.0.204.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.203...v1.0.204)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 11:04:38 +00:00
dependabot[bot]
294b658f43
build(deps): bump qrcode from 0.14.0 to 0.14.1
Bumps [qrcode](https://github.com/kennytm/qrcode-rust) from 0.14.0 to 0.14.1.
- [Release notes](https://github.com/kennytm/qrcode-rust/releases)
- [Commits](https://github.com/kennytm/qrcode-rust/commits/v0.14.1)

---
updated-dependencies:
- dependency-name: qrcode
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 11:04:18 +00:00
binarybaron
2824ebc893 Update swap.rs 2024-07-03 20:39:49 +02:00
binarybaron
07f788eb81 Update swap.rs 2024-07-03 20:14:09 +02:00
binarybaron
ef75019ac6 Update swap.rs 2024-07-03 20:12:22 +02:00
binarybaron
26e66ce9b9 Update swap.rs 2024-07-03 20:02:04 +02:00
binarybaron
d7b649b7a6 feat: Allow for cooperative Monero redeem after Bitcoin punish has happened 2024-07-03 19:47:45 +02:00
binarybaron
c5aa7edb6b
chore: Fix typo (#1701) 2024-07-02 12:03:56 +02:00
COMIT Botty McBotface
5c9185876d
Prepare release 0.13.2 (#1700) 2024-07-02 02:11:36 +02:00
binarybaron
6c1b85e408
Revert "Prepare release 0.13.2 (#1694)" (#1699)
This reverts commit 415323e4fcebc81e7e6ad0fdc50c600914575a36.
2024-07-02 01:53:57 +02:00
binarybaron
26109d6d8a
Revert "Prepare release 0.13.2 (#1697)" (#1698)
This reverts commit 5e735ef4f72cb95f9372cd6d69f39f5f9019283b.
2024-07-02 01:51:58 +02:00
COMIT Botty McBotface
5e735ef4f7
Prepare release 0.13.2 (#1697) 2024-07-02 01:28:07 +02:00
binarybaron
aa9b3c4687
fix: Add a new migration script for creating the buffered_transfer_proofs table (#1695) 2024-07-02 01:20:20 +02:00
COMIT Botty McBotface
415323e4fc
Prepare release 0.13.2 (#1694) 2024-07-01 23:27:00 +02:00
patrini32
173d077751
feat (Cli): Display reason for failed cancel-refund operation to the user (#1668)
We now display the reason for a failed cancel-refund operation to the user.

Fixes #683
2024-07-01 23:14:44 +02:00
binarybaron
23a27680a4
feat (Bob): Buffer transfer proof to database when we are running a different swap (#1669) 2024-06-28 21:39:30 +02:00
binarybaron
4c9d1e8d8d
Reduce check interval for Monero watch_for_transfer (#1670)
Oftentimes we fail to check the status of the Monero transaction on the first try (because it hasn't been registered on our Monero daemon yet, it takes a few seconds).

By decreasing the check interval from the default of 2 minutes to a 10th of that, we ensure that Bob get's his transfer proof faster.
2024-06-27 14:24:27 +02:00
Byron Hambly
21fed8c291
Merge pull request #1689 from comit-network/dependabot/cargo/uuid-1.9.1
build(deps): bump uuid from 1.9.0 to 1.9.1
2024-06-25 15:34:43 +02:00
Byron Hambly
5390a50eda
Merge pull request #1688 from comit-network/dependabot/cargo/serde_json-1.0.118
build(deps): bump serde_json from 1.0.117 to 1.0.118
2024-06-25 15:34:33 +02:00
dependabot[bot]
5551f513d5
build(deps): bump uuid from 1.9.0 to 1.9.1
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.9.0...1.9.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 11:49:47 +00:00
dependabot[bot]
fbf1c3dabf
build(deps): bump serde_json from 1.0.117 to 1.0.118
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.117 to 1.0.118.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.117...v1.0.118)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 11:48:33 +00:00
Byron Hambly
23a4d624da
Merge pull request #1687 from ikmckenz/add-missing-test
Add concurrent_bobs_after_xmr_lock_proof_sent test to ci
2024-06-25 10:37:50 +02:00
Ian McKenzie
3fc934a189 Add concurrent_bobs_after_xmr_lock_proof_sent test to ci 2024-06-24 21:27:13 -07:00
Byron Hambly
194fa1b1cb
Merge pull request #1686 from comit-network/dependabot/cargo/strum-0.26.3
build(deps): bump strum from 0.26.2 to 0.26.3
2024-06-24 14:00:21 +02:00
Byron Hambly
58baa48eba
Merge pull request #1685 from comit-network/dependabot/cargo/uuid-1.9.0
build(deps): bump uuid from 1.8.0 to 1.9.0
2024-06-24 14:00:02 +02:00
Byron Hambly
6f57517c05
Merge pull request #1684 from comit-network/dependabot/cargo/proptest-1.5.0
build(deps): bump proptest from 1.4.0 to 1.5.0
2024-06-24 13:59:52 +02:00
dependabot[bot]
2068a4cf78
build(deps): bump strum from 0.26.2 to 0.26.3
Bumps [strum](https://github.com/Peternator7/strum) from 0.26.2 to 0.26.3.
- [Release notes](https://github.com/Peternator7/strum/releases)
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/compare/v0.26.2...v0.26.3)

---
updated-dependencies:
- dependency-name: strum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 11:12:44 +00:00
dependabot[bot]
dabe1abb21
build(deps): bump uuid from 1.8.0 to 1.9.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.8.0...1.9.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 11:10:02 +00:00
dependabot[bot]
cca0d0027e
build(deps): bump proptest from 1.4.0 to 1.5.0
Bumps [proptest](https://github.com/proptest-rs/proptest) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/proptest-rs/proptest/releases)
- [Changelog](https://github.com/proptest-rs/proptest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/proptest-rs/proptest/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: proptest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 11:09:33 +00:00
dependabot[bot]
4d557d1ef9
build(deps): bump mockito from 1.3.0 to 1.4.0 (#1567)
* build(deps): bump mockito from 1.3.0 to 1.3.1

Bumps [mockito](https://github.com/lipanski/mockito) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/lipanski/mockito/releases)
- [Commits](https://github.com/lipanski/mockito/compare/1.3.0...1.3.1)

---
updated-dependencies:
- dependency-name: mockito
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: breaking change in mockito async

* bump mockito to 1.4

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Byron Hambly <byron@hambly.dev>
2024-06-19 15:35:05 +02:00
Byron Hambly
1d5df5498c
Merge pull request #1682 from comit-network/dependabot/cargo/serial_test-3.1.1
build(deps): bump serial_test from 3.0.0 to 3.1.1
2024-06-19 15:21:14 +02:00
Byron Hambly
787edbbca2
Merge pull request #1681 from comit-network/dependabot/cargo/url-2.5.2
build(deps): bump url from 2.5.0 to 2.5.2
2024-06-19 13:58:17 +02:00
dependabot[bot]
543f2748b5
build(deps): bump serial_test from 3.0.0 to 3.1.1
Bumps [serial_test](https://github.com/palfrey/serial_test) from 3.0.0 to 3.1.1.
- [Release notes](https://github.com/palfrey/serial_test/releases)
- [Commits](https://github.com/palfrey/serial_test/compare/v3.0.0...v3.1.1)

---
updated-dependencies:
- dependency-name: serial_test
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-19 11:35:30 +00:00
dependabot[bot]
7da996ab7b
build(deps): bump url from 2.5.0 to 2.5.2
Bumps [url](https://github.com/servo/rust-url) from 2.5.0 to 2.5.2.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.0...v2.5.2)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-19 11:34:57 +00:00
Byron Hambly
ad212c8663
Merge pull request #1680 from comit-network/dependabot/cargo/reqwest-0.12.5
build(deps): bump reqwest from 0.12.4 to 0.12.5
2024-06-18 15:27:21 +02:00
dependabot[bot]
f4792174da
build(deps): bump reqwest from 0.12.4 to 0.12.5
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.4 to 0.12.5.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.4...v0.12.5)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 11:45:28 +00:00
Byron Hambly
cbab8647fd
Merge pull request #1679 from comit-network/dependabot/github_actions/actions/checkout-4.1.7
build(deps): bump actions/checkout from 4.1.6 to 4.1.7
2024-06-13 18:01:00 +02:00
dependabot[bot]
122aee022a
build(deps): bump actions/checkout from 4.1.6 to 4.1.7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.6...v4.1.7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 11:53:20 +00:00
COMIT Botty McBotface
a3b1e772b2
Prepare release 0.13.1 (#1675) 2024-06-10 20:23:50 +02:00
Byron Hambly
90494ba4a5
fix: monero wallet refresh (#1596)
This PR changes the following behaviour in the refresh functionality of the monero wallet
- Allows for multiple retries because in some cases users have experienced an issue where the wallet rpc returns `no connection to daemon` even though the daemon is available. I'm not 100% sure why this happens but retrying often fixes the issue
- Print the current sync height after each failed attempt at syncing to see how far we've come
- The `monero-wallet-rpc` is started with the `--no-initial-sync` flag which ensures that as soon as it's started, it's ready to respond to requests
- The `monero-wallet-rpc` was upgraded to `v0.18.3.1` because this PR https://github.com/monero-project/monero/pull/8941 has improved some of the issues mentioned above


This PR is part of a larger effort to fix this issue https://github.com/comit-network/xmr-btc-swap/issues/1432
2024-06-10 18:53:52 +02:00
Byron Hambly
9594b0c46b
Merge pull request #1673 from comit-network/bob-extensive-quote-log
feat (Bob): Log extensive information about deposit requirements
2024-06-07 09:41:43 +02:00
Byron Hambly
72202b714e
Merge pull request #1663 from comit-network/dependabot/cargo/tokio-1.38.0
build(deps): bump tokio from 1.37.0 to 1.38.0
2024-06-07 09:32:01 +02:00
binarybaron
55b67d31d4 feat (Bob): Log extensive information about deposit requirements 2024-06-06 15:21:48 +02:00
dependabot[bot]
b99978879d
build(deps): bump tokio from 1.37.0 to 1.38.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.37.0 to 1.38.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.37.0...tokio-1.38.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-06 11:25:09 +00:00
chengehe
49a7f7eed6
chore: remove repeat word (#1671)
Signed-off-by: chengehe <hechenge@yeah.net>
2024-06-06 12:10:19 +02:00
Byron Hambly
2eb9e01f97
Merge pull request #1667 from comit-network/dependabot/cargo/toml-0.8.14
build(deps): bump toml from 0.8.13 to 0.8.14
2024-06-04 15:22:03 +02:00
dependabot[bot]
16d5ffc07e
build(deps): bump toml from 0.8.13 to 0.8.14
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.13 to 0.8.14.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.13...toml-v0.8.14)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-04 11:31:51 +00:00
pokkst
9635c0b551
fix (Bob): Check if Bitcoin redeem transaction was published before transitioning to CancelTimelockExpired (#1427)
* fix (Bob): Check if Bitcoin redeem transaction was published before transitioning to CancelTimelockExpired

---------

Co-authored-by: binarybaron <86064887+binarybaron@users.noreply.github.com>
Co-authored-by: Byron Hambly <bhambly@blockstream.com>
2024-06-04 12:49:15 +02:00
dependabot[bot]
1930540c1f
build(deps): bump reqwest from 0.11.27 to 0.12.4 (#1588)
* build(deps): bump reqwest from 0.11.27 to 0.12.0

Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.27 to 0.12.0.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.27...v0.12.0)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* wip

* wip

* ci: lock sqlx-cli install

* bump reqwest to 0.12.2

* deps: reqwest to 0.12.4

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Byron Hambly <byron@hambly.dev>
2024-05-30 10:27:48 +02:00
72 changed files with 2599 additions and 886 deletions

View File

@ -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"]

View File

@ -54,12 +54,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout tagged commit
uses: actions/checkout@v4.1.6
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:

View File

@ -11,14 +11,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4.1.6
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.6
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.6
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.6
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.6
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
@ -157,20 +157,23 @@ jobs:
alice_and_bob_refund_using_cancel_and_refund_command,
alice_and_bob_refund_using_cancel_then_refund_command,
alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired,
alice_manually_punishes_after_bob_dead_and_bob_cancels,
punish,
alice_punishes_after_restart_bob_dead,
alice_manually_punishes_after_bob_dead,
alice_refunds_after_restart_bob_refunded,
ensure_same_swap_id,
concurrent_bobs_before_xmr_lock_proof_sent,
concurrent_bobs_after_xmr_lock_proof_sent,
alice_manually_redeems_after_enc_sig_learned,
happy_path_bob_offline_while_alice_redeems_btc,
]
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4.1.6
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
@ -179,9 +182,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4.1.6
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
@ -190,11 +193,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4.1.6
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

View File

@ -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.6
- uses: actions/checkout@v4.2.1
- name: Extract version from branch name
id: extract-version

View File

@ -12,7 +12,7 @@ jobs:
name: "Draft a new release"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.6
- 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

View File

@ -10,7 +10,7 @@ jobs:
name: Create preview release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/checkout@v4.2.1
- name: Delete 'preview' release
uses: larryjoelane/delete-release-action@v1.0.24

View File

@ -7,6 +7,28 @@ 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
- CLI: We now display the reason for a failed cancel-refund operation to the user (#683)
## [0.13.1] - 2024-06-10
- Add retry logic to monero-wallet-rpc wallet refresh
## [0.13.0] - 2024-05-29
- Minimum Supported Rust Version (MSRV) bumped to 1.74
@ -356,7 +378,11 @@ 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.0...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
[0.12.3]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.2...0.12.3
[0.12.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.1...0.12.2

731
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,4 +3,6 @@ resolver = "2"
members = [ "monero-harness", "monero-rpc", "swap", "monero-wallet" ]
[patch.crates-io]
# patch until new release https://github.com/thomaseizinger/rust-jsonrpc-client/pull/51
jsonrpc_client = { git = "https://github.com/delta1/rust-jsonrpc-client.git", rev = "3b6081697cd616c952acb9c2f02d546357d35506" }
monero = { git = "https://github.com/comit-network/monero-rs", rev = "818f38b" }

22
Dockerfile Normal file
View 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"]

View File

@ -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.

View File

@ -110,7 +110,7 @@ The minimum and maximum amount as well as a spread, that is added on top of the
In order to be able to trade, the ASB must define a price to be able to agree on the amounts to be swapped with a CLI.
The `XMR<>BTC` price is currently determined by the price from the central exchange Kraken.
Upon startup the ASB connects to the Kraken price websocket and listens on the stream for price updates.
You can plug in a different price ticker websocket using the the `price_ticker_ws_url` configuration option.
You can plug in a different price ticker websocket using the `price_ticker_ws_url` configuration option.
You will have to make sure that the format returned is the same as the format used by Kraken.
Currently, we use a spot-price model, i.e. the ASB dictates the price to the CLI.

View File

@ -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.

View File

@ -10,7 +10,7 @@ anyhow = "1"
futures = "0.3"
monero-rpc = { path = "../monero-rpc" }
rand = "0.7"
testcontainers = "0.14"
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" ] }

View File

@ -12,7 +12,7 @@ jsonrpc_client = { version = "0.7", features = [ "reqwest" ] }
monero = "0.12"
monero-epee-bin-serde = "1"
rand = "0.7"
reqwest = { version = "0.11", default-features = false, features = [ "json" ] }
reqwest = { version = "0.12", default-features = false, features = [ "json" ] }
rust_decimal = { version = "1", features = [ "serde-float" ] }
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"

View File

@ -162,6 +162,12 @@ pub struct BlockHeight {
pub height: u32,
}
impl fmt::Display for BlockHeight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.height)
}
}
#[derive(Clone, Copy, Debug, Deserialize)]
#[serde(from = "CheckTxKeyResponse")]
pub struct CheckTxKey {

View File

@ -14,6 +14,6 @@ rand = "0.7"
curve25519-dalek = "3"
monero-harness = { path = "../monero-harness" }
rand = "0.7"
testcontainers = "0.14"
testcontainers = "0.15"
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs" ] }
tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "chrono", "tracing-log" ] }

View File

@ -1,6 +1,6 @@
[package]
name = "swap"
version = "0.13.0"
version = "0.13.4"
authors = [ "The COMIT guys <hello@comit.network>" ]
edition = "2021"
description = "XMR/BTC trustless atomic swaps."
@ -37,12 +37,14 @@ 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"
reqwest = { version = "0.11", features = [ "rustls-tls", "stream", "socks" ], default-features = false }
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"
serde = { version = "1", features = [ "derive" ] }
@ -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.8", features = [ "serde", "v4" ] }
uuid = { version = "1.11", features = [ "serde", "v4" ] }
void = "1"
[target.'cfg(not(windows))'.dependencies]
@ -77,19 +79,19 @@ tokio-tar = "0.3"
zip = "0.5"
[dev-dependencies]
bitcoin-harness = "0.2.2"
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.3.0"
mockito = "1.5"
monero-harness = { path = "../monero-harness" }
port_check = "0.2"
proptest = "1"
sequential-test = "0.2.4"
serde_cbor = "0.11"
serial_test = "3.0"
serial_test = "3.1"
tempfile = "3"
testcontainers = "0.14"
testcontainers = "0.15"
[build-dependencies]
anyhow = "1"

View 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

View File

@ -0,0 +1,5 @@
CREATE TABLE if NOT EXISTS buffered_transfer_proofs
(
swap_id TEXT PRIMARY KEY NOT NULL,
proof TEXT NOT NULL
);

View File

@ -195,5 +195,33 @@
}
},
"query": "\n SELECT state\n FROM swap_states\n WHERE swap_id = ?\n "
},
"e36c287aa98ae80ad4b6bb6f7e4b59cced041406a9db71da827b09f0d3bacfd6": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 2
}
},
"query": "\n INSERT INTO buffered_transfer_proofs (\n swap_id,\n proof\n ) VALUES (?, ?);\n "
},
"e9d422daf774d099fcbde6c4cda35821da948bd86cc57798b4d8375baf0b51ae": {
"describe": {
"columns": [
{
"name": "proof",
"ordinal": 0,
"type_info": "Text"
}
],
"nullable": [
false
],
"parameters": {
"Right": 1
}
},
"query": "\n SELECT proof\n FROM buffered_transfer_proofs\n WHERE swap_id = ?\n "
}
}

View File

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

View File

@ -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")?;
@ -376,7 +412,7 @@ impl Request {
},
result = async {
let (event_loop, mut event_loop_handle) =
EventLoop::new(swap_id, swarm, seller_peer_id)?;
EventLoop::new(swap_id, swarm, seller_peer_id, context.db.clone())?;
let event_loop = tokio::spawn(event_loop.run().in_current_span());
let bid_quote = event_loop_handle.request_quote().await?;
@ -522,7 +558,7 @@ impl Request {
}
let (event_loop, event_loop_handle) =
EventLoop::new(swap_id, swarm, seller_peer_id)?;
EventLoop::new(swap_id, swarm, seller_peer_id, context.db.clone())?;
let monero_receive_address = context.db.get_monero_address(swap_id).await?;
let swap = Swap::from_db(
Arc::clone(&context.db),
@ -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?;
@ -821,6 +954,7 @@ impl Request {
.await
.map_err(|err| {
method_span.in_scope(|| {
// The {:?} formatter is used to print the entire error chain
tracing::debug!(err = format!("{:?}", err), "API call resulted in an error");
});
err
@ -882,19 +1016,24 @@ where
loop {
let min_outstanding = bid_quote.min_quantity - max_giveable;
let min_fee = estimate_fee(min_outstanding).await?;
let min_deposit = min_outstanding + min_fee;
let min_bitcoin_lock_tx_fee = estimate_fee(min_outstanding).await?;
let min_deposit_until_swap_will_start = min_outstanding + min_bitcoin_lock_tx_fee;
let max_deposit_until_maximum_amount_is_reached =
maximum_amount - max_giveable + min_bitcoin_lock_tx_fee;
tracing::info!(
"Deposit at least {} to cover the min quantity with fee!",
min_deposit
min_deposit_until_swap_will_start
);
tracing::info!(
%deposit_address,
%min_deposit,
%min_deposit_until_swap_will_start,
%max_deposit_until_maximum_amount_is_reached,
%max_giveable,
%minimum_amount,
%maximum_amount,
%min_bitcoin_lock_tx_fee,
price = %bid_quote.price,
"Waiting for Bitcoin deposit",
);
@ -913,7 +1052,7 @@ where
tracing::info!(%new_balance, %max_giveable, "Received Bitcoin");
if max_giveable < bid_quote.min_quantity {
tracing::info!("Deposited amount is less than `min_quantity`");
tracing::info!("Deposited amount is not enough to cover `min_quantity` when accounting for network fees");
continue;
}

View File

@ -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};

View File

@ -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 },

View File

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

View File

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

View File

@ -38,8 +38,8 @@ pub async fn cancel(
// Alice already in final state
| AliceState::BtcRedeemed
| AliceState::XmrRefunded
| AliceState::BtcPunished
| AliceState::SafelyAborted => bail!("Swap is is in state {} which is not cancelable", state),
| AliceState::BtcPunished { .. }
| AliceState::SafelyAborted => bail!("Swap is in state {} which is not cancelable", state),
};
let txid = match state3.submit_tx_cancel(bitcoin_wallet.as_ref()).await {

View File

@ -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?;

View File

@ -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,

View File

@ -55,7 +55,7 @@ pub async fn refund(
AliceState::BtcRedeemTransactionPublished { .. }
| AliceState::BtcRedeemed
| AliceState::XmrRefunded
| AliceState::BtcPunished
| AliceState::BtcPunished { .. }
| AliceState::SafelyAborted => bail!(Error::SwapNotRefundable(state)),
};

View File

@ -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,

View File

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

View File

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

View File

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

View File

@ -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};

View File

@ -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),

View File

@ -1,5 +1,4 @@
use crate::bitcoin::wallet::Subscription;
use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Wallet};
use crate::bitcoin::{ExpiredTimelocks, Wallet};
use crate::protocol::bob::BobState;
use crate::protocol::Database;
use anyhow::{bail, Result};
@ -13,7 +12,7 @@ pub async fn cancel_and_refund(
db: Arc<dyn Database + Send + Sync>,
) -> Result<BobState> {
if let Err(err) = cancel(swap_id, bitcoin_wallet.clone(), db.clone()).await {
tracing::info!(%err, "Could not submit cancel transaction");
tracing::warn!(%err, "Could not cancel swap. Attempting to refund anyway");
};
let state = match refund(swap_id, bitcoin_wallet, db).await {
@ -21,7 +20,6 @@ pub async fn cancel_and_refund(
Err(e) => bail!(e),
};
tracing::info!("Refund transaction submitted");
Ok(state)
}
@ -29,12 +27,20 @@ pub async fn cancel(
swap_id: Uuid,
bitcoin_wallet: Arc<Wallet>,
db: Arc<dyn Database + Send + Sync>,
) -> Result<(Txid, Subscription, BobState)> {
) -> Result<(Txid, BobState)> {
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,
@ -47,34 +53,70 @@ pub async fn cancel(
| BobState::XmrRedeemed { .. }
| BobState::BtcPunished { .. }
| BobState::SafelyAborted => bail!(
"Cannot cancel swap {} because it is in state {} which is not refundable.",
"Cannot cancel swap {} because it is in state {} which is not cancellable.",
swap_id,
state
),
};
tracing::info!(%swap_id, "Manually cancelling swap");
tracing::info!(%swap_id, "Attempting to manually cancel swap");
let (txid, subscription) = match state6.submit_tx_cancel(bitcoin_wallet.as_ref()).await {
Ok(txid) => txid,
// Attempt to just publish the cancel transaction
match state6.submit_tx_cancel(bitcoin_wallet.as_ref()).await {
Ok((txid, _)) => {
let state = BobState::BtcCancelled(state6);
db.insert_latest_state(swap_id, state.clone().into())
.await?;
Ok((txid, state))
}
// If we fail to submit the cancel transaction it can have one of two reasons:
// 1. The cancel timelock hasn't expired yet
// 2. The cancel transaction has already been published by Alice
Err(err) => {
if let Ok(error_code) = parse_rpc_error_code(&err) {
tracing::debug!(%error_code, "parse rpc error");
if error_code == i64::from(RpcErrorCode::RpcVerifyAlreadyInChain) {
tracing::info!("Cancel transaction has already been confirmed on chain");
} else if error_code == i64::from(RpcErrorCode::RpcVerifyError) {
tracing::info!("General error trying to submit cancel transaction");
// Check if Alice has already published the cancel transaction while we were absent
if let Ok(tx) = state6.check_for_tx_cancel(bitcoin_wallet.as_ref()).await {
let state = BobState::BtcCancelled(state6);
db.insert_latest_state(swap_id, state.clone().into())
.await?;
tracing::info!("Alice has already cancelled the swap");
return Ok((tx.txid(), state));
}
// The cancel transaction has not been published yet and we failed to publish it ourselves
// Here we try to figure out why
match state6.expired_timelock(bitcoin_wallet.as_ref()).await {
// 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())
.await?;
tracing::info!("You have been punished for not refunding in time");
bail!(err.context("Cannot cancel swap because we have already been punished"));
}
// We cannot cancel because the cancel timelock has not expired yet
Ok(ExpiredTimelocks::None { blocks_left }) => {
bail!(err.context(
format!(
"Cannot cancel swap because the cancel timelock has not expired yet. Blocks left: {}",
blocks_left
)
));
}
Ok(ExpiredTimelocks::Cancel { .. }) => {
bail!(err.context("Failed to cancel swap even though cancel timelock has expired. This is unexpected."));
}
Err(timelock_err) => {
bail!(err
.context(timelock_err)
.context("Failed to cancel swap and could not check timelock status"));
}
}
bail!(err);
}
};
let state = BobState::BtcCancelled(state6);
db.insert_latest_state(swap_id, state.clone().into())
.await?;
Ok((txid, subscription, state))
}
}
pub async fn refund(
@ -85,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,
@ -104,12 +153,52 @@ pub async fn refund(
),
};
tracing::info!(%swap_id, "Manually refunding swap");
state6.publish_refund_btc(bitcoin_wallet.as_ref()).await?;
tracing::info!(%swap_id, "Attempting to manually refund swap");
let state = BobState::BtcRefunded(state6);
db.insert_latest_state(swap_id, state.clone().into())
.await?;
// Attempt to just publish the refund transaction
match state6.publish_refund_btc(bitcoin_wallet.as_ref()).await {
Ok(_) => {
let state = BobState::BtcRefunded(state6);
db.insert_latest_state(swap_id, state.clone().into())
.await?;
Ok(state)
Ok(state)
}
// If we fail to submit the refund transaction it can have one of two reasons:
// 1. The cancel transaction has not been published yet
// 2. The refund timelock has already expired and we have been punished
Err(bitcoin_publication_err) => {
match state6.expired_timelock(bitcoin_wallet.as_ref()).await {
// 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())
.await?;
tracing::info!("You have been punished for not refunding in time");
bail!(bitcoin_publication_err
.context("Cannot refund swap because we have already been punished"));
}
Ok(ExpiredTimelocks::None { blocks_left }) => {
bail!(
bitcoin_publication_err.context(format!(
"Cannot refund swap because the cancel timelock has not expired yet. Blocks left: {}",
blocks_left
))
);
}
Ok(ExpiredTimelocks::Cancel { .. }) => {
bail!(bitcoin_publication_err.context("Failed to refund swap even though cancel timelock has expired. This is unexpected."));
}
Err(e) => {
bail!(bitcoin_publication_err
.context(e)
.context("Failed to refund swap and could not check timelock status"));
}
}
}
}
}

View File

@ -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.")]

View File

@ -1,10 +1,12 @@
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;
use crate::protocol::bob::State2;
use crate::protocol::Database;
use anyhow::{Context, Result};
use futures::future::{BoxFuture, OptionFuture};
use futures::{FutureExt, StreamExt};
@ -13,6 +15,7 @@ use libp2p::swarm::dial_opts::DialOpts;
use libp2p::swarm::SwarmEvent;
use libp2p::{PeerId, Swarm};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;
@ -21,9 +24,11 @@ pub struct EventLoop {
swap_id: Uuid,
swarm: libp2p::Swarm<Behaviour>,
alice_peer_id: PeerId,
db: Arc<dyn Database + Send + Sync>,
// 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>>,
@ -33,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
@ -51,12 +56,13 @@ impl EventLoop {
swap_id: Uuid,
swarm: Swarm<Behaviour>,
alice_peer_id: PeerId,
db: Arc<dyn Database + Send + Sync>,
) -> Result<(Self, EventLoopHandle)> {
let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60));
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,
@ -64,17 +70,21 @@ 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,
};
let handle = EventLoopHandle {
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,
};
@ -108,44 +118,79 @@ impl EventLoop {
SwarmEvent::Behaviour(OutEvent::TransferProofReceived { msg, channel, peer }) => {
let swap_id = msg.swap_id;
if peer != self.alice_peer_id {
tracing::warn!(
%swap_id,
"Ignoring malicious transfer proof from {}, expected to receive it from {}",
peer,
self.alice_peer_id);
continue;
}
if swap_id != self.swap_id {
// TODO: Save unexpected transfer proofs in the database and check for messages in the database when handling swaps
tracing::warn!("Received unexpected transfer proof for swap {} while running swap {}. This transfer proof will be ignored", swap_id, self.swap_id);
// When receiving a transfer proof that is unexpected we still have to acknowledge that it was received
let _ = self.swarm.behaviour_mut().transfer_proof.send_response(channel, ());
continue;
}
let mut responder = match self.transfer_proof.send(msg.tx_lock_proof).await {
Ok(responder) => responder,
Err(e) => {
tracing::warn!("Failed to pass on transfer proof: {:#}", e);
continue;
if swap_id == self.swap_id {
if peer != self.alice_peer_id {
tracing::warn!(
%swap_id,
"Ignoring malicious transfer proof from {}, expected to receive it from {}",
peer,
self.alice_peer_id);
continue;
}
};
self.pending_transfer_proof = OptionFuture::from(Some(async move {
let _ = responder.recv().await;
let mut responder = match self.transfer_proof.send(msg.tx_lock_proof).await {
Ok(responder) => responder,
Err(e) => {
tracing::warn!("Failed to pass on transfer proof: {:#}", e);
continue;
}
};
channel
}.boxed()));
self.pending_transfer_proof = OptionFuture::from(Some(async move {
let _ = responder.recv().await;
channel
}.boxed()));
}else {
// Check if the transfer proof is sent from the correct peer and if we have a record of the swap
match self.db.get_peer_id(swap_id).await {
// We have a record of the swap
Ok(buffer_swap_alice_peer_id) => {
if buffer_swap_alice_peer_id == self.alice_peer_id {
// Save transfer proof in the database such that we can process it later when we resume the swap
match self.db.insert_buffered_transfer_proof(swap_id, msg.tx_lock_proof).await {
Ok(_) => {
tracing::info!("Received transfer proof for swap {} while running swap {}. Buffering this transfer proof in the database for later retrieval", swap_id, self.swap_id);
let _ = self.swarm.behaviour_mut().transfer_proof.send_response(channel, ());
}
Err(e) => {
tracing::error!("Failed to buffer transfer proof for swap {}: {:#}", swap_id, e);
}
};
}else {
tracing::warn!(
%swap_id,
"Ignoring malicious transfer proof from {}, expected to receive it from {}",
self.swap_id,
buffer_swap_alice_peer_id);
}
},
// We do not have a record of the swap or an error occurred while retrieving the peer id of Alice
Err(e) => {
if let Some(sqlx::Error::RowNotFound) = e.downcast_ref::<sqlx::Error>() {
tracing::warn!("Ignoring transfer proof for swap {} while running swap {}. We do not have a record of this swap", swap_id, self.swap_id);
} else {
tracing::error!("Ignoring transfer proof for swap {} while running swap {}. Failed to retrieve the peer id of Alice for the corresponding swap: {:#}", swap_id, self.swap_id, e);
}
}
}
}
}
SwarmEvent::Behaviour(OutEvent::EncryptedSignatureAcknowledged { id }) => {
if let Some(responder) = self.inflight_encrypted_signature_requests.remove(&id) {
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;
@ -204,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);
},
}
}
}
@ -220,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 {
@ -244,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,

View File

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

View File

@ -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
View 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);
}
}

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

View File

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

View File

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

View File

@ -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"),
}
}
}

View File

@ -1,30 +1,41 @@
use crate::database::Swap;
use crate::monero::Address;
use crate::monero::{Address, TransferProof};
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)
}
@ -303,6 +314,56 @@ impl Database for SqliteDatabase {
result
}
async fn insert_buffered_transfer_proof(
&self,
swap_id: Uuid,
proof: TransferProof,
) -> Result<()> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let proof = serde_json::to_string(&proof)?;
sqlx::query!(
r#"
INSERT INTO buffered_transfer_proofs (
swap_id,
proof
) VALUES (?, ?);
"#,
swap_id,
proof
)
.execute(&mut conn)
.await?;
Ok(())
}
async fn get_buffered_transfer_proof(&self, swap_id: Uuid) -> Result<Option<TransferProof>> {
let mut conn = self.pool.acquire().await?;
let swap_id = swap_id.to_string();
let row = sqlx::query!(
r#"
SELECT proof
FROM buffered_transfer_proofs
WHERE swap_id = ?
"#,
swap_id
)
.fetch_all(&mut conn)
.await?;
if row.is_empty() {
return Ok(None);
}
let proof_str = &row[0].proof;
let proof = serde_json::from_str(proof_str)?;
Ok(Some(proof))
}
async fn raw_all(&self) -> Result<HashMap<Uuid, Vec<serde_json::Value>>> {
let mut conn = self.pool.acquire().await?;
let rows = sqlx::query!(
@ -367,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();
@ -379,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();
@ -390,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]
@ -459,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)
}

View File

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

View File

@ -6,6 +6,7 @@ use ::monero::{Address, Network, PrivateKey, PublicKey};
use anyhow::{Context, Result};
use monero_rpc::wallet::{BlockHeight, MoneroWalletRpc as _, Refreshed};
use monero_rpc::{jsonrpc, wallet};
use std::ops::Div;
use std::str::FromStr;
use std::time::Duration;
use tokio::sync::Mutex;
@ -45,6 +46,7 @@ impl Wallet {
pub async fn connect(client: wallet::Client, name: String, env_config: Config) -> Result<Self> {
let main_address =
monero::Address::from_str(client.get_address(0).await?.address.as_str())?;
Ok(Self {
inner: Mutex::new(client),
network: env_config.monero_network,
@ -125,13 +127,14 @@ impl Wallet {
let temp_wallet_address =
Address::standard(self.network, public_spend_key, public_view_key);
let wallet = self.inner.lock().await;
// Close the default wallet before generating the other wallet to ensure that
// it saves its state correctly
let _ = wallet.close_wallet().await?;
let _ = self.inner.lock().await.close_wallet().await?;
let _ = wallet
let _ = self
.inner
.lock()
.await
.generate_from_keys(
file_name,
temp_wallet_address.to_string(),
@ -144,8 +147,14 @@ impl Wallet {
.await?;
// Try to send all the funds from the generated wallet to the default wallet
match wallet.refresh().await {
Ok(_) => match wallet.sweep_all(self.main_address.to_string()).await {
match self.refresh(3).await {
Ok(_) => match self
.inner
.lock()
.await
.sweep_all(self.main_address.to_string())
.await
{
Ok(sweep_all) => {
for tx in sweep_all.tx_hash_list {
tracing::info!(
@ -166,7 +175,12 @@ impl Wallet {
}
}
let _ = wallet.open_wallet(self.name.clone()).await?;
let _ = self
.inner
.lock()
.await
.open_wallet(self.name.clone())
.await?;
Ok(())
}
@ -220,7 +234,7 @@ impl Wallet {
let address = Address::standard(self.network, public_spend_key, public_view_key.into());
let check_interval = tokio::time::interval(self.sync_interval);
let check_interval = tokio::time::interval(self.sync_interval.div(10));
wait_for_confirmations(
&self.inner,
@ -261,8 +275,44 @@ impl Wallet {
self.main_address
}
pub async fn refresh(&self) -> Result<Refreshed> {
Ok(self.inner.lock().await.refresh().await?)
pub async fn refresh(&self, max_attempts: usize) -> Result<Refreshed> {
const RETRY_INTERVAL: Duration = Duration::from_secs(1);
for i in 1..=max_attempts {
tracing::info!(name = %self.name, attempt=i, "Syncing Monero wallet");
let result = self.inner.lock().await.refresh().await;
match result {
Ok(refreshed) => {
tracing::info!(name = %self.name, "Monero wallet synced");
return Ok(refreshed);
}
Err(error) => {
let attempts_left = max_attempts - i;
// We would not want to fail here if the height is not available
// as it is not critical for the operation of the wallet.
// We can just log a warning and continue.
let height = match self.inner.lock().await.get_height().await {
Ok(height) => height.to_string(),
Err(_) => {
tracing::warn!(name = %self.name, "Failed to fetch Monero wallet height during sync");
"unknown".to_string()
}
};
tracing::warn!(attempt=i, %height, %attempts_left, name = %self.name, %error, "Failed to sync Monero wallet");
if attempts_left == 0 {
return Err(error.into());
}
}
}
tokio::time::sleep(RETRY_INTERVAL).await;
}
unreachable!("Loop should have returned by now");
}
}

View File

@ -352,6 +352,7 @@ impl WalletRpc {
.arg("--disable-rpc-login")
.arg("--wallet-dir")
.arg(self.working_dir.join("monero-data"))
.arg("--no-initial-sync")
.spawn()?;
let stdout = child
@ -369,7 +370,7 @@ impl WalletRpc {
}
// If we do not hear from the monero_wallet_rpc process for 3 seconds we assume
// it is is ready
// it is ready
#[cfg(target_os = "windows")]
while let Ok(line) =
tokio::time::timeout(std::time::Duration::from_secs(3), reader.next_line()).await
@ -479,7 +480,7 @@ mod tests {
#[tokio::test]
async fn test_is_daemon_available_success() {
let mut server = mockito::Server::new();
let mut server = mockito::Server::new_async().await;
let _ = server
.mock("GET", "/get_info")
@ -510,7 +511,7 @@ mod tests {
#[tokio::test]
async fn test_is_daemon_available_wrong_network_failure() {
let mut server = mockito::Server::new();
let mut server = mockito::Server::new_async().await;
let _ = server
.mock("GET", "/get_info")
@ -541,7 +542,7 @@ mod tests {
#[tokio::test]
async fn test_is_daemon_available_not_synced_failure() {
let mut server = mockito::Server::new();
let mut server = mockito::Server::new_async().await;
let _ = server
.mock("GET", "/get_info")

View File

@ -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;

View 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);

View File

@ -1,6 +1,6 @@
use async_trait::async_trait;
use futures::stream::FusedStream;
use futures::{future, Future, Stream, StreamExt};
use futures::{future, Future, StreamExt};
use libp2p::core::muxing::StreamMuxerBox;
use libp2p::core::transport::upgrade::Version;
use libp2p::core::transport::MemoryTransport;
@ -75,8 +75,8 @@ async fn get_local_tcp_address() -> Multiaddr {
}
pub async fn await_events_or_timeout<A, B, E1, E2>(
swarm_1: &mut (impl Stream<Item = SwarmEvent<A, E1>> + FusedStream + Unpin),
swarm_2: &mut (impl Stream<Item = SwarmEvent<B, E2>> + FusedStream + Unpin),
swarm_1: &mut (impl FusedStream<Item = SwarmEvent<A, E1>> + FusedStream + Unpin),
swarm_2: &mut (impl FusedStream<Item = SwarmEvent<B, E2>> + FusedStream + Unpin),
) -> (SwarmEvent<A, E1>, SwarmEvent<B, E2>)
where
SwarmEvent<A, E1>: Debug,

View File

@ -146,4 +146,13 @@ pub trait Database {
async fn get_states(&self, swap_id: Uuid) -> Result<Vec<State>>;
async fn all(&self) -> Result<Vec<(Uuid, State)>>;
async fn raw_all(&self) -> Result<HashMap<Uuid, Vec<serde_json::Value>>>;
async fn insert_buffered_transfer_proof(
&self,
swap_id: Uuid,
proof: monero::TransferProof,
) -> Result<()>;
async fn get_buffered_transfer_proof(
&self,
swap_id: Uuid,
) -> Result<Option<monero::TransferProof>>;
}

View File

@ -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,

View File

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

View File

@ -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)]
@ -489,6 +505,27 @@ pub struct State4 {
}
impl State4 {
pub async fn check_for_tx_redeem(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<State5> {
let tx_redeem =
bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address, self.tx_redeem_fee);
let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin, tx_redeem.digest());
let tx_redeem_candidate = bitcoin_wallet.get_raw_transaction(tx_redeem.txid()).await?;
let tx_redeem_sig =
tx_redeem.extract_signature_by_key(tx_redeem_candidate, self.b.public())?;
let s_a = bitcoin::recover(self.S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)?;
let s_a = monero::private_key_from_secp256k1_scalar(s_a.into());
Ok(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,
})
}
pub fn tx_redeem_encsig(&self) -> bitcoin::EncryptedSignature {
let tx_redeem =
bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address, self.tx_redeem_fee);
@ -550,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,
@ -583,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)]
@ -590,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,
@ -625,18 +703,20 @@ impl State6 {
tx_cancel_status,
))
}
pub async fn check_for_tx_cancel(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<Transaction> {
let tx_cancel = bitcoin::TxCancel::new(
pub fn construct_tx_cancel(&self) -> Result<bitcoin::TxCancel> {
bitcoin::TxCancel::new(
&self.tx_lock,
self.cancel_timelock,
self.A,
self.b.public(),
self.tx_cancel_fee,
)?;
)
}
pub async fn check_for_tx_cancel(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<Transaction> {
let tx_cancel = self.construct_tx_cancel()?;
let tx = bitcoin_wallet.get_raw_transaction(tx_cancel.txid()).await?;
@ -647,15 +727,10 @@ impl State6 {
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<(Txid, Subscription)> {
let transaction = bitcoin::TxCancel::new(
&self.tx_lock,
self.cancel_timelock,
self.A,
self.b.public(),
self.tx_cancel_fee,
)?
.complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone())
.context("Failed to complete Bitcoin cancel transaction")?;
let transaction = self
.construct_tx_cancel()?
.complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone())
.context("Failed to complete Bitcoin cancel transaction")?;
let (tx_id, subscription) = bitcoin_wallet.broadcast(transaction, "cancel").await?;
@ -670,13 +745,7 @@ impl State6 {
}
pub fn signed_refund_transaction(&self) -> Result<Transaction> {
let tx_cancel = bitcoin::TxCancel::new(
&self.tx_lock,
self.cancel_timelock,
self.A,
self.b.public(),
self.tx_cancel_fee,
)?;
let tx_cancel = self.construct_tx_cancel()?;
let tx_refund =
bitcoin::TxRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee);
@ -694,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,
}
}
}

View File

@ -1,23 +1,33 @@
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;
use crate::protocol::bob::state::*;
use crate::protocol::{bob, Database};
use crate::{bitcoin, monero};
use anyhow::{bail, Context, Result};
use std::sync::Arc;
use tokio::select;
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
@ -27,13 +37,14 @@ 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(&current_state) {
current_state = next_state(
let next_state = next_state(
swap.id,
current_state.clone(),
&mut swap.event_loop_handle,
swap.db.clone(),
swap.bitcoin_wallet.as_ref(),
swap.monero_wallet.as_ref(),
swap.monero_receive_address,
@ -41,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(&current_state) && next_state == current_state {
break;
}
current_state = next_state;
}
Ok(current_state)
@ -52,6 +69,7 @@ async fn next_state(
swap_id: Uuid,
state: BobState,
event_loop_handle: &mut EventLoopHandle,
db: Arc<dyn Database + Send + Sync>,
bitcoin_wallet: &bitcoin::Wallet,
monero_wallet: &monero::Wallet,
monero_receive_address: monero::Address,
@ -118,12 +136,28 @@ async fn next_state(
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
if let ExpiredTimelocks::None { .. } = state3.expired_timelock(bitcoin_wallet).await? {
tracing::info!("Waiting for Alice to lock Monero");
let buffered_transfer_proof = db
.get_buffered_transfer_proof(swap_id)
.await
.context("Failed to get buffered transfer proof")?;
if let Some(transfer_proof) = buffered_transfer_proof {
tracing::debug!(txid = %transfer_proof.tx_hash(), "Found buffered transfer proof");
tracing::info!(txid = %transfer_proof.tx_hash(), "Alice locked Monero");
return Ok(BobState::XmrLockProofReceived {
state: state3,
lock_transfer_proof: transfer_proof,
monero_wallet_restore_blockheight,
});
}
let transfer_proof_watcher = event_loop_handle.recv_transfer_proof();
let cancel_timelock_expires =
tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock);
tracing::info!("Waiting for Alice to lock Monero");
select! {
transfer_proof = transfer_proof_watcher => {
let transfer_proof = transfer_proof?;
@ -140,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)
}
}
@ -169,20 +203,27 @@ 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) => {
// In case we send the encrypted signature to Alice, but she doesn't give us a confirmation
// We need to check if she still published the Bitcoin redeem transaction
// Otherwise we risk staying stuck in "XmrLocked"
if let Ok(state5) = state.check_for_tx_redeem(bitcoin_wallet).await {
return Ok(BobState::BtcRedeemed(state5));
}
let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await;
if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? {
@ -207,6 +248,13 @@ async fn next_state(
}
}
BobState::EncSigSent(state) => {
// We need to make sure that Alice did not publish the redeem transaction while we were offline
// Even if the cancel timelock expired, if Alice published the redeem transaction while we were away we cannot miss it
// If we do we cannot refund and will never be able to leave the "CancelTimelockExpired" state
if let Ok(state5) = state.check_for_tx_redeem(bitcoin_wallet).await {
return Ok(BobState::BtcRedeemed(state5));
}
let tx_lock_status = bitcoin_wallet.subscribe_to(state.tx_lock.clone()).await;
if let ExpiredTimelocks::None { .. } = state.expired_timelock(bitcoin_wallet).await? {
@ -224,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 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().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(),
@ -285,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 },
})

View File

@ -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(|| {

View File

@ -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::*;

View File

@ -50,7 +50,7 @@ async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() {
// Bob manually cancels
bob_join_handle.abort();
let (_, _, state) = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db).await?;
let (_, state) = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db).await?;
assert!(matches!(state, BobState::BtcCancelled { .. }));
let (bob_swap, bob_join_handle) = ctx

View File

@ -42,10 +42,10 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
let error = cli::cancel(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db)
.await
.unwrap_err();
assert_eq!(
parse_rpc_error_code(&error).unwrap(),
i64::from(RpcErrorCode::RpcVerifyRejected)
);
assert!(error
.to_string()
.contains("Cannot cancel swap because the cancel timelock has not expired yet"));
ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await;
@ -72,10 +72,9 @@ async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors()
let error = cli::refund(bob_swap.id, bob_swap.bitcoin_wallet, bob_swap.db)
.await
.unwrap_err();
assert_eq!(
parse_rpc_error_code(&error).unwrap(),
i64::from(RpcErrorCode::RpcVerifyError)
);
assert!(error
.to_string()
.contains("Cannot refund swap because the cancel timelock has not expired yet"));
let (bob_swap, _) = ctx
.stop_and_resume_bob_from_db(bob_join_handle, swap_id)

View File

@ -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;

View File

@ -0,0 +1,98 @@
pub mod harness;
use harness::alice_run_until::is_xmr_lock_transaction_sent;
use harness::bob_run_until::is_btc_locked;
use harness::FastPunishConfig;
use swap::asb;
use swap::asb::FixedRate;
use swap::cli;
use swap::protocol::alice::AliceState;
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 using the cancel and
/// punish command. Then Bob tries to refund.
#[tokio::test]
async fn alice_manually_punishes_after_bob_dead_and_bob_cancels() {
harness::setup_test(FastPunishConfig, |mut ctx| async move {
let (bob_swap, bob_join_handle) = ctx.bob_swap().await;
let bob_swap_id = bob_swap.id;
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
let alice_swap = ctx.alice_next_swap().await;
let alice_bitcoin_wallet = alice_swap.bitcoin_wallet.clone();
let alice_swap = tokio::spawn(alice::run_until(
alice_swap,
is_xmr_lock_transaction_sent,
FixedRate::default(),
));
let bob_state = bob_swap.await??;
assert!(matches!(bob_state, BobState::BtcLocked { .. }));
let alice_state = alice_swap.await??;
// Ensure cancel timelock is expired
if let AliceState::XmrLockTransactionSent { state3, .. } = alice_state {
alice_bitcoin_wallet
.subscribe_to(state3.tx_lock)
.await
.wait_until_confirmed_with(state3.cancel_timelock)
.await?;
} else {
panic!("Alice in unexpected state {}", alice_state);
}
// manual cancel (required to be able to punish)
ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await;
let (_, alice_state) =
asb::cancel(alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db).await?;
// Ensure punish timelock is expired
if let AliceState::BtcCancelled { state3, .. } = alice_state {
alice_bitcoin_wallet
.subscribe_to(state3.tx_cancel())
.await
.wait_until_confirmed_with(state3.punish_timelock)
.await?;
} else {
panic!("Alice in unexpected state {}", alice_state);
}
// manual punish
ctx.restart_alice().await;
let alice_swap = ctx.alice_next_swap().await;
let (_, alice_state) =
asb::punish(alice_swap.swap_id, alice_swap.bitcoin_wallet, alice_swap.db).await?;
ctx.assert_alice_punished(alice_state).await;
// Bob is in wrong state.
let (bob_swap, bob_join_handle) = ctx
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
.await;
assert!(matches!(bob_swap.state, BobState::BtcLocked { .. }));
bob_join_handle.abort();
let (_, state) = cli::cancel(bob_swap_id, bob_swap.bitcoin_wallet, bob_swap.db).await?;
// Bob should be in BtcCancelled state now.
assert!(matches!(state, BobState::BtcCancelled { .. }));
let (bob_swap, _) = ctx
.stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id)
.await;
assert!(matches!(bob_swap.state, BobState::BtcCancelled { .. }));
// Alice punished Bob, so he should be in the BtcPunished state.
let error = cli::refund(bob_swap_id, bob_swap.bitcoin_wallet, bob_swap.db)
.await
.unwrap_err();
assert_eq!(
error.to_string(),
"Cannot refund swap because we have already been punished"
);
Ok(())
})
.await;
}

View File

@ -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;

View File

@ -32,8 +32,7 @@ async fn concurrent_bobs_before_xmr_lock_proof_sent() {
let alice_swap_2 = tokio::spawn(alice::run(alice_swap_2, FixedRate::default()));
// The 2nd swap ALWAYS finish successfully in this
// scenario, but will receive an "unwanted" transfer proof that is ignored in
// the event loop.
// scenario, but will receive an "unwanted" transfer proof that is buffered until the 1st swap is resumed
let bob_state_2 = bob_swap_2.await??;
assert!(matches!(bob_state_2, BobState::XmrRedeemed { .. }));
@ -46,15 +45,13 @@ async fn concurrent_bobs_before_xmr_lock_proof_sent() {
.await;
assert!(matches!(bob_state_1, BobState::BtcLocked { .. }));
// The 1st (paused) swap is expected to refund, because the transfer
// proof is delivered to the wrong swap, and we currently don't store it in the
// database for the other swap.
// The 1st (paused) swap is expected to finish successfully because the transfer proof is buffered when it is receives while another swap is running.
let bob_state_1 = bob::run(bob_swap_1).await?;
assert!(matches!(bob_state_1, BobState::BtcRefunded { .. }));
assert!(matches!(bob_state_1, BobState::XmrRedeemed { .. }));
let alice_state_1 = alice_swap_1.await??;
assert!(matches!(alice_state_1, AliceState::XmrRefunded { .. }));
assert!(matches!(alice_state_1, AliceState::BtcRedeemed { .. }));
Ok(())
})

View File

@ -0,0 +1,44 @@
pub mod harness;
use crate::harness::bob_run_until::is_encsig_sent;
use swap::asb::FixedRate;
use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob};
use tokio::join;
#[tokio::test]
async fn given_bob_restarts_while_alice_redeems_btc() {
harness::setup_test(harness::SlowCancelConfig, |mut ctx| async move {
let (bob_swap, bob_handle) = ctx.bob_swap().await;
let swap_id = bob_swap.id;
let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_encsig_sent));
let alice_swap = ctx.alice_next_swap().await;
let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default()));
let (bob_state, alice_state) = join!(bob_swap, alice_swap);
ctx.assert_alice_redeemed(alice_state??).await;
assert!(matches!(bob_state??, BobState::EncSigSent { .. }));
let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_handle, swap_id).await;
if let BobState::EncSigSent(state4) = bob_swap.state.clone() {
bob_swap
.bitcoin_wallet
.subscribe_to(state4.tx_lock)
.await
.wait_until_confirmed_with(state4.cancel_timelock)
.await?;
} else {
panic!("Bob in unexpected state {}", bob_swap.state);
}
// Restart Bob
let bob_state = bob::run(bob_swap).await?;
ctx.assert_bob_redeemed(bob_state).await;
Ok(())
})
.await;
}

View File

@ -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);
@ -427,18 +431,18 @@ impl BobParams {
}
pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, cli::EventLoop)> {
let (event_loop, handle) = self.new_eventloop(swap_id).await?;
if let Some(parent_dir) = self.db_path.parent() {
ensure_directory_exists(parent_dir)?;
}
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?;
let swap = bob::Swap::from_db(
db,
db.clone(),
swap_id,
self.bitcoin_wallet.clone(),
self.monero_wallet.clone(),
@ -457,15 +461,15 @@ impl BobParams {
) -> Result<(bob::Swap, cli::EventLoop)> {
let swap_id = Uuid::new_v4();
let (event_loop, handle) = self.new_eventloop(swap_id).await?;
if let Some(parent_dir) = self.db_path.parent() {
ensure_directory_exists(parent_dir)?;
}
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?;
db.insert_peer_id(swap_id, self.alice_peer_id).await?;
@ -487,6 +491,7 @@ impl BobParams {
pub async fn new_eventloop(
&self,
swap_id: Uuid,
db: Arc<dyn Database + Send + Sync>,
) -> Result<(cli::EventLoop, cli::EventLoopHandle)> {
let tor_socks5_port = get_port()
.expect("We don't care about Tor in the tests so we get a free port to disable it.");
@ -503,7 +508,7 @@ impl BobParams {
.behaviour_mut()
.add_address(self.alice_peer_id, self.alice_address.clone());
cli::EventLoop::new(swap_id, swarm, self.alice_peer_id)
cli::EventLoop::new(swap_id, swarm, self.alice_peer_id, db.clone())
}
}
@ -651,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(),
@ -697,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
@ -818,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);
@ -891,7 +896,7 @@ impl Wallet for monero::Wallet {
type Amount = monero::Amount;
async fn refresh(&self) -> Result<()> {
self.refresh().await?;
self.refresh(1).await?;
Ok(())
}
@ -996,6 +1001,10 @@ pub mod alice_run_until {
pub fn is_encsig_learned(state: &AliceState) -> bool {
matches!(state, AliceState::EncSigLearned { .. })
}
pub fn is_btc_redeemed(state: &AliceState) -> bool {
matches!(state, AliceState::BtcRedeemed { .. })
}
}
pub mod bob_run_until {

View File

@ -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;

View File

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

View 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-----