mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge #780
780: Sqlite database r=rishflab a=rishflab Closes #427, #762, #770 Co-authored-by: rishflab <rishflab@hotmail.com>
This commit is contained in:
commit
9ea73a8e66
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -104,7 +104,6 @@ jobs:
|
||||
alice_manually_punishes_after_bob_dead,
|
||||
alice_refunds_after_restart_bob_refunded,
|
||||
ensure_same_swap_id,
|
||||
concurrent_bobs_after_xmr_lock_proof_sent,
|
||||
concurrent_bobs_before_xmr_lock_proof_sent,
|
||||
alice_manually_redeems_after_enc_sig_learned
|
||||
]
|
||||
|
32
CHANGELOG.md
32
CHANGELOG.md
@ -14,10 +14,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
The force flag was used to ignore blockheight and protocol state checks.
|
||||
Users can still restart a swap with these checks using the `resume` subcommand.
|
||||
- Changed log level of the "Advancing state", "Establishing Connection through Tor proxy" and "Connection through Tor established" log message from tracing to debug in the CLI.
|
||||
- ASB and CLI can migrate their data to sqlite to store swaps and related data.
|
||||
This makes it easier to build applications on top of xmr-btc-swap by enabling developers to read swap information directly from the database.
|
||||
This resolved an issue where users where unable to run concurrent processes, for example, users could not print the swap history if another ASB or CLI process was running.
|
||||
The sqlite database filed is named `sqlite` and is found in the data directory.
|
||||
You can print the data directory using the `config` subcommand.
|
||||
The schema can be found here [here](swap/migrations/20210903050345_create_swaps_table.sql).
|
||||
|
||||
#### Database migration guide
|
||||
|
||||
##### Delete old data
|
||||
|
||||
The simplest way to migrate is to accept the loss of data and delete the old database.
|
||||
|
||||
1. Find the location of the old database using the `config` subcommand.
|
||||
2. Delete the database
|
||||
3. Run xmr-btc-swap
|
||||
xmr-btc swap will create a new sqlite database and use that from now on.
|
||||
|
||||
##### Preserve old data
|
||||
|
||||
It is possible to migrate critical data from the old db to the sqlite but there are many pitfalls.
|
||||
|
||||
1. Run xmr-btc-swap as you would normally
|
||||
xmr-btc-swap will try and automatically migrate your existing data to the new database.
|
||||
If the existing database contains swaps for very early releases, the migration will fail due to an incompatible schema.
|
||||
2. Print out the swap history using the `history` subcommand.
|
||||
3. Print out the swap history stored in the old database by also passing the `--sled` flag.
|
||||
eg. `swap-cli --sled history`
|
||||
4. Compare the old and new history to see if you are happy with migration.
|
||||
5. If you are unhappy with the new history you can continue to use the old database by passing the `--sled flag`
|
||||
|
||||
### Added
|
||||
|
||||
- Added a `disable-timestamp` flag to the ASB that disables timestamps from logs.
|
||||
- A `config` subcommand that prints the current configuration including the data directory location.
|
||||
This feature should alleviate difficulties users were having when finding where xmr-btc-swap was storing data.
|
||||
|
||||
## [0.8.3] - 2021-09-03
|
||||
|
||||
|
248
Cargo.lock
generated
248
Cargo.lock
generated
@ -49,6 +49,17 @@ version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
|
||||
dependencies = [
|
||||
"getrandom 0.2.2",
|
||||
"once_cell",
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
@ -128,6 +139,15 @@ dependencies = [
|
||||
"pin-project-lite 0.2.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic"
|
||||
version = "0.5.0"
|
||||
@ -610,7 +630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"nom 5.1.2",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
@ -691,6 +711,21 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10c2722795460108a7872e1cd933a85d6ec38abc4baecad51028f702da28889f"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
@ -723,6 +758,16 @@ dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
@ -937,6 +982,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa_fun"
|
||||
version = "0.6.2-alpha.0"
|
||||
@ -977,6 +1028,9 @@ name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "electrum-client"
|
||||
@ -1175,6 +1229,17 @@ dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-intrusive"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"lock_api 0.4.5",
|
||||
"parking_lot 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.17"
|
||||
@ -1362,7 +1427,25 @@ version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ahash 0.4.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1614,7 +1697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"hashbrown",
|
||||
"hashbrown 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2088,6 +2171,17 @@ dependencies = [
|
||||
"libsecp256k1-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.2"
|
||||
@ -2148,7 +2242,7 @@ version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f374d42cdfc1d7dbf3d3dec28afab2eb97ffbf43a3234d795b5986dbf4b90ba"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"hashbrown 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2231,6 +2325,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c835948974f68e0bd58636fc6c5b1fbff7b297e3046f11b3b3c18bbac012c6d"
|
||||
|
||||
[[package]]
|
||||
name = "miniscript"
|
||||
version = "5.1.0"
|
||||
@ -2429,6 +2529,17 @@ dependencies = [
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
@ -3536,6 +3647,7 @@ version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@ -3804,12 +3916,122 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "sqlformat"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4"
|
||||
dependencies = [
|
||||
"itertools 0.10.1",
|
||||
"nom 7.0.0",
|
||||
"unicode_categories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4b94ab0f8c21ee4899b93b06451ef5d965f1a355982ee73684338228498440"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec28b91a01e1fe286d6ba66f68289a2286df023fc97444e1fd86c2fd6d5dc026"
|
||||
dependencies = [
|
||||
"ahash 0.7.4",
|
||||
"atoi",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"bytes 1.0.1",
|
||||
"crc",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"either",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-intrusive",
|
||||
"futures-util",
|
||||
"hashlink",
|
||||
"hex 0.4.3",
|
||||
"itoa",
|
||||
"libc",
|
||||
"libsqlite3-sys",
|
||||
"log 0.4.14",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"parking_lot 0.11.2",
|
||||
"percent-encoding 2.1.0",
|
||||
"rustls 0.19.0",
|
||||
"serde",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
"sqlx-rt",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"tokio-stream",
|
||||
"url 2.2.2",
|
||||
"webpki",
|
||||
"webpki-roots 0.21.0",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dc33c35d54774eed73d54568d47a6ac099aed8af5e1556a017c131be88217d5"
|
||||
dependencies = [
|
||||
"dotenv",
|
||||
"either",
|
||||
"futures",
|
||||
"heck",
|
||||
"hex 0.4.3",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx-core",
|
||||
"sqlx-rt",
|
||||
"syn",
|
||||
"url 2.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-rt"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14302b678d9c76b28f2e60115211e25e0aabc938269991745a169753dc00e35c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
@ -3906,6 +4128,7 @@ dependencies = [
|
||||
"ed25519-dalek",
|
||||
"futures",
|
||||
"get-port",
|
||||
"hex 0.4.3",
|
||||
"hyper 0.14.12",
|
||||
"itertools 0.10.1",
|
||||
"libp2p",
|
||||
@ -3930,6 +4153,7 @@ dependencies = [
|
||||
"sigma_fun",
|
||||
"sled",
|
||||
"spectral",
|
||||
"sqlx",
|
||||
"structopt",
|
||||
"strum",
|
||||
"tempfile",
|
||||
@ -4508,6 +4732,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.4.0"
|
||||
@ -4790,6 +5020,16 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7741161a40200a867c96dfa5574544efa4178cf4c8f770b62dd1cc0362d7ae1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.4.3"
|
||||
|
@ -17,7 +17,6 @@ status = [
|
||||
"docker_tests (alice_manually_punishes_after_bob_dead)",
|
||||
"docker_tests (alice_refunds_after_restart_bob_refunded)",
|
||||
"docker_tests (ensure_same_swap_id)",
|
||||
"docker_tests (concurrent_bobs_after_xmr_lock_proof_sent)",
|
||||
"docker_tests (concurrent_bobs_before_xmr_lock_proof_sent)",
|
||||
"docker_tests (alice_manually_redeems_after_enc_sig_learned)"
|
||||
]
|
||||
|
@ -29,6 +29,7 @@ directories-next = "2"
|
||||
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde" ] }
|
||||
ed25519-dalek = "1"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
hex = "0.4"
|
||||
itertools = "0.10"
|
||||
libp2p = { git = "https://github.com/comit-network/rust-libp2p", branch = "rendezvous", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous" ] }
|
||||
miniscript = { version = "5", features = [ "serde" ] }
|
||||
@ -49,6 +50,7 @@ serde_with = { version = "1", features = [ "macros" ] }
|
||||
sha2 = "0.9"
|
||||
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] }
|
||||
sled = "0.34"
|
||||
sqlx = { version = "0.5", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] }
|
||||
structopt = "0.3"
|
||||
strum = { version = "0.21", features = [ "derive" ] }
|
||||
thiserror = "1"
|
||||
|
25
swap/migrations/20210903050345_create_swaps_table.sql
Normal file
25
swap/migrations/20210903050345_create_swaps_table.sql
Normal file
@ -0,0 +1,25 @@
|
||||
CREATE TABLE if NOT EXISTS swap_states
|
||||
(
|
||||
id INTEGER PRIMARY KEY autoincrement NOT NULL,
|
||||
swap_id TEXT NOT NULL,
|
||||
entered_at TEXT NOT NULL,
|
||||
state TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE if NOT EXISTS monero_addresses
|
||||
(
|
||||
swap_id TEXT PRIMARY KEY NOT NULL,
|
||||
address TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE if NOT EXISTS peers
|
||||
(
|
||||
swap_id TEXT PRIMARY KEY NOT NULL,
|
||||
peer_id TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE if NOT EXISTS peer_addresses
|
||||
(
|
||||
peer_id TEXT NOT NULL,
|
||||
address TEXT NOT NULL
|
||||
);
|
6
swap/sqlite_dev_setup.sh
Normal file
6
swap/sqlite_dev_setup.sh
Normal file
@ -0,0 +1,6 @@
|
||||
# crated temporary DB
|
||||
# run the migration scripts to create the tables
|
||||
# prepare the sqlx-data.json rust mappings
|
||||
DATABASE_URL=sqlite:tempdb cargo sqlx database create
|
||||
DATABASE_URL=sqlite:tempdb cargo sqlx migrate run
|
||||
DATABASE_URL=sqlite:./swap/tempdb cargo sqlx prepare -- --bin swap
|
139
swap/sqlx-data.json
Normal file
139
swap/sqlx-data.json
Normal file
@ -0,0 +1,139 @@
|
||||
{
|
||||
"db": "SQLite",
|
||||
"081c729a0f1ad6e4ff3e13d6702c946bc4d37d50f40670b4f51d2efcce595aa6": {
|
||||
"query": "\n SELECT peer_id\n FROM peers\n WHERE swap_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "peer_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"0ab84c094964968e96a3f2bf590d9ae92227d057386921e0e57165b887de3c75": {
|
||||
"query": "\n insert into peer_addresses (\n peer_id,\n address\n ) values (?, ?);\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"1ec38c85e7679b2eb42b3df75d9098772ce44fdb8db3012d3c2410d828b74157": {
|
||||
"query": "\n SELECT swap_id, state\n FROM (\n SELECT max(id), swap_id, state\n FROM swap_states\n GROUP BY swap_id\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "swap_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"2a356078a41b321234adf2aa385b501749f907f7c422945a8bdda2b6274f5225": {
|
||||
"query": "\n insert into peers (\n swap_id,\n peer_id\n ) values (?, ?);\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"50a5764546f69c118fa0b64120da50f51073d36257d49768de99ff863e3511e0": {
|
||||
"query": "\n insert into monero_addresses (\n swap_id,\n address\n ) values (?, ?);\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"88f761a4f7a0429cad1df0b1bebb1c0a27b2a45656549b23076d7542cfa21ecf": {
|
||||
"query": "\n SELECT state\n FROM swap_states\n WHERE swap_id = ?\n ORDER BY id desc\n LIMIT 1;\n\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "state",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"a0eb85d04ee3842c52291dad4d225941d1141af735922fcbc665868997fce304": {
|
||||
"query": "\n SELECT address\n FROM peer_addresses\n WHERE peer_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "address",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"b703032b4ddc627a1124817477e7a8e5014bdc694c36a14053ef3bb2fc0c69b0": {
|
||||
"query": "\n insert into swap_states (\n swap_id,\n entered_at,\n state\n ) values (?, ?, ?);\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"ce270dd4a4b9615695a79864240c5401e2122077365e5e5a19408c068c7f9454": {
|
||||
"query": "\n SELECT address\n FROM monero_addresses\n WHERE swap_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "address",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ where
|
||||
let json = args.json;
|
||||
let disable_timestamp = args.disable_timestamp;
|
||||
let testnet = args.testnet;
|
||||
let sled = args.sled;
|
||||
let config = args.config;
|
||||
let command: RawCommand = args.cmd;
|
||||
|
||||
@ -29,6 +30,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Start { resume_only },
|
||||
@ -37,6 +39,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::History,
|
||||
@ -45,6 +48,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::WithdrawBtc {
|
||||
@ -56,10 +60,20 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Balance,
|
||||
},
|
||||
RawCommand::Config => Arguments {
|
||||
testnet,
|
||||
json,
|
||||
sled,
|
||||
disable_timestamp,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Config,
|
||||
},
|
||||
RawCommand::ManualRecovery(ManualRecovery::Redeem {
|
||||
redeem_params: RecoverCommandParams { swap_id },
|
||||
do_not_await_finality,
|
||||
@ -67,6 +81,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Redeem {
|
||||
@ -81,6 +96,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Cancel { swap_id },
|
||||
@ -91,6 +107,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Refund { swap_id },
|
||||
@ -101,6 +118,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::Punish { swap_id },
|
||||
@ -109,6 +127,7 @@ where
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path: config_path(config, testnet)?,
|
||||
env_config: env_config(testnet),
|
||||
cmd: Command::SafelyAbort { swap_id },
|
||||
@ -168,6 +187,7 @@ pub struct BitcoinAddressNetworkMismatch {
|
||||
pub struct Arguments {
|
||||
pub testnet: bool,
|
||||
pub json: bool,
|
||||
pub sled: bool,
|
||||
pub disable_timestamp: bool,
|
||||
pub config_path: PathBuf,
|
||||
pub env_config: env::Config,
|
||||
@ -180,6 +200,7 @@ pub enum Command {
|
||||
resume_only: bool,
|
||||
},
|
||||
History,
|
||||
Config,
|
||||
WithdrawBtc {
|
||||
amount: Option<Amount>,
|
||||
address: Address,
|
||||
@ -228,6 +249,13 @@ pub struct RawArguments {
|
||||
)]
|
||||
pub disable_timestamp: bool,
|
||||
|
||||
#[structopt(
|
||||
short,
|
||||
long = "sled",
|
||||
help = "Forces the asb to use the deprecated sled db if it is available"
|
||||
)]
|
||||
pub sled: bool,
|
||||
|
||||
#[structopt(
|
||||
long = "config",
|
||||
help = "Provide a custom path to the configuration file. The configuration file must be a toml file.",
|
||||
@ -252,6 +280,8 @@ pub enum RawCommand {
|
||||
},
|
||||
#[structopt(about = "Prints swap-id and the state of each swap ever made.")]
|
||||
History,
|
||||
#[structopt(about = "Prints the current config")]
|
||||
Config,
|
||||
#[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")]
|
||||
WithdrawBtc {
|
||||
#[structopt(
|
||||
@ -344,6 +374,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -362,6 +393,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -380,6 +412,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -402,6 +435,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -429,6 +463,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -455,6 +490,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -481,6 +517,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -507,6 +544,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
@ -527,6 +565,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -545,6 +584,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -563,6 +603,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -587,6 +628,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -614,6 +656,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -641,6 +684,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -668,6 +712,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -695,6 +740,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: true,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: false,
|
||||
config_path: default_testnet_conf_path,
|
||||
env_config: testnet_env_config,
|
||||
@ -715,6 +761,7 @@ mod tests {
|
||||
let expected_args = Arguments {
|
||||
testnet: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
disable_timestamp: true,
|
||||
config_path: default_mainnet_conf_path,
|
||||
env_config: mainnet_env_config,
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::asb::{Behaviour, OutEvent, Rate};
|
||||
use crate::database::Database;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::swap_setup::alice::WalletSnapshot;
|
||||
use crate::network::transfer_proof;
|
||||
use crate::protocol::alice::{AliceState, State3, Swap};
|
||||
use crate::protocol::{Database, State};
|
||||
use crate::{bitcoin, env, kraken, monero};
|
||||
use anyhow::{Context, Result};
|
||||
use futures::future;
|
||||
@ -14,7 +14,7 @@ use libp2p::swarm::SwarmEvent;
|
||||
use libp2p::{PeerId, Swarm};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::convert::{Infallible, TryInto};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
@ -39,7 +39,7 @@ where
|
||||
env_config: env::Config,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
db: Arc<Database>,
|
||||
db: Arc<dyn Database + Send + Sync>,
|
||||
latest_rate: LR,
|
||||
min_buy: bitcoin::Amount,
|
||||
max_buy: bitcoin::Amount,
|
||||
@ -71,7 +71,7 @@ where
|
||||
env_config: env::Config,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
db: Arc<Database>,
|
||||
db: Arc<dyn Database + Send + Sync>,
|
||||
latest_rate: LR,
|
||||
min_buy: bitcoin::Amount,
|
||||
max_buy: bitcoin::Amount,
|
||||
@ -108,16 +108,21 @@ where
|
||||
self.inflight_encrypted_signatures
|
||||
.push(future::pending().boxed());
|
||||
|
||||
let unfinished_swaps = match self.db.unfinished_alice() {
|
||||
Ok(unfinished_swaps) => unfinished_swaps,
|
||||
Err(_) => {
|
||||
tracing::error!("Failed to load unfinished swaps");
|
||||
let swaps = match self.db.all().await {
|
||||
Ok(swaps) => swaps,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to load swaps from database: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let unfinished_swaps = swaps
|
||||
.into_iter()
|
||||
.filter(|(_swap_id, state)| !state.swap_finished())
|
||||
.collect::<Vec<(Uuid, State)>>();
|
||||
|
||||
for (swap_id, state) in unfinished_swaps {
|
||||
let peer_id = match self.db.get_peer_id(swap_id) {
|
||||
let peer_id = match self.db.get_peer_id(swap_id).await {
|
||||
Ok(peer_id) => peer_id,
|
||||
Err(_) => {
|
||||
tracing::warn!(%swap_id, "Resuming swap skipped because no peer-id found for swap in database");
|
||||
@ -133,7 +138,7 @@ where
|
||||
monero_wallet: self.monero_wallet.clone(),
|
||||
env_config: self.env_config,
|
||||
db: self.db.clone(),
|
||||
state: state.into(),
|
||||
state: state.try_into().expect("Alice state loaded from db"),
|
||||
swap_id,
|
||||
};
|
||||
|
||||
@ -197,7 +202,7 @@ where
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::EncryptedSignatureReceived{ msg, channel, peer }) => {
|
||||
let swap_id = msg.swap_id;
|
||||
let swap_peer = self.db.get_peer_id(swap_id);
|
||||
let swap_peer = self.db.get_peer_id(swap_id).await;
|
||||
|
||||
// Ensure that an incoming encrypted signature is sent by the peer-id associated with the swap
|
||||
let swap_peer = match swap_peer {
|
||||
|
@ -1,16 +1,17 @@
|
||||
use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Txid, Wallet};
|
||||
use crate::database::{Database, Swap};
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn cancel(
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<Wallet>,
|
||||
db: Arc<Database>,
|
||||
db: Arc<dyn Database>,
|
||||
) -> Result<(Txid, AliceState)> {
|
||||
let state = db.get_state(swap_id)?.try_into_alice()?.into();
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
let (monero_wallet_restore_blockheight, transfer_proof, state3) = match state {
|
||||
|
||||
@ -58,8 +59,7 @@ pub async fn cancel(
|
||||
transfer_proof,
|
||||
state3,
|
||||
};
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok((txid, state))
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::bitcoin::{self, Txid};
|
||||
use crate::database::{Database, Swap};
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -14,9 +15,9 @@ pub enum Error {
|
||||
pub async fn punish(
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
db: Arc<Database>,
|
||||
db: Arc<dyn Database>,
|
||||
) -> Result<(Txid, AliceState)> {
|
||||
let state = db.get_state(swap_id)?.try_into_alice()?.into();
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
let state3 = match state {
|
||||
// Punish potentially possible (no knowledge of cancel transaction)
|
||||
@ -46,8 +47,7 @@ pub async fn punish(
|
||||
let txid = state3.punish_btc(&bitcoin_wallet).await?;
|
||||
|
||||
let state = AliceState::BtcPunished;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok((txid, state))
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::bitcoin::{Txid, Wallet};
|
||||
use crate::database::{Database, Swap};
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -23,10 +24,10 @@ impl Finality {
|
||||
pub async fn redeem(
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<Wallet>,
|
||||
db: Arc<Database>,
|
||||
db: Arc<dyn Database>,
|
||||
finality: Finality,
|
||||
) -> Result<(Txid, AliceState)> {
|
||||
let state = db.get_state(swap_id)?.try_into_alice()?.into();
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
match state {
|
||||
AliceState::EncSigLearned {
|
||||
@ -42,17 +43,14 @@ pub async fn redeem(
|
||||
subscription.wait_until_seen().await?;
|
||||
|
||||
let state = AliceState::BtcRedeemTransactionPublished { state3 };
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
.await?;
|
||||
db.insert_latest_state(swap_id, state.into()).await?;
|
||||
|
||||
if let Finality::Await = finality {
|
||||
subscription.wait_until_final().await?;
|
||||
}
|
||||
|
||||
let state = AliceState::BtcRedeemed;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok((txid, state))
|
||||
@ -64,8 +62,7 @@ pub async fn redeem(
|
||||
}
|
||||
|
||||
let state = AliceState::BtcRedeemed;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
let txid = state3.tx_redeem().txid();
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::bitcoin::{self};
|
||||
use crate::database::{Database, Swap};
|
||||
use crate::monero;
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{bail, Result};
|
||||
use libp2p::PeerId;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -26,9 +27,9 @@ pub async fn refund(
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
db: Arc<Database>,
|
||||
db: Arc<dyn Database>,
|
||||
) -> Result<AliceState> {
|
||||
let state = db.get_state(swap_id)?.try_into_alice()?.into();
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
let (monero_wallet_restore_blockheight, transfer_proof, state3) = match state {
|
||||
// In case no XMR has been locked, move to Safely Aborted
|
||||
@ -66,7 +67,7 @@ pub async fn refund(
|
||||
tracing::debug!(%swap_id, "Bitcoin refund transaction found, extracting key to refund Monero");
|
||||
state3.extract_monero_private_key(published_refund_tx)?
|
||||
} else {
|
||||
let bob_peer_id = db.get_peer_id(swap_id)?;
|
||||
let bob_peer_id = db.get_peer_id(swap_id).await?;
|
||||
bail!(Error::RefundTransactionNotPublishedYet(bob_peer_id),);
|
||||
};
|
||||
|
||||
@ -81,8 +82,7 @@ pub async fn refund(
|
||||
.await?;
|
||||
|
||||
let state = AliceState::XmrRefunded;
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok(state)
|
||||
|
@ -1,11 +1,12 @@
|
||||
use crate::database::{Database, Swap};
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn safely_abort(swap_id: Uuid, db: Arc<Database>) -> Result<AliceState> {
|
||||
let state = db.get_state(swap_id)?.try_into_alice()?.into();
|
||||
pub async fn safely_abort(swap_id: Uuid, db: Arc<dyn Database>) -> Result<AliceState> {
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
match state {
|
||||
AliceState::Started { .. }
|
||||
@ -13,8 +14,7 @@ pub async fn safely_abort(swap_id: Uuid, db: Arc<Database>) -> Result<AliceState
|
||||
| AliceState::BtcLocked { .. } => {
|
||||
let state = AliceState::SafelyAborted;
|
||||
|
||||
let db_state = (&state).into();
|
||||
db.insert_latest_state(swap_id, Swap::Alice(db_state))
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok(state)
|
||||
|
@ -18,6 +18,7 @@ use libp2p::core::multiaddr::Protocol;
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::swarm::AddressScore;
|
||||
use libp2p::Swarm;
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
@ -28,11 +29,11 @@ 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::database::Database;
|
||||
use swap::database::open_db;
|
||||
use swap::monero::Amount;
|
||||
use swap::network::rendezvous::XmrBtcNamespace;
|
||||
use swap::network::swarm;
|
||||
use swap::protocol::alice::run;
|
||||
use swap::protocol::alice::{run, AliceState};
|
||||
use swap::seed::Seed;
|
||||
use swap::tor::AuthenticatedClient;
|
||||
use swap::{asb, bitcoin, kraken, monero, tor};
|
||||
@ -46,6 +47,7 @@ async fn main() -> Result<()> {
|
||||
testnet,
|
||||
json,
|
||||
disable_timestamp,
|
||||
sled,
|
||||
config_path,
|
||||
env_config,
|
||||
cmd,
|
||||
@ -91,9 +93,9 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
let db_path = config.data.dir.join("database");
|
||||
let sled_path = config.data.dir.join(db_path);
|
||||
|
||||
let db = Database::open(config.data.dir.join(db_path).as_path())
|
||||
.context("Could not open database")?;
|
||||
let db = open_db(sled_path, config.data.dir.join("sqlite"), sled).await?;
|
||||
|
||||
let seed =
|
||||
Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed");
|
||||
@ -177,7 +179,7 @@ async fn main() -> Result<()> {
|
||||
env_config,
|
||||
Arc::new(bitcoin_wallet),
|
||||
Arc::new(monero_wallet),
|
||||
Arc::new(db),
|
||||
db,
|
||||
kraken_rate.clone(),
|
||||
config.maker.min_buy_btc,
|
||||
config.maker.max_buy_btc,
|
||||
@ -208,12 +210,17 @@ async fn main() -> Result<()> {
|
||||
|
||||
table.set_header(vec!["SWAP ID", "STATE"]);
|
||||
|
||||
for (swap_id, state) in db.all_alice()? {
|
||||
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()]);
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
}
|
||||
Command::Config => {
|
||||
let config_json = serde_json::to_string_pretty(&config)?;
|
||||
println!("{}", config_json);
|
||||
}
|
||||
Command::WithdrawBtc { amount, address } => {
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
@ -248,7 +255,7 @@ async fn main() -> Result<()> {
|
||||
Command::Cancel { swap_id } => {
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
let (txid, _) = cancel(swap_id, Arc::new(bitcoin_wallet), Arc::new(db)).await?;
|
||||
let (txid, _) = cancel(swap_id, Arc::new(bitcoin_wallet), db).await?;
|
||||
|
||||
tracing::info!("Cancel transaction successfully published with id {}", txid);
|
||||
}
|
||||
@ -260,7 +267,7 @@ async fn main() -> Result<()> {
|
||||
swap_id,
|
||||
Arc::new(bitcoin_wallet),
|
||||
Arc::new(monero_wallet),
|
||||
Arc::new(db),
|
||||
db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -269,12 +276,12 @@ async fn main() -> Result<()> {
|
||||
Command::Punish { swap_id } => {
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
let (txid, _) = punish(swap_id, Arc::new(bitcoin_wallet), Arc::new(db)).await?;
|
||||
let (txid, _) = punish(swap_id, Arc::new(bitcoin_wallet), db).await?;
|
||||
|
||||
tracing::info!("Punish transaction successfully published with id {}", txid);
|
||||
}
|
||||
Command::SafelyAbort { swap_id } => {
|
||||
safely_abort(swap_id, Arc::new(db)).await?;
|
||||
safely_abort(swap_id, db).await?;
|
||||
|
||||
tracing::info!("Swap safely aborted");
|
||||
}
|
||||
@ -287,7 +294,7 @@ async fn main() -> Result<()> {
|
||||
let (txid, _) = redeem(
|
||||
swap_id,
|
||||
Arc::new(bitcoin_wallet),
|
||||
Arc::new(db),
|
||||
db,
|
||||
Finality::from_bool(do_not_await_finality),
|
||||
)
|
||||
.await?;
|
||||
|
@ -17,6 +17,7 @@ use comfy_table::Table;
|
||||
use qrcode::render::unicode;
|
||||
use qrcode::QrCode;
|
||||
use std::cmp::min;
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
@ -25,13 +26,13 @@ use std::time::Duration;
|
||||
use swap::bitcoin::TxLock;
|
||||
use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult};
|
||||
use swap::cli::{list_sellers, EventLoop, SellerStatus};
|
||||
use swap::database::Database;
|
||||
use swap::database::open_db;
|
||||
use swap::env::Config;
|
||||
use swap::libp2p_ext::MultiAddrExt;
|
||||
use swap::network::quote::BidQuote;
|
||||
use swap::network::swarm;
|
||||
use swap::protocol::bob;
|
||||
use swap::protocol::bob::Swap;
|
||||
use swap::protocol::bob::{BobState, Swap};
|
||||
use swap::seed::Seed;
|
||||
use swap::{bitcoin, cli, monero};
|
||||
use url::Url;
|
||||
@ -44,6 +45,7 @@ async fn main() -> Result<()> {
|
||||
data_dir,
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
cmd,
|
||||
} = match parse_args_and_apply_defaults(env::args_os())? {
|
||||
ParseResult::Arguments(args) => args,
|
||||
@ -66,8 +68,7 @@ async fn main() -> Result<()> {
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
|
||||
@ -82,7 +83,6 @@ async fn main() -> Result<()> {
|
||||
let (monero_wallet, _process) =
|
||||
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
|
||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||
|
||||
let seller_peer_id = seller
|
||||
.extract_peer_id()
|
||||
.context("Seller address must contain peer ID")?;
|
||||
@ -139,20 +139,49 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
Command::History => {
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
|
||||
let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["SWAP ID", "STATE"]);
|
||||
|
||||
for (swap_id, state) in db.all_bob()? {
|
||||
for (swap_id, state) in db.all().await? {
|
||||
let state: BobState = state.try_into()?;
|
||||
table.add_row(vec![swap_id.to_string(), state.to_string()]);
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
}
|
||||
|
||||
Command::Config => {
|
||||
println!("Data directory: {}", data_dir.display());
|
||||
println!(
|
||||
"Log files locations: {}",
|
||||
format!("{}/wallet", data_dir.display())
|
||||
);
|
||||
println!(
|
||||
"Sled folder location: {}",
|
||||
format!("{}/database", data_dir.display())
|
||||
);
|
||||
println!(
|
||||
"Sqlite file location: {}",
|
||||
format!("{}/sqlite", data_dir.display())
|
||||
);
|
||||
println!(
|
||||
"Seed file location: {}",
|
||||
format!("{}/seed.pem", data_dir.display())
|
||||
);
|
||||
println!(
|
||||
"Monero-wallet-rpc location: {}",
|
||||
format!("{}/monero", data_dir.display())
|
||||
);
|
||||
println!(
|
||||
"Internal bitcoin wallet location: {}",
|
||||
format!("{}/wallet", data_dir.display())
|
||||
);
|
||||
println!(
|
||||
"Internal bitcoin wallet location: {}",
|
||||
format!("{}/wallet", data_dir.display())
|
||||
);
|
||||
}
|
||||
Command::WithdrawBtc {
|
||||
bitcoin_electrum_rpc_url,
|
||||
bitcoin_target_block,
|
||||
@ -215,8 +244,7 @@ async fn main() -> Result<()> {
|
||||
tor_socks5_port,
|
||||
} => {
|
||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
|
||||
@ -232,8 +260,8 @@ async fn main() -> Result<()> {
|
||||
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
|
||||
let bitcoin_wallet = Arc::new(bitcoin_wallet);
|
||||
|
||||
let seller_peer_id = db.get_peer_id(swap_id)?;
|
||||
let seller_addresses = db.get_addresses(seller_peer_id)?;
|
||||
let seller_peer_id = db.get_peer_id(swap_id).await?;
|
||||
let seller_addresses = db.get_addresses(seller_peer_id).await?;
|
||||
|
||||
let behaviour = cli::Behaviour::new(seller_peer_id, env_config, bitcoin_wallet.clone());
|
||||
let mut swarm =
|
||||
@ -251,7 +279,7 @@ async fn main() -> Result<()> {
|
||||
EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
|
||||
let handle = tokio::spawn(event_loop.run());
|
||||
|
||||
let monero_receive_address = db.get_monero_address(swap_id)?;
|
||||
let monero_receive_address = db.get_monero_address(swap_id).await?;
|
||||
let swap = Swap::from_db(
|
||||
db,
|
||||
swap_id,
|
||||
@ -260,7 +288,8 @@ async fn main() -> Result<()> {
|
||||
env_config,
|
||||
event_loop_handle,
|
||||
monero_receive_address,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokio::select! {
|
||||
event_loop_result = handle => {
|
||||
@ -277,8 +306,7 @@ async fn main() -> Result<()> {
|
||||
bitcoin_target_block,
|
||||
} => {
|
||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
|
||||
@ -300,8 +328,7 @@ async fn main() -> Result<()> {
|
||||
bitcoin_target_block,
|
||||
} => {
|
||||
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
|
||||
let db = Database::open(data_dir.join("database").as_path())
|
||||
.context("Failed to open database")?;
|
||||
let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
|
||||
let seed = Seed::from_file_or_generate(data_dir.as_path())
|
||||
.context("Failed to read in seed file")?;
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Txid, Wallet};
|
||||
use crate::database::{Database, Swap};
|
||||
use crate::protocol::bob::BobState;
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn cancel(
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<Wallet>,
|
||||
db: Database,
|
||||
db: Arc<dyn Database>,
|
||||
) -> Result<(Txid, BobState)> {
|
||||
let state = db.get_state(swap_id)?.try_into_bob()?.into();
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
let state6 = match state {
|
||||
BobState::BtcLocked(state3) => state3.cancel(),
|
||||
@ -48,8 +49,8 @@ pub async fn cancel(
|
||||
};
|
||||
|
||||
let state = BobState::BtcCancelled(state6);
|
||||
let db_state = state.clone().into();
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok((txid, state))
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ pub struct Arguments {
|
||||
pub env_config: env::Config,
|
||||
pub debug: bool,
|
||||
pub json: bool,
|
||||
pub sled: bool,
|
||||
pub data_dir: PathBuf,
|
||||
pub cmd: Command,
|
||||
}
|
||||
@ -66,6 +67,7 @@ where
|
||||
|
||||
let debug = args.debug;
|
||||
let json = args.json;
|
||||
let sled = args.sled;
|
||||
let is_testnet = args.testnet;
|
||||
let data = args.data;
|
||||
|
||||
@ -90,6 +92,7 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::BuyXmr {
|
||||
seller,
|
||||
@ -106,9 +109,18 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::History,
|
||||
},
|
||||
RawCommand::Config => Arguments {
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::Config,
|
||||
},
|
||||
RawCommand::Balance { bitcoin } => {
|
||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||
bitcoin.apply_defaults(is_testnet)?;
|
||||
@ -117,6 +129,7 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::Balance {
|
||||
bitcoin_electrum_rpc_url,
|
||||
@ -136,6 +149,7 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::WithdrawBtc {
|
||||
bitcoin_electrum_rpc_url,
|
||||
@ -159,6 +173,7 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::Resume {
|
||||
swap_id,
|
||||
@ -180,6 +195,7 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::Cancel {
|
||||
swap_id,
|
||||
@ -199,6 +215,7 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::Refund {
|
||||
swap_id,
|
||||
@ -214,6 +231,7 @@ where
|
||||
env_config: env_config_from(is_testnet),
|
||||
debug,
|
||||
json,
|
||||
sled,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::ListSellers {
|
||||
rendezvous_point,
|
||||
@ -238,6 +256,7 @@ pub enum Command {
|
||||
tor_socks5_port: u16,
|
||||
},
|
||||
History,
|
||||
Config,
|
||||
WithdrawBtc {
|
||||
bitcoin_electrum_rpc_url: Url,
|
||||
bitcoin_target_block: usize,
|
||||
@ -304,6 +323,13 @@ struct RawArguments {
|
||||
)]
|
||||
json: bool,
|
||||
|
||||
#[structopt(
|
||||
short,
|
||||
long = "sled",
|
||||
help = "Forces the swap-cli to use the deprecated sled db if it is available"
|
||||
)]
|
||||
sled: bool,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
cmd: RawCommand,
|
||||
}
|
||||
@ -338,6 +364,8 @@ enum RawCommand {
|
||||
},
|
||||
/// Show a list of past, ongoing and completed swaps
|
||||
History,
|
||||
#[structopt(about = "Prints the current config")]
|
||||
Config,
|
||||
#[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")]
|
||||
WithdrawBtc {
|
||||
#[structopt(flatten)]
|
||||
@ -1105,6 +1133,7 @@ mod tests {
|
||||
env_config: env::Testnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(TESTNET),
|
||||
cmd: Command::BuyXmr {
|
||||
seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(),
|
||||
@ -1125,6 +1154,7 @@ mod tests {
|
||||
env_config: env::Mainnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(MAINNET),
|
||||
cmd: Command::BuyXmr {
|
||||
seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(),
|
||||
@ -1144,6 +1174,7 @@ mod tests {
|
||||
env_config: env::Testnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(TESTNET),
|
||||
cmd: Command::Resume {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
@ -1161,6 +1192,7 @@ mod tests {
|
||||
env_config: env::Mainnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(MAINNET),
|
||||
cmd: Command::Resume {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
@ -1177,6 +1209,7 @@ mod tests {
|
||||
env_config: env::Testnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(TESTNET),
|
||||
cmd: Command::Cancel {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
@ -1192,6 +1225,7 @@ mod tests {
|
||||
env_config: env::Mainnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(MAINNET),
|
||||
cmd: Command::Cancel {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
@ -1206,6 +1240,7 @@ mod tests {
|
||||
env_config: env::Testnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(TESTNET),
|
||||
cmd: Command::Refund {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
@ -1221,6 +1256,7 @@ mod tests {
|
||||
env_config: env::Mainnet::get_config(),
|
||||
debug: false,
|
||||
json: false,
|
||||
sled: false,
|
||||
data_dir: data_dir_path_cli().join(MAINNET),
|
||||
cmd: Command::Refund {
|
||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||
|
@ -1,12 +1,17 @@
|
||||
use crate::bitcoin::Wallet;
|
||||
use crate::database::{Database, Swap};
|
||||
use crate::protocol::bob::BobState;
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn refund(swap_id: Uuid, bitcoin_wallet: Arc<Wallet>, db: Database) -> Result<BobState> {
|
||||
let state = db.get_state(swap_id)?.try_into_bob()?.into();
|
||||
pub async fn refund(
|
||||
swap_id: Uuid,
|
||||
bitcoin_wallet: Arc<Wallet>,
|
||||
db: Arc<dyn Database>,
|
||||
) -> Result<BobState> {
|
||||
let state = db.get_state(swap_id).await?.try_into()?;
|
||||
|
||||
let state6 = match state {
|
||||
BobState::BtcLocked(state3) => state3.cancel(),
|
||||
@ -31,9 +36,8 @@ pub async fn refund(swap_id: Uuid, bitcoin_wallet: Arc<Wallet>, db: Database) ->
|
||||
state6.publish_refund_btc(bitcoin_wallet.as_ref()).await?;
|
||||
|
||||
let state = BobState::BtcRefunded(state6);
|
||||
let db_state = state.clone().into();
|
||||
|
||||
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
|
||||
db.insert_latest_state(swap_id, state.clone().into())
|
||||
.await?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
pub use self::sled::SledDatabase;
|
||||
pub use alice::Alice;
|
||||
pub use bob::Bob;
|
||||
pub use sqlite::SqliteDatabase;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use itertools::Itertools;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use serde::de::DeserializeOwned;
|
||||
use crate::fs::ensure_directory_exists;
|
||||
use crate::protocol::{Database, State};
|
||||
use anyhow::{bail, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod alice;
|
||||
mod bob;
|
||||
mod sled;
|
||||
mod sqlite;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum Swap {
|
||||
@ -20,6 +22,24 @@ pub enum Swap {
|
||||
Bob(Bob),
|
||||
}
|
||||
|
||||
impl From<State> for Swap {
|
||||
fn from(state: State) -> Self {
|
||||
match state {
|
||||
State::Alice(state) => Swap::Alice(state.into()),
|
||||
State::Bob(state) => Swap::Bob(state.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Swap> for State {
|
||||
fn from(value: Swap) -> Self {
|
||||
match value {
|
||||
Swap::Alice(alice) => State::Alice(alice.into()),
|
||||
Swap::Bob(bob) => State::Bob(bob.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Alice> for Swap {
|
||||
fn from(from: Alice) -> Self {
|
||||
Swap::Alice(from)
|
||||
@ -65,413 +85,74 @@ impl Swap {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Database {
|
||||
swaps: sled::Tree,
|
||||
peers: sled::Tree,
|
||||
addresses: sled::Tree,
|
||||
monero_addresses: sled::Tree,
|
||||
}
|
||||
pub async fn open_db(
|
||||
sled_path: impl AsRef<Path>,
|
||||
sqlite_path: impl AsRef<Path>,
|
||||
force_sled: bool,
|
||||
) -> Result<Arc<dyn Database + Send + Sync>> {
|
||||
// if sled exists and sqlite doesnt exist try and migrate
|
||||
// if sled and sqlite exists and the sled flag is set, use sled
|
||||
// if sled and sqlite exists, use sqlite
|
||||
match (
|
||||
sled_path.as_ref().exists(),
|
||||
sqlite_path.as_ref().exists(),
|
||||
force_sled,
|
||||
) {
|
||||
(true, false, false) => {
|
||||
tracing::info!("Attempting to migrate old data to the new sqlite database...");
|
||||
let sled_db = SledDatabase::open(sled_path.as_ref()).await?;
|
||||
|
||||
impl Database {
|
||||
pub fn open(path: &Path) -> Result<Self> {
|
||||
tracing::debug!("Opening database at {}", path.display());
|
||||
ensure_directory_exists(sqlite_path.as_ref())?;
|
||||
tokio::fs::File::create(&sqlite_path).await?;
|
||||
let sqlite = SqliteDatabase::open(sqlite_path).await?;
|
||||
|
||||
let db =
|
||||
sled::open(path).with_context(|| format!("Could not open the DB at {:?}", path))?;
|
||||
let swap_states = sled_db.all().await?;
|
||||
for (swap_id, state) in swap_states.iter() {
|
||||
sqlite.insert_latest_state(*swap_id, state.clone()).await?;
|
||||
}
|
||||
|
||||
let swaps = db.open_tree("swaps")?;
|
||||
let peers = db.open_tree("peers")?;
|
||||
let addresses = db.open_tree("addresses")?;
|
||||
let monero_addresses = db.open_tree("monero_addresses")?;
|
||||
let monero_addresses = sled_db.get_all_monero_addresses();
|
||||
for (swap_id, monero_address) in monero_addresses.flatten() {
|
||||
sqlite
|
||||
.insert_monero_address(swap_id, monero_address)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Database {
|
||||
swaps,
|
||||
peers,
|
||||
addresses,
|
||||
monero_addresses,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> {
|
||||
let peer_id_str = peer_id.to_string();
|
||||
|
||||
let key = serialize(&swap_id)?;
|
||||
let value = serialize(&peer_id_str).context("Could not serialize peer-id")?;
|
||||
|
||||
self.peers.insert(key, value)?;
|
||||
|
||||
self.peers
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
|
||||
pub fn get_peer_id(&self, swap_id: Uuid) -> Result<PeerId> {
|
||||
let key = serialize(&swap_id)?;
|
||||
|
||||
let encoded = self
|
||||
.peers
|
||||
.get(&key)?
|
||||
.ok_or_else(|| anyhow!("No peer-id found for swap id {} in database", swap_id))?;
|
||||
|
||||
let peer_id: String = deserialize(&encoded).context("Could not deserialize peer-id")?;
|
||||
Ok(PeerId::from_str(peer_id.as_str())?)
|
||||
}
|
||||
|
||||
pub async fn insert_monero_address(
|
||||
&self,
|
||||
swap_id: Uuid,
|
||||
address: monero::Address,
|
||||
) -> Result<()> {
|
||||
let key = swap_id.as_bytes();
|
||||
let value = serialize(&address)?;
|
||||
|
||||
self.monero_addresses.insert(key, value)?;
|
||||
|
||||
self.monero_addresses
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
|
||||
pub fn get_monero_address(&self, swap_id: Uuid) -> Result<monero::Address> {
|
||||
let encoded = self
|
||||
.monero_addresses
|
||||
.get(swap_id.as_bytes())?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"No Monero address found for swap id {} in database",
|
||||
swap_id
|
||||
)
|
||||
})?;
|
||||
|
||||
let monero_address = deserialize(&encoded)?;
|
||||
|
||||
Ok(monero_address)
|
||||
}
|
||||
|
||||
pub async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> {
|
||||
let key = peer_id.to_bytes();
|
||||
|
||||
let existing_addresses = self.addresses.get(&key)?;
|
||||
|
||||
let new_addresses = {
|
||||
let existing_addresses = existing_addresses.clone();
|
||||
|
||||
Some(match existing_addresses {
|
||||
Some(encoded) => {
|
||||
let mut addresses = deserialize::<Vec<Multiaddr>>(&encoded)?;
|
||||
addresses.push(address);
|
||||
|
||||
serialize(&addresses)?
|
||||
let peer_addresses = sled_db.get_all_addresses();
|
||||
for (peer_id, addresses) in peer_addresses.flatten() {
|
||||
for address in addresses {
|
||||
sqlite.insert_address(peer_id, address).await?;
|
||||
}
|
||||
None => serialize(&[address])?,
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
self.addresses
|
||||
.compare_and_swap(key, existing_addresses, new_addresses)??;
|
||||
let peers = sled_db.get_all_peers();
|
||||
for (swap_id, peer_id) in peers.flatten() {
|
||||
sqlite.insert_peer_id(swap_id, peer_id).await?;
|
||||
}
|
||||
|
||||
self.addresses
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
tracing::info!("Sucessfully migrated data to sqlite! Using sqlite.");
|
||||
|
||||
pub fn get_addresses(&self, peer_id: PeerId) -> Result<Vec<Multiaddr>> {
|
||||
let key = peer_id.to_bytes();
|
||||
|
||||
let addresses = match self.addresses.get(&key)? {
|
||||
Some(encoded) => deserialize(&encoded).context("Failed to deserialize addresses")?,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
Ok(addresses)
|
||||
}
|
||||
|
||||
pub async fn insert_latest_state(&self, swap_id: Uuid, state: Swap) -> Result<()> {
|
||||
let key = serialize(&swap_id)?;
|
||||
let new_value = serialize(&state).context("Could not serialize new state value")?;
|
||||
|
||||
let old_value = self.swaps.get(&key)?;
|
||||
|
||||
self.swaps
|
||||
.compare_and_swap(key, old_value, Some(new_value))
|
||||
.context("Could not write in the DB")?
|
||||
.context("Stored swap somehow changed, aborting saving")?;
|
||||
|
||||
self.swaps
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
|
||||
pub fn get_state(&self, swap_id: Uuid) -> Result<Swap> {
|
||||
let key = serialize(&swap_id)?;
|
||||
|
||||
let encoded = self
|
||||
.swaps
|
||||
.get(&key)?
|
||||
.ok_or_else(|| anyhow!("Swap with id {} not found in database", swap_id))?;
|
||||
|
||||
let state = deserialize(&encoded).context("Could not deserialize state")?;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub fn all_alice(&self) -> Result<Vec<(Uuid, Alice)>> {
|
||||
self.all_alice_iter().collect()
|
||||
}
|
||||
|
||||
fn all_alice_iter(&self) -> impl Iterator<Item = Result<(Uuid, Alice)>> {
|
||||
self.all_swaps_iter().map(|item| {
|
||||
let (swap_id, swap) = item?;
|
||||
Ok((swap_id, swap.try_into_alice()?))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all_bob(&self) -> Result<Vec<(Uuid, Bob)>> {
|
||||
self.all_bob_iter().collect()
|
||||
}
|
||||
|
||||
fn all_bob_iter(&self) -> impl Iterator<Item = Result<(Uuid, Bob)>> {
|
||||
self.all_swaps_iter().map(|item| {
|
||||
let (swap_id, swap) = item?;
|
||||
Ok((swap_id, swap.try_into_bob()?))
|
||||
})
|
||||
}
|
||||
|
||||
fn all_swaps_iter(&self) -> impl Iterator<Item = Result<(Uuid, Swap)>> {
|
||||
self.swaps.iter().map(|item| {
|
||||
let (key, value) = item.context("Failed to retrieve swap from DB")?;
|
||||
|
||||
let swap_id = deserialize::<Uuid>(&key)?;
|
||||
let swap = deserialize::<Swap>(&value).context("Failed to deserialize swap")?;
|
||||
|
||||
Ok((swap_id, swap))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unfinished_alice(&self) -> Result<Vec<(Uuid, Alice)>> {
|
||||
self.all_alice_iter()
|
||||
.filter_ok(|(_swap_id, alice)| !matches!(alice, Alice::Done(_)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<T>(t: &T) -> Result<Vec<u8>>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Ok(serde_cbor::to_vec(t)?)
|
||||
}
|
||||
|
||||
pub fn deserialize<T>(v: &[u8]) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
Ok(serde_cbor::from_slice(&v)?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::database::alice::{Alice, AliceEndState};
|
||||
use crate::database::bob::{Bob, BobEndState};
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_write_and_read_to_multiple_keys() {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
|
||||
let state_1 = Swap::Alice(Alice::Done(AliceEndState::BtcRedeemed));
|
||||
let swap_id_1 = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
||||
.await
|
||||
.expect("Failed to save second state");
|
||||
|
||||
let state_2 = Swap::Bob(Bob::Done(BobEndState::SafelyAborted));
|
||||
let swap_id_2 = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id_2, state_2.clone())
|
||||
.await
|
||||
.expect("Failed to save first state");
|
||||
|
||||
let recovered_1 = db
|
||||
.get_state(swap_id_1)
|
||||
.expect("Failed to recover first state");
|
||||
|
||||
let recovered_2 = db
|
||||
.get_state(swap_id_2)
|
||||
.expect("Failed to recover second state");
|
||||
|
||||
assert_eq!(recovered_1, state_1);
|
||||
assert_eq!(recovered_2, state_2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_write_twice_to_one_key() {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
|
||||
let state = Swap::Alice(Alice::Done(AliceEndState::SafelyAborted));
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id, state.clone())
|
||||
.await
|
||||
.expect("Failed to save state the first time");
|
||||
let recovered = db
|
||||
.get_state(swap_id)
|
||||
.expect("Failed to recover state the first time");
|
||||
|
||||
// We insert and recover twice to ensure database implementation allows the
|
||||
// caller to write to an existing key
|
||||
db.insert_latest_state(swap_id, recovered)
|
||||
.await
|
||||
.expect("Failed to save state the second time");
|
||||
let recovered = db
|
||||
.get_state(swap_id)
|
||||
.expect("Failed to recover state the second time");
|
||||
|
||||
assert_eq!(recovered, state);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn all_swaps_as_alice() {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
|
||||
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||
let alice_swap = Swap::Alice(alice_state.clone());
|
||||
let alice_swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(alice_swap_id, alice_swap)
|
||||
.await
|
||||
.expect("Failed to save alice state 1");
|
||||
|
||||
let alice_swaps = db.all_alice().unwrap();
|
||||
assert_eq!(alice_swaps.len(), 1);
|
||||
assert!(alice_swaps.contains(&(alice_swap_id, alice_state)));
|
||||
|
||||
let bob_state = Bob::Done(BobEndState::SafelyAborted);
|
||||
let bob_swap = Swap::Bob(bob_state);
|
||||
let bob_swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(bob_swap_id, bob_swap)
|
||||
.await
|
||||
.expect("Failed to save bob state 1");
|
||||
|
||||
let err = db.all_alice().unwrap_err();
|
||||
|
||||
assert_eq!(err.downcast_ref::<NotAlice>().unwrap(), &NotAlice);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn all_swaps_as_bob() {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
|
||||
let bob_state = Bob::Done(BobEndState::SafelyAborted);
|
||||
let bob_swap = Swap::Bob(bob_state.clone());
|
||||
let bob_swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(bob_swap_id, bob_swap)
|
||||
.await
|
||||
.expect("Failed to save bob state 1");
|
||||
|
||||
let bob_swaps = db.all_bob().unwrap();
|
||||
assert_eq!(bob_swaps.len(), 1);
|
||||
assert!(bob_swaps.contains(&(bob_swap_id, bob_state)));
|
||||
|
||||
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||
let alice_swap = Swap::Alice(alice_state);
|
||||
let alice_swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(alice_swap_id, alice_swap)
|
||||
.await
|
||||
.expect("Failed to save alice state 1");
|
||||
|
||||
let err = db.all_bob().unwrap_err();
|
||||
|
||||
assert_eq!(err.downcast_ref::<NotBob>().unwrap(), &NotBob);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_save_swap_state_and_peer_id_with_same_swap_id() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
|
||||
let alice_id = Uuid::new_v4();
|
||||
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||
let alice_swap = Swap::Alice(alice_state);
|
||||
let peer_id = PeerId::random();
|
||||
|
||||
db.insert_latest_state(alice_id, alice_swap.clone()).await?;
|
||||
db.insert_peer_id(alice_id, peer_id).await?;
|
||||
|
||||
let loaded_swap = db.get_state(alice_id)?;
|
||||
let loaded_peer_id = db.get_peer_id(alice_id)?;
|
||||
|
||||
assert_eq!(alice_swap, loaded_swap);
|
||||
assert_eq!(peer_id, loaded_peer_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_reopen_db() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let alice_id = Uuid::new_v4();
|
||||
let alice_state = Alice::Done(AliceEndState::BtcPunished);
|
||||
let alice_swap = Swap::Alice(alice_state);
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
|
||||
{
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
db.insert_latest_state(alice_id, alice_swap.clone()).await?;
|
||||
db.insert_peer_id(alice_id, peer_id).await?;
|
||||
Ok(Arc::new(sqlite))
|
||||
}
|
||||
|
||||
let db = Database::open(db_dir.path()).unwrap();
|
||||
|
||||
let loaded_swap = db.get_state(alice_id)?;
|
||||
let loaded_peer_id = db.get_peer_id(alice_id)?;
|
||||
|
||||
assert_eq!(alice_swap, loaded_swap);
|
||||
assert_eq!(peer_id, loaded_peer_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_and_load_addresses() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir()?;
|
||||
let peer_id = PeerId::random();
|
||||
let home1 = "/ip4/127.0.0.1/tcp/1".parse::<Multiaddr>()?;
|
||||
let home2 = "/ip4/127.0.0.1/tcp/2".parse::<Multiaddr>()?;
|
||||
|
||||
{
|
||||
let db = Database::open(db_dir.path())?;
|
||||
db.insert_address(peer_id, home1.clone()).await?;
|
||||
db.insert_address(peer_id, home2.clone()).await?;
|
||||
(_, false, false) => {
|
||||
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?;
|
||||
Ok(Arc::new(sqlite))
|
||||
}
|
||||
(_, true, false) => {
|
||||
tracing::debug!("Using existing sqlite database.");
|
||||
let sqlite = SqliteDatabase::open(sqlite_path).await?;
|
||||
Ok(Arc::new(sqlite))
|
||||
}
|
||||
(false, _, true) => {
|
||||
bail!("Sled database does not exist at specified location")
|
||||
}
|
||||
(true, _, true) => {
|
||||
tracing::debug!("Sled flag set. Using sled database.");
|
||||
let sled = SledDatabase::open(sled_path.as_ref()).await?;
|
||||
Ok(Arc::new(sled))
|
||||
}
|
||||
|
||||
let addresses = Database::open(db_dir.path())?.get_addresses(peer_id)?;
|
||||
|
||||
assert_eq!(addresses, vec![home1, home2]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_and_load_monero_address() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir()?;
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
Database::open(db_dir.path())?.insert_monero_address(swap_id, "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a".parse()?).await?;
|
||||
let loaded_monero_address = Database::open(db_dir.path())?.get_monero_address(swap_id)?;
|
||||
|
||||
assert_eq!(loaded_monero_address.to_string(), "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -78,8 +78,8 @@ pub enum AliceEndState {
|
||||
BtcPunished,
|
||||
}
|
||||
|
||||
impl From<&AliceState> for Alice {
|
||||
fn from(alice_state: &AliceState) -> Self {
|
||||
impl From<AliceState> for Alice {
|
||||
fn from(alice_state: AliceState) -> Self {
|
||||
match alice_state {
|
||||
AliceState::Started { state3 } => Alice::Started {
|
||||
state3: state3.as_ref().clone(),
|
||||
@ -95,8 +95,8 @@ impl From<&AliceState> for Alice {
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrLocked {
|
||||
@ -104,8 +104,8 @@ impl From<&AliceState> for Alice {
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::XmrLocked {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrLockTransferProofSent {
|
||||
@ -113,8 +113,8 @@ impl From<&AliceState> for Alice {
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::EncSigLearned {
|
||||
@ -123,10 +123,10 @@ impl From<&AliceState> for Alice {
|
||||
state3,
|
||||
encrypted_signature,
|
||||
} => Alice::EncSigLearned {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
encrypted_signature: *encrypted_signature.clone(),
|
||||
encrypted_signature: encrypted_signature.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcRedeemTransactionPublished { state3 } => {
|
||||
Alice::BtcRedeemTransactionPublished {
|
||||
@ -139,8 +139,8 @@ impl From<&AliceState> for Alice {
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::BtcCancelled {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcRefunded {
|
||||
@ -149,9 +149,9 @@ impl From<&AliceState> for Alice {
|
||||
spend_key,
|
||||
state3,
|
||||
} => Alice::BtcRefunded {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
spend_key: *spend_key,
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcPunishable {
|
||||
@ -159,8 +159,8 @@ impl From<&AliceState> for Alice {
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::BtcPunishable {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded),
|
||||
@ -169,8 +169,8 @@ impl From<&AliceState> for Alice {
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight,
|
||||
transfer_proof: transfer_proof.clone(),
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),
|
||||
|
400
swap/src/database/sled.rs
Normal file
400
swap/src/database/sled.rs
Normal file
@ -0,0 +1,400 @@
|
||||
use crate::database::Swap;
|
||||
use crate::protocol::{Database, State};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use crate::database::alice::Alice;
|
||||
pub use crate::database::bob::Bob;
|
||||
|
||||
pub struct SledDatabase {
|
||||
swaps: sled::Tree,
|
||||
peers: sled::Tree,
|
||||
addresses: sled::Tree,
|
||||
monero_addresses: sled::Tree,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Database for SledDatabase {
|
||||
async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> {
|
||||
let peer_id_str = peer_id.to_string();
|
||||
|
||||
let key = serialize(&swap_id)?;
|
||||
let value = serialize(&peer_id_str).context("Could not serialize peer-id")?;
|
||||
|
||||
self.peers.insert(key, value)?;
|
||||
|
||||
self.peers
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
|
||||
async fn get_peer_id(&self, swap_id: Uuid) -> Result<PeerId> {
|
||||
let key = serialize(&swap_id)?;
|
||||
|
||||
let encoded = self
|
||||
.peers
|
||||
.get(&key)?
|
||||
.ok_or_else(|| anyhow!("No peer-id found for swap id {} in database", swap_id))?;
|
||||
|
||||
let peer_id: String = deserialize(&encoded).context("Could not deserialize peer-id")?;
|
||||
Ok(PeerId::from_str(peer_id.as_str())?)
|
||||
}
|
||||
|
||||
async fn insert_monero_address(&self, swap_id: Uuid, address: monero::Address) -> Result<()> {
|
||||
let key = swap_id.as_bytes();
|
||||
let value = serialize(&address)?;
|
||||
|
||||
self.monero_addresses.insert(key, value)?;
|
||||
|
||||
self.monero_addresses
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
|
||||
async fn get_monero_address(&self, swap_id: Uuid) -> Result<monero::Address> {
|
||||
let encoded = self
|
||||
.monero_addresses
|
||||
.get(swap_id.as_bytes())?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"No Monero address found for swap id {} in database",
|
||||
swap_id
|
||||
)
|
||||
})?;
|
||||
|
||||
let monero_address = deserialize(&encoded)?;
|
||||
|
||||
Ok(monero_address)
|
||||
}
|
||||
|
||||
async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> {
|
||||
let key = peer_id.to_bytes();
|
||||
|
||||
let existing_addresses = self.addresses.get(&key)?;
|
||||
|
||||
let new_addresses = {
|
||||
let existing_addresses = existing_addresses.clone();
|
||||
|
||||
Some(match existing_addresses {
|
||||
Some(encoded) => {
|
||||
let mut addresses = deserialize::<Vec<Multiaddr>>(&encoded)?;
|
||||
addresses.push(address);
|
||||
|
||||
serialize(&addresses)?
|
||||
}
|
||||
None => serialize(&[address])?,
|
||||
})
|
||||
};
|
||||
|
||||
self.addresses
|
||||
.compare_and_swap(key, existing_addresses, new_addresses)??;
|
||||
|
||||
self.addresses
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
|
||||
async fn get_addresses(&self, peer_id: PeerId) -> Result<Vec<Multiaddr>> {
|
||||
let key = peer_id.to_bytes();
|
||||
|
||||
let addresses = match self.addresses.get(&key)? {
|
||||
Some(encoded) => deserialize(&encoded).context("Failed to deserialize addresses")?,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
Ok(addresses)
|
||||
}
|
||||
|
||||
async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()> {
|
||||
let key = serialize(&swap_id)?;
|
||||
let swap = Swap::from(state);
|
||||
let new_value = serialize(&swap).context("Could not serialize new state value")?;
|
||||
|
||||
let old_value = self.swaps.get(&key)?;
|
||||
|
||||
self.swaps
|
||||
.compare_and_swap(key, old_value, Some(new_value))
|
||||
.context("Could not write in the DB")?
|
||||
.context("Stored swap somehow changed, aborting saving")?;
|
||||
|
||||
self.swaps
|
||||
.flush_async()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.context("Could not flush db")
|
||||
}
|
||||
|
||||
async fn get_state(&self, swap_id: Uuid) -> Result<State> {
|
||||
let key = serialize(&swap_id)?;
|
||||
|
||||
let encoded = self
|
||||
.swaps
|
||||
.get(&key)?
|
||||
.ok_or_else(|| anyhow!("Swap with id {} not found in database", swap_id))?;
|
||||
|
||||
let swap = deserialize::<Swap>(&encoded).context("Could not deserialize state")?;
|
||||
|
||||
let state = State::from(swap);
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
async fn all(&self) -> Result<Vec<(Uuid, State)>> {
|
||||
self.all_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl SledDatabase {
|
||||
pub async fn open(path: &Path) -> Result<Self> {
|
||||
tracing::debug!("Opening database at {}", path.display());
|
||||
|
||||
let db =
|
||||
sled::open(path).with_context(|| format!("Could not open the DB at {:?}", path))?;
|
||||
|
||||
let swaps = db.open_tree("swaps")?;
|
||||
let peers = db.open_tree("peers")?;
|
||||
let addresses = db.open_tree("addresses")?;
|
||||
let monero_addresses = db.open_tree("monero_addresses")?;
|
||||
|
||||
Ok(SledDatabase {
|
||||
swaps,
|
||||
peers,
|
||||
addresses,
|
||||
monero_addresses,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_peers(&self) -> impl Iterator<Item = Result<(Uuid, PeerId)>> {
|
||||
self.peers.iter().map(|item| {
|
||||
let (key, value) = item.context("Failed to retrieve peer id from DB")?;
|
||||
|
||||
let swap_id = deserialize::<Uuid>(&key)?;
|
||||
let peer_id_bytes =
|
||||
deserialize::<Vec<u8>>(&value).context("Failed to deserialize swap")?;
|
||||
|
||||
let peer_id = PeerId::from_bytes(&peer_id_bytes)?;
|
||||
|
||||
Ok((swap_id, peer_id))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_addresses(&self) -> impl Iterator<Item = Result<(PeerId, Vec<Multiaddr>)>> {
|
||||
self.addresses.iter().map(|item| {
|
||||
let (key, value) = item.context("Failed to retrieve peer address from DB")?;
|
||||
|
||||
let peer_id_bytes = deserialize::<Vec<u8>>(&key)?;
|
||||
let addr =
|
||||
deserialize::<Vec<Multiaddr>>(&value).context("Failed to deserialize swap")?;
|
||||
|
||||
let peer_id = PeerId::from_bytes(&peer_id_bytes)?;
|
||||
|
||||
Ok((peer_id, addr))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_monero_addresses(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<(Uuid, monero::Address)>> {
|
||||
self.monero_addresses.iter().map(|item| {
|
||||
let (key, value) = item.context("Failed to retrieve monero address from DB")?;
|
||||
|
||||
let swap_id = deserialize::<Uuid>(&key)?;
|
||||
let addr =
|
||||
deserialize::<monero::Address>(&value).context("Failed to deserialize swap")?;
|
||||
|
||||
Ok((swap_id, addr))
|
||||
})
|
||||
}
|
||||
|
||||
fn all_iter(&self) -> impl Iterator<Item = Result<(Uuid, State)>> {
|
||||
self.swaps.iter().map(|item| {
|
||||
let (key, value) = item.context("Failed to retrieve swap from DB")?;
|
||||
|
||||
let swap_id = deserialize::<Uuid>(&key)?;
|
||||
let swap = deserialize::<Swap>(&value).context("Failed to deserialize swap")?;
|
||||
|
||||
let state = State::from(swap);
|
||||
|
||||
Ok((swap_id, state))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<T>(t: &T) -> Result<Vec<u8>>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Ok(serde_cbor::to_vec(t)?)
|
||||
}
|
||||
|
||||
pub fn deserialize<T>(v: &[u8]) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
Ok(serde_cbor::from_slice(&v)?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::protocol::alice::AliceState;
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_write_and_read_to_multiple_keys() {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = SledDatabase::open(db_dir.path()).await.unwrap();
|
||||
|
||||
let state_1 = State::from(AliceState::BtcRedeemed);
|
||||
let swap_id_1 = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
||||
.await
|
||||
.expect("Failed to save second state");
|
||||
|
||||
let state_2 = State::from(AliceState::BtcPunished);
|
||||
let swap_id_2 = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id_2, state_2.clone())
|
||||
.await
|
||||
.expect("Failed to save first state");
|
||||
|
||||
let recovered_1 = db
|
||||
.get_state(swap_id_1)
|
||||
.await
|
||||
.expect("Failed to recover first state");
|
||||
|
||||
let recovered_2 = db
|
||||
.get_state(swap_id_2)
|
||||
.await
|
||||
.expect("Failed to recover second state");
|
||||
|
||||
assert_eq!(recovered_1, state_1);
|
||||
assert_eq!(recovered_2, state_2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_write_twice_to_one_key() {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = SledDatabase::open(db_dir.path()).await.unwrap();
|
||||
|
||||
let state = State::from(AliceState::SafelyAborted);
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
db.insert_latest_state(swap_id, state.clone())
|
||||
.await
|
||||
.expect("Failed to save state the first time");
|
||||
let recovered = db
|
||||
.get_state(swap_id)
|
||||
.await
|
||||
.expect("Failed to recover state the first time");
|
||||
|
||||
// We insert and recover twice to ensure database implementation allows the
|
||||
// caller to write to an existing key
|
||||
db.insert_latest_state(swap_id, recovered)
|
||||
.await
|
||||
.expect("Failed to save state the second time");
|
||||
let recovered = db
|
||||
.get_state(swap_id)
|
||||
.await
|
||||
.expect("Failed to recover state the second time");
|
||||
|
||||
assert_eq!(recovered, state);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_save_swap_state_and_peer_id_with_same_swap_id() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let db = SledDatabase::open(db_dir.path()).await.unwrap();
|
||||
|
||||
let alice_id = Uuid::new_v4();
|
||||
let alice_state = State::from(AliceState::BtcPunished);
|
||||
let peer_id = PeerId::random();
|
||||
|
||||
db.insert_latest_state(alice_id, alice_state.clone())
|
||||
.await?;
|
||||
db.insert_peer_id(alice_id, peer_id).await?;
|
||||
|
||||
let loaded_swap = db.get_state(alice_id).await?;
|
||||
let loaded_peer_id = db.get_peer_id(alice_id).await?;
|
||||
|
||||
assert_eq!(alice_state, loaded_swap);
|
||||
assert_eq!(peer_id, loaded_peer_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_reopen_db() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir().unwrap();
|
||||
let alice_id = Uuid::new_v4();
|
||||
let alice_state = State::from(AliceState::BtcPunished);
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
|
||||
{
|
||||
let db = SledDatabase::open(db_dir.path()).await.unwrap();
|
||||
db.insert_latest_state(alice_id, alice_state.clone())
|
||||
.await?;
|
||||
db.insert_peer_id(alice_id, peer_id).await?;
|
||||
}
|
||||
|
||||
let db = SledDatabase::open(db_dir.path()).await.unwrap();
|
||||
|
||||
let loaded_swap = db.get_state(alice_id).await?;
|
||||
let loaded_peer_id = db.get_peer_id(alice_id).await?;
|
||||
|
||||
assert_eq!(alice_state, loaded_swap);
|
||||
assert_eq!(peer_id, loaded_peer_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_and_load_addresses() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir()?;
|
||||
let peer_id = PeerId::random();
|
||||
let home1 = "/ip4/127.0.0.1/tcp/1".parse::<Multiaddr>()?;
|
||||
let home2 = "/ip4/127.0.0.1/tcp/2".parse::<Multiaddr>()?;
|
||||
|
||||
{
|
||||
let db = SledDatabase::open(db_dir.path()).await?;
|
||||
db.insert_address(peer_id, home1.clone()).await?;
|
||||
db.insert_address(peer_id, home2.clone()).await?;
|
||||
}
|
||||
|
||||
let addresses = SledDatabase::open(db_dir.path())
|
||||
.await?
|
||||
.get_addresses(peer_id)
|
||||
.await?;
|
||||
|
||||
assert_eq!(addresses, vec![home1, home2]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_and_load_monero_address() -> Result<()> {
|
||||
let db_dir = tempfile::tempdir()?;
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
SledDatabase::open(db_dir.path()).await?.insert_monero_address(swap_id, "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a".parse()?).await?;
|
||||
let loaded_monero_address = SledDatabase::open(db_dir.path())
|
||||
.await?
|
||||
.get_monero_address(swap_id)
|
||||
.await?;
|
||||
|
||||
assert_eq!(loaded_monero_address.to_string(), "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
383
swap/src/database/sqlite.rs
Normal file
383
swap/src/database/sqlite.rs
Normal file
@ -0,0 +1,383 @@
|
||||
use crate::database::Swap;
|
||||
use crate::monero::Address;
|
||||
use crate::protocol::{Database, State};
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use sqlx::sqlite::Sqlite;
|
||||
use sqlx::{Pool, SqlitePool};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct SqliteDatabase {
|
||||
pool: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl SqliteDatabase {
|
||||
pub async fn open(path: impl AsRef<Path>) -> Result<Self>
|
||||
where
|
||||
Self: std::marker::Sized,
|
||||
{
|
||||
let path_str = format!("sqlite:{}", path.as_ref().display());
|
||||
let pool = SqlitePool::connect(&path_str).await?;
|
||||
let mut sqlite = Self { pool };
|
||||
sqlite.run_migrations().await?;
|
||||
Ok(sqlite)
|
||||
}
|
||||
|
||||
async fn run_migrations(&mut self) -> anyhow::Result<()> {
|
||||
sqlx::migrate!("./migrations").run(&self.pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Database for SqliteDatabase {
|
||||
async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
let peer_id = peer_id.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into peers (
|
||||
swap_id,
|
||||
peer_id
|
||||
) values (?, ?);
|
||||
"#,
|
||||
swap_id,
|
||||
peer_id
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_peer_id(&self, swap_id: Uuid) -> Result<PeerId> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT peer_id
|
||||
FROM peers
|
||||
WHERE swap_id = ?
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
let peer_id = PeerId::from_str(&row.peer_id)?;
|
||||
Ok(peer_id)
|
||||
}
|
||||
|
||||
async fn insert_monero_address(&self, swap_id: Uuid, address: Address) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
let address = address.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into monero_addresses (
|
||||
swap_id,
|
||||
address
|
||||
) values (?, ?);
|
||||
"#,
|
||||
swap_id,
|
||||
address
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_monero_address(&self, swap_id: Uuid) -> Result<Address> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT address
|
||||
FROM monero_addresses
|
||||
WHERE swap_id = ?
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
let address = row.address.parse()?;
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let peer_id = peer_id.to_string();
|
||||
let address = address.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into peer_addresses (
|
||||
peer_id,
|
||||
address
|
||||
) values (?, ?);
|
||||
"#,
|
||||
peer_id,
|
||||
address
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_addresses(&self, peer_id: PeerId) -> Result<Vec<Multiaddr>> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let peer_id = peer_id.to_string();
|
||||
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT address
|
||||
FROM peer_addresses
|
||||
WHERE peer_id = ?
|
||||
"#,
|
||||
peer_id,
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let addresses = rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let multiaddr = Multiaddr::from_str(&row.address)?;
|
||||
Ok(multiaddr)
|
||||
})
|
||||
.collect::<Result<Vec<Multiaddr>>>();
|
||||
|
||||
addresses
|
||||
}
|
||||
|
||||
async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let entered_at = OffsetDateTime::now_utc();
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
let swap = serde_json::to_string(&Swap::from(state))?;
|
||||
let entered_at = entered_at.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into swap_states (
|
||||
swap_id,
|
||||
entered_at,
|
||||
state
|
||||
) values (?, ?, ?);
|
||||
"#,
|
||||
swap_id,
|
||||
entered_at,
|
||||
swap
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_state(&self, swap_id: Uuid) -> Result<State> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let swap_id = swap_id.to_string();
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT state
|
||||
FROM swap_states
|
||||
WHERE swap_id = ?
|
||||
ORDER BY id desc
|
||||
LIMIT 1;
|
||||
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let row = row
|
||||
.first()
|
||||
.context(format!("No state in database for swap: {}", swap_id))?;
|
||||
let swap: Swap = serde_json::from_str(&row.state)?;
|
||||
|
||||
Ok(swap.into())
|
||||
}
|
||||
|
||||
async fn all(&self) -> Result<Vec<(Uuid, State)>> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT swap_id, state
|
||||
FROM (
|
||||
SELECT max(id), swap_id, state
|
||||
FROM swap_states
|
||||
GROUP BY swap_id
|
||||
)
|
||||
"#
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let result = rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let swap_id = Uuid::from_str(&row.swap_id)?;
|
||||
let state = match serde_json::from_str::<Swap>(&row.state) {
|
||||
Ok(a) => Ok(State::from(a)),
|
||||
Err(e) => Err(e),
|
||||
}?;
|
||||
Ok((swap_id, state))
|
||||
})
|
||||
.collect::<Result<Vec<(Uuid, State)>>>();
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::bob::BobState;
|
||||
use std::fs::File;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_and_load_state() {
|
||||
let db = setup_test_db().await.unwrap();
|
||||
|
||||
let state_1 = State::Alice(AliceState::BtcRedeemed);
|
||||
let swap_id_1 = Uuid::new_v4();
|
||||
|
||||
db.insert_latest_state(swap_id_1, state_1).await.unwrap();
|
||||
|
||||
let state_1 = State::Alice(AliceState::BtcRedeemed);
|
||||
|
||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let state_1_loaded = db.get_state(swap_id_1).await.unwrap();
|
||||
|
||||
assert_eq!(state_1, state_1_loaded);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_retrieve_all_latest_states() {
|
||||
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 swap_id_1 = Uuid::new_v4();
|
||||
let swap_id_2 = Uuid::new_v4();
|
||||
|
||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
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())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let latest_loaded = db.all().await.unwrap();
|
||||
|
||||
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_1)));
|
||||
assert!(!latest_loaded.contains(&(swap_id_1, state_2)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_load_monero_address() -> Result<()> {
|
||||
let db = setup_test_db().await?;
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
let monero_address = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a".parse()?;
|
||||
|
||||
db.insert_monero_address(swap_id, monero_address).await?;
|
||||
|
||||
let loaded_monero_address = db.get_monero_address(swap_id).await?;
|
||||
|
||||
assert_eq!(monero_address, loaded_monero_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_and_load_multiaddr() -> Result<()> {
|
||||
let db = setup_test_db().await?;
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
let multiaddr1 = "/ip4/127.0.0.1".parse::<Multiaddr>()?;
|
||||
let multiaddr2 = "/ip4/127.0.0.2".parse::<Multiaddr>()?;
|
||||
|
||||
db.insert_address(peer_id, multiaddr1.clone()).await?;
|
||||
db.insert_address(peer_id, multiaddr2.clone()).await?;
|
||||
|
||||
let loaded_multiaddr = db.get_addresses(peer_id).await?;
|
||||
|
||||
assert!(loaded_multiaddr.contains(&multiaddr1));
|
||||
assert!(loaded_multiaddr.contains(&multiaddr2));
|
||||
assert_eq!(loaded_multiaddr.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_and_load_peer_id() -> Result<()> {
|
||||
let db = setup_test_db().await?;
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
let multiaddr1 = "/ip4/127.0.0.1".parse::<Multiaddr>()?;
|
||||
let multiaddr2 = "/ip4/127.0.0.2".parse::<Multiaddr>()?;
|
||||
|
||||
db.insert_address(peer_id, multiaddr1.clone()).await?;
|
||||
db.insert_address(peer_id, multiaddr2.clone()).await?;
|
||||
|
||||
let loaded_multiaddr = db.get_addresses(peer_id).await?;
|
||||
|
||||
assert!(loaded_multiaddr.contains(&multiaddr1));
|
||||
assert!(loaded_multiaddr.contains(&multiaddr2));
|
||||
assert_eq!(loaded_multiaddr.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_test_db() -> Result<SqliteDatabase> {
|
||||
let temp_db = tempdir().unwrap().into_path().join("tempdb");
|
||||
|
||||
// file has to exist in order to connect with sqlite
|
||||
File::create(temp_db.clone()).unwrap();
|
||||
|
||||
let db = SqliteDatabase::open(temp_db).await?;
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
use crate::asb;
|
||||
use crate::bitcoin::{CancelTimelock, PunishTimelock};
|
||||
use serde::Serialize;
|
||||
use std::cmp::max;
|
||||
use std::time::Duration;
|
||||
use time::ext::NumericalStdDuration;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize)]
|
||||
pub struct Config {
|
||||
pub bitcoin_lock_mempool_timeout: Duration,
|
||||
pub bitcoin_lock_confirmed_timeout: Duration,
|
||||
@ -15,6 +16,7 @@ pub struct Config {
|
||||
pub bitcoin_network: bitcoin::Network,
|
||||
pub monero_avg_block_time: Duration,
|
||||
pub monero_finality_confirmations: u64,
|
||||
#[serde(with = "monero_network")]
|
||||
pub monero_network: monero::Network,
|
||||
}
|
||||
|
||||
@ -123,6 +125,23 @@ pub fn new(is_testnet: bool, asb_config: &asb::config::Config) -> Config {
|
||||
}
|
||||
}
|
||||
|
||||
mod monero_network {
|
||||
use crate::monero::Network;
|
||||
use serde::Serializer;
|
||||
|
||||
pub fn serialize<S>(x: &monero::Network, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let str = match x {
|
||||
Network::Mainnet => "mainnet",
|
||||
Network::Stagenet => "stagenet",
|
||||
Network::Testnet => "testnet",
|
||||
};
|
||||
s.serialize_str(&str)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -234,6 +234,15 @@ pub mod monero_private_key {
|
||||
let mut s = s;
|
||||
PrivateKey::consensus_decode(&mut s).map_err(|err| E::custom(format!("{:?}", err)))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
let bytes = hex::decode(s).map_err(|err| E::custom(format!("{:?}", err)))?;
|
||||
PrivateKey::consensus_decode(&mut bytes.as_slice())
|
||||
.map_err(|err| E::custom(format!("{:?}", err)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S>(x: &PrivateKey, s: S) -> Result<S::Ok, S::Error>
|
||||
@ -243,7 +252,11 @@ pub mod monero_private_key {
|
||||
let mut bytes = Cursor::new(vec![]);
|
||||
x.consensus_encode(&mut bytes)
|
||||
.map_err(|err| S::Error::custom(format!("{:?}", err)))?;
|
||||
s.serialize_bytes(bytes.into_inner().as_ref())
|
||||
if s.is_human_readable() {
|
||||
s.serialize_str(&hex::encode(bytes.into_inner()))
|
||||
} else {
|
||||
s.serialize_bytes(bytes.into_inner().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(
|
||||
@ -252,7 +265,13 @@ pub mod monero_private_key {
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let key = deserializer.deserialize_bytes(BytesVisitor)?;
|
||||
let key = {
|
||||
if deserializer.is_human_readable() {
|
||||
deserializer.deserialize_string(BytesVisitor)?
|
||||
} else {
|
||||
deserializer.deserialize_bytes(BytesVisitor)?
|
||||
}
|
||||
};
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
@ -351,7 +370,17 @@ mod tests {
|
||||
pub struct MoneroAmount(#[serde(with = "monero_amount")] crate::monero::Amount);
|
||||
|
||||
#[test]
|
||||
fn serde_monero_private_key() {
|
||||
fn serde_monero_private_key_json() {
|
||||
let key = MoneroPrivateKey(monero::PrivateKey::from_scalar(
|
||||
crate::monero::Scalar::random(&mut OsRng),
|
||||
));
|
||||
let encoded = serde_json::to_vec(&key).unwrap();
|
||||
let decoded: MoneroPrivateKey = serde_json::from_slice(&encoded).unwrap();
|
||||
assert_eq!(key, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_monero_private_key_cbor() {
|
||||
let key = MoneroPrivateKey(monero::PrivateKey::from_scalar(
|
||||
crate::monero::Scalar::random(&mut OsRng),
|
||||
));
|
||||
|
@ -1,10 +1,18 @@
|
||||
use crate::protocol::alice::swap::is_complete as alice_is_complete;
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::bob::swap::is_complete as bob_is_complete;
|
||||
use crate::protocol::bob::BobState;
|
||||
use crate::{bitcoin, monero};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use conquer_once::Lazy;
|
||||
use ecdsa_fun::fun::marker::Mark;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use sigma_fun::ext::dl_secp256k1_ed25519_eq::{CrossCurveDLEQ, CrossCurveDLEQProof};
|
||||
use sigma_fun::HashTranscript;
|
||||
use std::convert::TryInto;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod alice;
|
||||
@ -65,3 +73,74 @@ pub struct Message4 {
|
||||
tx_punish_sig: bitcoin::Signature,
|
||||
tx_cancel_sig: bitcoin::Signature,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum State {
|
||||
Alice(AliceState),
|
||||
Bob(BobState),
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn swap_finished(&self) -> bool {
|
||||
match self {
|
||||
State::Alice(state) => alice_is_complete(state),
|
||||
State::Bob(state) => bob_is_complete(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AliceState> for State {
|
||||
fn from(alice: AliceState) -> Self {
|
||||
Self::Alice(alice)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BobState> for State {
|
||||
fn from(bob: BobState) -> Self {
|
||||
Self::Bob(bob)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq)]
|
||||
#[error("Not in the role of Alice")]
|
||||
pub struct NotAlice;
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq)]
|
||||
#[error("Not in the role of Bob")]
|
||||
pub struct NotBob;
|
||||
|
||||
impl TryInto<BobState> for State {
|
||||
type Error = NotBob;
|
||||
|
||||
fn try_into(self) -> std::result::Result<BobState, Self::Error> {
|
||||
match self {
|
||||
State::Alice(_) => Err(NotBob),
|
||||
State::Bob(state) => Ok(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<AliceState> for State {
|
||||
type Error = NotAlice;
|
||||
|
||||
fn try_into(self) -> std::result::Result<AliceState, Self::Error> {
|
||||
match self {
|
||||
State::Alice(state) => Ok(state),
|
||||
State::Bob(_) => Err(NotAlice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Database {
|
||||
async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()>;
|
||||
async fn get_peer_id(&self, swap_id: Uuid) -> Result<PeerId>;
|
||||
async fn insert_monero_address(&self, swap_id: Uuid, address: monero::Address) -> Result<()>;
|
||||
async fn get_monero_address(&self, swap_id: Uuid) -> Result<monero::Address>;
|
||||
async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()>;
|
||||
async fn get_addresses(&self, peer_id: PeerId) -> Result<Vec<Multiaddr>>;
|
||||
async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()>;
|
||||
async fn get_state(&self, swap_id: Uuid) -> Result<State>;
|
||||
async fn all(&self) -> Result<Vec<(Uuid, State)>>;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Run an XMR/BTC swap in the role of Alice.
|
||||
//! Alice holds XMR and wishes receive BTC.
|
||||
use crate::database::Database;
|
||||
use crate::env::Config;
|
||||
use crate::protocol::Database;
|
||||
use crate::{asb, bitcoin, monero};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
@ -19,5 +19,5 @@ pub struct Swap {
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub env_config: Config,
|
||||
pub swap_id: Uuid,
|
||||
pub db: Arc<Database>,
|
||||
pub db: Arc<dyn Database + Send + Sync>,
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof;
|
||||
use std::fmt;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AliceState {
|
||||
Started {
|
||||
state3: Box<State3>,
|
||||
|
@ -4,7 +4,7 @@ use crate::asb::{EventLoopHandle, LatestRate};
|
||||
use crate::bitcoin::ExpiredTimelocks;
|
||||
use crate::env::Config;
|
||||
use crate::protocol::alice::{AliceState, Swap};
|
||||
use crate::{bitcoin, database, monero};
|
||||
use crate::{bitcoin, monero};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use tokio::select;
|
||||
use tokio::time::timeout;
|
||||
@ -40,9 +40,8 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let db_state = (¤t_state).into();
|
||||
swap.db
|
||||
.insert_latest_state(swap.swap_id, database::Swap::Alice(db_state))
|
||||
.insert_latest_state(swap.swap_id, current_state.clone().into())
|
||||
.await?;
|
||||
}
|
||||
|
||||
@ -398,7 +397,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn is_complete(state: &AliceState) -> bool {
|
||||
pub(crate) fn is_complete(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::XmrRefunded
|
||||
|
@ -3,11 +3,12 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::protocol::Database;
|
||||
use crate::{bitcoin, cli, env, monero};
|
||||
|
||||
pub use self::state::*;
|
||||
pub use self::swap::{run, run_until};
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub mod state;
|
||||
pub mod swap;
|
||||
@ -15,7 +16,7 @@ pub mod swap;
|
||||
pub struct Swap {
|
||||
pub state: BobState,
|
||||
pub event_loop_handle: cli::EventLoopHandle,
|
||||
pub db: Database,
|
||||
pub db: Arc<dyn Database + Send + Sync>,
|
||||
pub bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
pub monero_wallet: Arc<monero::Wallet>,
|
||||
pub env_config: env::Config,
|
||||
@ -26,7 +27,7 @@ pub struct Swap {
|
||||
impl Swap {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
db: Database,
|
||||
db: Arc<dyn Database + Send + Sync>,
|
||||
id: Uuid,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
@ -52,8 +53,8 @@ impl Swap {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn from_db(
|
||||
db: Database,
|
||||
pub async fn from_db(
|
||||
db: Arc<dyn Database + Send + Sync>,
|
||||
id: Uuid,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
@ -61,7 +62,7 @@ impl Swap {
|
||||
event_loop_handle: cli::EventLoopHandle,
|
||||
monero_receive_address: monero::Address,
|
||||
) -> Result<Self> {
|
||||
let state = db.get_state(id)?.try_into_bob()?.into();
|
||||
let state = db.get_state(id).await?.try_into()?;
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
|
@ -21,7 +21,7 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof;
|
||||
use std::fmt;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BobState {
|
||||
Started {
|
||||
btc_amount: bitcoin::Amount,
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund};
|
||||
use crate::cli::EventLoopHandle;
|
||||
use crate::database::Swap;
|
||||
use crate::network::swap_setup::bob::NewSwap;
|
||||
use crate::protocol::bob;
|
||||
use crate::protocol::bob::state::*;
|
||||
@ -33,7 +32,7 @@ pub async fn run_until(
|
||||
while !is_target_state(¤t_state) {
|
||||
current_state = next_state(
|
||||
swap.id,
|
||||
current_state,
|
||||
current_state.clone(),
|
||||
&mut swap.event_loop_handle,
|
||||
swap.bitcoin_wallet.as_ref(),
|
||||
swap.monero_wallet.as_ref(),
|
||||
@ -41,9 +40,8 @@ pub async fn run_until(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let db_state = current_state.clone().into();
|
||||
swap.db
|
||||
.insert_latest_state(swap.id, Swap::Bob(db_state))
|
||||
.insert_latest_state(swap.id, current_state.clone().into())
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
@ -16,15 +16,16 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund};
|
||||
use swap::database::Database;
|
||||
use swap::database::SqliteDatabase;
|
||||
use swap::env::{Config, GetConfig};
|
||||
use swap::fs::ensure_directory_exists;
|
||||
use swap::network::swarm;
|
||||
use swap::protocol::alice::{AliceState, Swap};
|
||||
use swap::protocol::bob::BobState;
|
||||
use swap::protocol::{alice, bob};
|
||||
use swap::seed::Seed;
|
||||
use swap::{asb, bitcoin, cli, env, monero};
|
||||
use tempfile::tempdir;
|
||||
use tempfile::{tempdir, NamedTempFile};
|
||||
use testcontainers::clients::Cli;
|
||||
use testcontainers::{Container, Docker, RunArgs};
|
||||
use tokio::sync::mpsc;
|
||||
@ -82,7 +83,7 @@ where
|
||||
.parse()
|
||||
.expect("failed to parse Alice's address");
|
||||
|
||||
let alice_db_path = tempdir().unwrap().into_path();
|
||||
let alice_db_path = NamedTempFile::new().unwrap().path().to_path_buf();
|
||||
let (alice_handle, alice_swap_handle) = start_alice(
|
||||
&alice_seed,
|
||||
alice_db_path.clone(),
|
||||
@ -110,7 +111,7 @@ where
|
||||
|
||||
let bob_params = BobParams {
|
||||
seed: Seed::random().unwrap(),
|
||||
db_path: tempdir().unwrap().path().to_path_buf(),
|
||||
db_path: NamedTempFile::new().unwrap().path().to_path_buf(),
|
||||
bitcoin_wallet: bob_bitcoin_wallet.clone(),
|
||||
monero_wallet: bob_monero_wallet.clone(),
|
||||
alice_address: alice_listen_address.clone(),
|
||||
@ -222,7 +223,13 @@ async fn start_alice(
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
monero_wallet: Arc<monero::Wallet>,
|
||||
) -> (AliceApplicationHandle, Receiver<alice::Swap>) {
|
||||
let db = Arc::new(Database::open(db_path.as_path()).unwrap());
|
||||
if let Some(parent_dir) = db_path.parent() {
|
||||
ensure_directory_exists(parent_dir).unwrap();
|
||||
}
|
||||
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 min_buy = bitcoin::Amount::from_sat(u64::MIN);
|
||||
let max_buy = bitcoin::Amount::from_sat(u64::MAX);
|
||||
@ -402,7 +409,14 @@ struct BobParams {
|
||||
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?;
|
||||
let db = Database::open(&self.db_path)?;
|
||||
|
||||
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 swap = bob::Swap::from_db(
|
||||
db,
|
||||
@ -412,7 +426,8 @@ impl BobParams {
|
||||
self.env_config,
|
||||
handle,
|
||||
self.monero_wallet.get_main_address(),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((swap, event_loop))
|
||||
}
|
||||
@ -424,7 +439,14 @@ impl BobParams {
|
||||
let swap_id = Uuid::new_v4();
|
||||
|
||||
let (event_loop, handle) = self.new_eventloop(swap_id).await?;
|
||||
let db = Database::open(&self.db_path)?;
|
||||
|
||||
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 swap = bob::Swap::new(
|
||||
db,
|
||||
|
Loading…
Reference in New Issue
Block a user