780: Sqlite database r=rishflab a=rishflab

Closes #427, #762, #770

Co-authored-by: rishflab <rishflab@hotmail.com>
This commit is contained in:
bors[bot] 2021-10-06 23:26:43 +00:00 committed by GitHub
commit 9ea73a8e66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1721 additions and 544 deletions

View File

@ -104,7 +104,6 @@ jobs:
alice_manually_punishes_after_bob_dead, alice_manually_punishes_after_bob_dead,
alice_refunds_after_restart_bob_refunded, alice_refunds_after_restart_bob_refunded,
ensure_same_swap_id, ensure_same_swap_id,
concurrent_bobs_after_xmr_lock_proof_sent,
concurrent_bobs_before_xmr_lock_proof_sent, concurrent_bobs_before_xmr_lock_proof_sent,
alice_manually_redeems_after_enc_sig_learned alice_manually_redeems_after_enc_sig_learned
] ]

View File

@ -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. 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. 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. - 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
- Added a `disable-timestamp` flag to the ASB that disables timestamps from logs. - 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 ## [0.8.3] - 2021-09-03

248
Cargo.lock generated
View File

@ -49,6 +49,17 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" 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]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@ -128,6 +139,15 @@ dependencies = [
"pin-project-lite 0.2.6", "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]] [[package]]
name = "atomic" name = "atomic"
version = "0.5.0" version = "0.5.0"
@ -610,7 +630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"nom", "nom 5.1.2",
"serde", "serde",
"toml", "toml",
] ]
@ -691,6 +711,21 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 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]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.2.1" version = "1.2.1"
@ -723,6 +758,16 @@ dependencies = [
"scopeguard", "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]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.5" version = "0.8.5"
@ -937,6 +982,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]] [[package]]
name = "ecdsa_fun" name = "ecdsa_fun"
version = "0.6.2-alpha.0" version = "0.6.2-alpha.0"
@ -977,6 +1028,9 @@ name = "either"
version = "1.6.1" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "electrum-client" name = "electrum-client"
@ -1175,6 +1229,17 @@ dependencies = [
"num_cpus", "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]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.17" version = "0.3.17"
@ -1362,7 +1427,25 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [ 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]] [[package]]
@ -1614,7 +1697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [ dependencies = [
"autocfg 1.0.1", "autocfg 1.0.1",
"hashbrown", "hashbrown 0.9.1",
] ]
[[package]] [[package]]
@ -2088,6 +2171,17 @@ dependencies = [
"libsecp256k1-core", "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]] [[package]]
name = "libz-sys" name = "libz-sys"
version = "1.1.2" version = "1.1.2"
@ -2148,7 +2242,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f374d42cdfc1d7dbf3d3dec28afab2eb97ffbf43a3234d795b5986dbf4b90ba" checksum = "1f374d42cdfc1d7dbf3d3dec28afab2eb97ffbf43a3234d795b5986dbf4b90ba"
dependencies = [ dependencies = [
"hashbrown", "hashbrown 0.9.1",
] ]
[[package]] [[package]]
@ -2231,6 +2325,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "minimal-lexical"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c835948974f68e0bd58636fc6c5b1fbff7b297e3046f11b3b3c18bbac012c6d"
[[package]] [[package]]
name = "miniscript" name = "miniscript"
version = "5.1.0" version = "5.1.0"
@ -2429,6 +2529,17 @@ dependencies = [
"version_check 0.9.3", "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]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.3.6" version = "0.3.6"
@ -3536,6 +3647,7 @@ version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
dependencies = [ dependencies = [
"indexmap",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
@ -3804,12 +3916,122 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 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]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 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]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
@ -3906,6 +4128,7 @@ dependencies = [
"ed25519-dalek", "ed25519-dalek",
"futures", "futures",
"get-port", "get-port",
"hex 0.4.3",
"hyper 0.14.12", "hyper 0.14.12",
"itertools 0.10.1", "itertools 0.10.1",
"libp2p", "libp2p",
@ -3930,6 +4153,7 @@ dependencies = [
"sigma_fun", "sigma_fun",
"sled", "sled",
"spectral", "spectral",
"sqlx",
"structopt", "structopt",
"strum", "strum",
"tempfile", "tempfile",
@ -4508,6 +4732,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
version = "0.4.0" version = "0.4.0"
@ -4790,6 +5020,16 @@ dependencies = [
"thiserror", "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]] [[package]]
name = "widestring" name = "widestring"
version = "0.4.3" version = "0.4.3"

View File

@ -17,7 +17,6 @@ status = [
"docker_tests (alice_manually_punishes_after_bob_dead)", "docker_tests (alice_manually_punishes_after_bob_dead)",
"docker_tests (alice_refunds_after_restart_bob_refunded)", "docker_tests (alice_refunds_after_restart_bob_refunded)",
"docker_tests (ensure_same_swap_id)", "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 (concurrent_bobs_before_xmr_lock_proof_sent)",
"docker_tests (alice_manually_redeems_after_enc_sig_learned)" "docker_tests (alice_manually_redeems_after_enc_sig_learned)"
] ]

View File

@ -29,6 +29,7 @@ directories-next = "2"
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde" ] } ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde" ] }
ed25519-dalek = "1" ed25519-dalek = "1"
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }
hex = "0.4"
itertools = "0.10" 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" ] } 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" ] } miniscript = { version = "5", features = [ "serde" ] }
@ -49,6 +50,7 @@ serde_with = { version = "1", features = [ "macros" ] }
sha2 = "0.9" sha2 = "0.9"
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] } sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] }
sled = "0.34" sled = "0.34"
sqlx = { version = "0.5", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] }
structopt = "0.3" structopt = "0.3"
strum = { version = "0.21", features = [ "derive" ] } strum = { version = "0.21", features = [ "derive" ] }
thiserror = "1" thiserror = "1"

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

View File

@ -21,6 +21,7 @@ where
let json = args.json; let json = args.json;
let disable_timestamp = args.disable_timestamp; let disable_timestamp = args.disable_timestamp;
let testnet = args.testnet; let testnet = args.testnet;
let sled = args.sled;
let config = args.config; let config = args.config;
let command: RawCommand = args.cmd; let command: RawCommand = args.cmd;
@ -29,6 +30,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::Start { resume_only }, cmd: Command::Start { resume_only },
@ -37,6 +39,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::History, cmd: Command::History,
@ -45,6 +48,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::WithdrawBtc { cmd: Command::WithdrawBtc {
@ -56,10 +60,20 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::Balance, 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 { RawCommand::ManualRecovery(ManualRecovery::Redeem {
redeem_params: RecoverCommandParams { swap_id }, redeem_params: RecoverCommandParams { swap_id },
do_not_await_finality, do_not_await_finality,
@ -67,6 +81,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::Redeem { cmd: Command::Redeem {
@ -81,6 +96,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::Cancel { swap_id }, cmd: Command::Cancel { swap_id },
@ -91,6 +107,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::Refund { swap_id }, cmd: Command::Refund { swap_id },
@ -101,6 +118,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::Punish { swap_id }, cmd: Command::Punish { swap_id },
@ -109,6 +127,7 @@ where
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path: config_path(config, testnet)?, config_path: config_path(config, testnet)?,
env_config: env_config(testnet), env_config: env_config(testnet),
cmd: Command::SafelyAbort { swap_id }, cmd: Command::SafelyAbort { swap_id },
@ -168,6 +187,7 @@ pub struct BitcoinAddressNetworkMismatch {
pub struct Arguments { pub struct Arguments {
pub testnet: bool, pub testnet: bool,
pub json: bool, pub json: bool,
pub sled: bool,
pub disable_timestamp: bool, pub disable_timestamp: bool,
pub config_path: PathBuf, pub config_path: PathBuf,
pub env_config: env::Config, pub env_config: env::Config,
@ -180,6 +200,7 @@ pub enum Command {
resume_only: bool, resume_only: bool,
}, },
History, History,
Config,
WithdrawBtc { WithdrawBtc {
amount: Option<Amount>, amount: Option<Amount>,
address: Address, address: Address,
@ -228,6 +249,13 @@ pub struct RawArguments {
)] )]
pub disable_timestamp: bool, 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( #[structopt(
long = "config", long = "config",
help = "Provide a custom path to the configuration file. The configuration file must be a toml file.", 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.")] #[structopt(about = "Prints swap-id and the state of each swap ever made.")]
History, History,
#[structopt(about = "Prints the current config")]
Config,
#[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")] #[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")]
WithdrawBtc { WithdrawBtc {
#[structopt( #[structopt(
@ -344,6 +374,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -362,6 +393,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -380,6 +412,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -402,6 +435,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -429,6 +463,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -455,6 +490,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -481,6 +517,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -507,6 +544,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,
@ -527,6 +565,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -545,6 +584,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -563,6 +603,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -587,6 +628,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -614,6 +656,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -641,6 +684,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -668,6 +712,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -695,6 +740,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: true, testnet: true,
json: false, json: false,
sled: false,
disable_timestamp: false, disable_timestamp: false,
config_path: default_testnet_conf_path, config_path: default_testnet_conf_path,
env_config: testnet_env_config, env_config: testnet_env_config,
@ -715,6 +761,7 @@ mod tests {
let expected_args = Arguments { let expected_args = Arguments {
testnet: false, testnet: false,
json: false, json: false,
sled: false,
disable_timestamp: true, disable_timestamp: true,
config_path: default_mainnet_conf_path, config_path: default_mainnet_conf_path,
env_config: mainnet_env_config, env_config: mainnet_env_config,

View File

@ -1,9 +1,9 @@
use crate::asb::{Behaviour, OutEvent, Rate}; use crate::asb::{Behaviour, OutEvent, Rate};
use crate::database::Database;
use crate::network::quote::BidQuote; use crate::network::quote::BidQuote;
use crate::network::swap_setup::alice::WalletSnapshot; use crate::network::swap_setup::alice::WalletSnapshot;
use crate::network::transfer_proof; use crate::network::transfer_proof;
use crate::protocol::alice::{AliceState, State3, Swap}; use crate::protocol::alice::{AliceState, State3, Swap};
use crate::protocol::{Database, State};
use crate::{bitcoin, env, kraken, monero}; use crate::{bitcoin, env, kraken, monero};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use futures::future; use futures::future;
@ -14,7 +14,7 @@ use libp2p::swarm::SwarmEvent;
use libp2p::{PeerId, Swarm}; use libp2p::{PeerId, Swarm};
use rust_decimal::Decimal; use rust_decimal::Decimal;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::Infallible; use std::convert::{Infallible, TryInto};
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -39,7 +39,7 @@ where
env_config: env::Config, env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
db: Arc<Database>, db: Arc<dyn Database + Send + Sync>,
latest_rate: LR, latest_rate: LR,
min_buy: bitcoin::Amount, min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
@ -71,7 +71,7 @@ where
env_config: env::Config, env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
db: Arc<Database>, db: Arc<dyn Database + Send + Sync>,
latest_rate: LR, latest_rate: LR,
min_buy: bitcoin::Amount, min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount, max_buy: bitcoin::Amount,
@ -108,16 +108,21 @@ where
self.inflight_encrypted_signatures self.inflight_encrypted_signatures
.push(future::pending().boxed()); .push(future::pending().boxed());
let unfinished_swaps = match self.db.unfinished_alice() { let swaps = match self.db.all().await {
Ok(unfinished_swaps) => unfinished_swaps, Ok(swaps) => swaps,
Err(_) => { Err(e) => {
tracing::error!("Failed to load unfinished swaps"); tracing::error!("Failed to load swaps from database: {}", e);
return; 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 { 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, Ok(peer_id) => peer_id,
Err(_) => { Err(_) => {
tracing::warn!(%swap_id, "Resuming swap skipped because no peer-id found for swap in database"); 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(), monero_wallet: self.monero_wallet.clone(),
env_config: self.env_config, env_config: self.env_config,
db: self.db.clone(), db: self.db.clone(),
state: state.into(), state: state.try_into().expect("Alice state loaded from db"),
swap_id, swap_id,
}; };
@ -197,7 +202,7 @@ where
} }
SwarmEvent::Behaviour(OutEvent::EncryptedSignatureReceived{ msg, channel, peer }) => { SwarmEvent::Behaviour(OutEvent::EncryptedSignatureReceived{ msg, channel, peer }) => {
let swap_id = msg.swap_id; 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 // Ensure that an incoming encrypted signature is sent by the peer-id associated with the swap
let swap_peer = match swap_peer { let swap_peer = match swap_peer {

View File

@ -1,16 +1,17 @@
use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Txid, Wallet}; use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Txid, Wallet};
use crate::database::{Database, Swap};
use crate::protocol::alice::AliceState; use crate::protocol::alice::AliceState;
use crate::protocol::Database;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
pub async fn cancel( pub async fn cancel(
swap_id: Uuid, swap_id: Uuid,
bitcoin_wallet: Arc<Wallet>, bitcoin_wallet: Arc<Wallet>,
db: Arc<Database>, db: Arc<dyn Database>,
) -> Result<(Txid, AliceState)> { ) -> 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 { let (monero_wallet_restore_blockheight, transfer_proof, state3) = match state {
@ -58,8 +59,7 @@ pub async fn cancel(
transfer_proof, transfer_proof,
state3, state3,
}; };
let db_state = (&state).into(); db.insert_latest_state(swap_id, state.clone().into())
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
Ok((txid, state)) Ok((txid, state))

View File

@ -1,7 +1,8 @@
use crate::bitcoin::{self, Txid}; use crate::bitcoin::{self, Txid};
use crate::database::{Database, Swap};
use crate::protocol::alice::AliceState; use crate::protocol::alice::AliceState;
use crate::protocol::Database;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
@ -14,9 +15,9 @@ pub enum Error {
pub async fn punish( pub async fn punish(
swap_id: Uuid, swap_id: Uuid,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
db: Arc<Database>, db: Arc<dyn Database>,
) -> Result<(Txid, AliceState)> { ) -> 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 { let state3 = match state {
// Punish potentially possible (no knowledge of cancel transaction) // 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 txid = state3.punish_btc(&bitcoin_wallet).await?;
let state = AliceState::BtcPunished; let state = AliceState::BtcPunished;
let db_state = (&state).into(); db.insert_latest_state(swap_id, state.clone().into())
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
Ok((txid, state)) Ok((txid, state))

View File

@ -1,7 +1,8 @@
use crate::bitcoin::{Txid, Wallet}; use crate::bitcoin::{Txid, Wallet};
use crate::database::{Database, Swap};
use crate::protocol::alice::AliceState; use crate::protocol::alice::AliceState;
use crate::protocol::Database;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
@ -23,10 +24,10 @@ impl Finality {
pub async fn redeem( pub async fn redeem(
swap_id: Uuid, swap_id: Uuid,
bitcoin_wallet: Arc<Wallet>, bitcoin_wallet: Arc<Wallet>,
db: Arc<Database>, db: Arc<dyn Database>,
finality: Finality, finality: Finality,
) -> Result<(Txid, AliceState)> { ) -> 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 { match state {
AliceState::EncSigLearned { AliceState::EncSigLearned {
@ -42,17 +43,14 @@ pub async fn redeem(
subscription.wait_until_seen().await?; subscription.wait_until_seen().await?;
let state = AliceState::BtcRedeemTransactionPublished { state3 }; let state = AliceState::BtcRedeemTransactionPublished { state3 };
let db_state = (&state).into(); db.insert_latest_state(swap_id, state.into()).await?;
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?;
if let Finality::Await = finality { if let Finality::Await = finality {
subscription.wait_until_final().await?; subscription.wait_until_final().await?;
} }
let state = AliceState::BtcRedeemed; let state = AliceState::BtcRedeemed;
let db_state = (&state).into(); db.insert_latest_state(swap_id, state.clone().into())
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
Ok((txid, state)) Ok((txid, state))
@ -64,8 +62,7 @@ pub async fn redeem(
} }
let state = AliceState::BtcRedeemed; let state = AliceState::BtcRedeemed;
let db_state = (&state).into(); db.insert_latest_state(swap_id, state.clone().into())
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
let txid = state3.tx_redeem().txid(); let txid = state3.tx_redeem().txid();

View File

@ -1,9 +1,10 @@
use crate::bitcoin::{self}; use crate::bitcoin::{self};
use crate::database::{Database, Swap};
use crate::monero; use crate::monero;
use crate::protocol::alice::AliceState; use crate::protocol::alice::AliceState;
use crate::protocol::Database;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use libp2p::PeerId; use libp2p::PeerId;
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
@ -26,9 +27,9 @@ pub async fn refund(
swap_id: Uuid, swap_id: Uuid,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
db: Arc<Database>, db: Arc<dyn Database>,
) -> Result<AliceState> { ) -> 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 { let (monero_wallet_restore_blockheight, transfer_proof, state3) = match state {
// In case no XMR has been locked, move to Safely Aborted // 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"); tracing::debug!(%swap_id, "Bitcoin refund transaction found, extracting key to refund Monero");
state3.extract_monero_private_key(published_refund_tx)? state3.extract_monero_private_key(published_refund_tx)?
} else { } 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),); bail!(Error::RefundTransactionNotPublishedYet(bob_peer_id),);
}; };
@ -81,8 +82,7 @@ pub async fn refund(
.await?; .await?;
let state = AliceState::XmrRefunded; let state = AliceState::XmrRefunded;
let db_state = (&state).into(); db.insert_latest_state(swap_id, state.clone().into())
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
Ok(state) Ok(state)

View File

@ -1,11 +1,12 @@
use crate::database::{Database, Swap};
use crate::protocol::alice::AliceState; use crate::protocol::alice::AliceState;
use crate::protocol::Database;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
pub async fn safely_abort(swap_id: Uuid, db: Arc<Database>) -> Result<AliceState> { pub async fn safely_abort(swap_id: Uuid, 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()?;
match state { match state {
AliceState::Started { .. } AliceState::Started { .. }
@ -13,8 +14,7 @@ pub async fn safely_abort(swap_id: Uuid, db: Arc<Database>) -> Result<AliceState
| AliceState::BtcLocked { .. } => { | AliceState::BtcLocked { .. } => {
let state = AliceState::SafelyAborted; let state = AliceState::SafelyAborted;
let db_state = (&state).into(); db.insert_latest_state(swap_id, state.clone().into())
db.insert_latest_state(swap_id, Swap::Alice(db_state))
.await?; .await?;
Ok(state) Ok(state)

View File

@ -18,6 +18,7 @@ use libp2p::core::multiaddr::Protocol;
use libp2p::core::Multiaddr; use libp2p::core::Multiaddr;
use libp2p::swarm::AddressScore; use libp2p::swarm::AddressScore;
use libp2p::Swarm; use libp2p::Swarm;
use std::convert::TryInto;
use std::env; use std::env;
use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc; use std::sync::Arc;
@ -28,11 +29,11 @@ use swap::asb::config::{
initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized, 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::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::monero::Amount;
use swap::network::rendezvous::XmrBtcNamespace; use swap::network::rendezvous::XmrBtcNamespace;
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::alice::run; use swap::protocol::alice::{run, AliceState};
use swap::seed::Seed; use swap::seed::Seed;
use swap::tor::AuthenticatedClient; use swap::tor::AuthenticatedClient;
use swap::{asb, bitcoin, kraken, monero, tor}; use swap::{asb, bitcoin, kraken, monero, tor};
@ -46,6 +47,7 @@ async fn main() -> Result<()> {
testnet, testnet,
json, json,
disable_timestamp, disable_timestamp,
sled,
config_path, config_path,
env_config, env_config,
cmd, cmd,
@ -91,9 +93,9 @@ async fn main() -> Result<()> {
} }
let db_path = config.data.dir.join("database"); 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()) let db = open_db(sled_path, config.data.dir.join("sqlite"), sled).await?;
.context("Could not open database")?;
let seed = let seed =
Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize 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, env_config,
Arc::new(bitcoin_wallet), Arc::new(bitcoin_wallet),
Arc::new(monero_wallet), Arc::new(monero_wallet),
Arc::new(db), db,
kraken_rate.clone(), kraken_rate.clone(),
config.maker.min_buy_btc, config.maker.min_buy_btc,
config.maker.max_buy_btc, config.maker.max_buy_btc,
@ -208,12 +210,17 @@ async fn main() -> Result<()> {
table.set_header(vec!["SWAP ID", "STATE"]); 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()]); table.add_row(vec![swap_id.to_string(), state.to_string()]);
} }
println!("{}", table); println!("{}", table);
} }
Command::Config => {
let config_json = serde_json::to_string_pretty(&config)?;
println!("{}", config_json);
}
Command::WithdrawBtc { amount, address } => { Command::WithdrawBtc { amount, address } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
@ -248,7 +255,7 @@ async fn main() -> Result<()> {
Command::Cancel { swap_id } => { Command::Cancel { swap_id } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; 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); tracing::info!("Cancel transaction successfully published with id {}", txid);
} }
@ -260,7 +267,7 @@ async fn main() -> Result<()> {
swap_id, swap_id,
Arc::new(bitcoin_wallet), Arc::new(bitcoin_wallet),
Arc::new(monero_wallet), Arc::new(monero_wallet),
Arc::new(db), db,
) )
.await?; .await?;
@ -269,12 +276,12 @@ async fn main() -> Result<()> {
Command::Punish { swap_id } => { Command::Punish { swap_id } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; 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); tracing::info!("Punish transaction successfully published with id {}", txid);
} }
Command::SafelyAbort { swap_id } => { Command::SafelyAbort { swap_id } => {
safely_abort(swap_id, Arc::new(db)).await?; safely_abort(swap_id, db).await?;
tracing::info!("Swap safely aborted"); tracing::info!("Swap safely aborted");
} }
@ -287,7 +294,7 @@ async fn main() -> Result<()> {
let (txid, _) = redeem( let (txid, _) = redeem(
swap_id, swap_id,
Arc::new(bitcoin_wallet), Arc::new(bitcoin_wallet),
Arc::new(db), db,
Finality::from_bool(do_not_await_finality), Finality::from_bool(do_not_await_finality),
) )
.await?; .await?;

View File

@ -17,6 +17,7 @@ use comfy_table::Table;
use qrcode::render::unicode; use qrcode::render::unicode;
use qrcode::QrCode; use qrcode::QrCode;
use std::cmp::min; use std::cmp::min;
use std::convert::TryInto;
use std::env; use std::env;
use std::future::Future; use std::future::Future;
use std::path::PathBuf; use std::path::PathBuf;
@ -25,13 +26,13 @@ use std::time::Duration;
use swap::bitcoin::TxLock; use swap::bitcoin::TxLock;
use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult}; use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult};
use swap::cli::{list_sellers, EventLoop, SellerStatus}; use swap::cli::{list_sellers, EventLoop, SellerStatus};
use swap::database::Database; use swap::database::open_db;
use swap::env::Config; use swap::env::Config;
use swap::libp2p_ext::MultiAddrExt; use swap::libp2p_ext::MultiAddrExt;
use swap::network::quote::BidQuote; use swap::network::quote::BidQuote;
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::bob; use swap::protocol::bob;
use swap::protocol::bob::Swap; use swap::protocol::bob::{BobState, Swap};
use swap::seed::Seed; use swap::seed::Seed;
use swap::{bitcoin, cli, monero}; use swap::{bitcoin, cli, monero};
use url::Url; use url::Url;
@ -44,6 +45,7 @@ async fn main() -> Result<()> {
data_dir, data_dir,
debug, debug,
json, json,
sled,
cmd, cmd,
} = match parse_args_and_apply_defaults(env::args_os())? { } = match parse_args_and_apply_defaults(env::args_os())? {
ParseResult::Arguments(args) => args, ParseResult::Arguments(args) => args,
@ -66,8 +68,7 @@ async fn main() -> Result<()> {
let swap_id = Uuid::new_v4(); let swap_id = Uuid::new_v4();
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
let db = Database::open(data_dir.join("database").as_path()) let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
.context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .context("Failed to read in seed file")?;
@ -82,7 +83,6 @@ async fn main() -> Result<()> {
let (monero_wallet, _process) = let (monero_wallet, _process) =
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?; init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
let bitcoin_wallet = Arc::new(bitcoin_wallet); let bitcoin_wallet = Arc::new(bitcoin_wallet);
let seller_peer_id = seller let seller_peer_id = seller
.extract_peer_id() .extract_peer_id()
.context("Seller address must contain peer ID")?; .context("Seller address must contain peer ID")?;
@ -139,20 +139,49 @@ async fn main() -> Result<()> {
} }
} }
Command::History => { Command::History => {
let db = Database::open(data_dir.join("database").as_path()) let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
.context("Failed to open database")?;
let mut table = Table::new(); let mut table = Table::new();
table.set_header(vec!["SWAP ID", "STATE"]); 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()]); table.add_row(vec![swap_id.to_string(), state.to_string()]);
} }
println!("{}", table); 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 { Command::WithdrawBtc {
bitcoin_electrum_rpc_url, bitcoin_electrum_rpc_url,
bitcoin_target_block, bitcoin_target_block,
@ -215,8 +244,7 @@ async fn main() -> Result<()> {
tor_socks5_port, tor_socks5_port,
} => { } => {
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
let db = Database::open(data_dir.join("database").as_path()) let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
.context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .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?; init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
let bitcoin_wallet = Arc::new(bitcoin_wallet); let bitcoin_wallet = Arc::new(bitcoin_wallet);
let seller_peer_id = db.get_peer_id(swap_id)?; let seller_peer_id = db.get_peer_id(swap_id).await?;
let seller_addresses = db.get_addresses(seller_peer_id)?; let seller_addresses = db.get_addresses(seller_peer_id).await?;
let behaviour = cli::Behaviour::new(seller_peer_id, env_config, bitcoin_wallet.clone()); let behaviour = cli::Behaviour::new(seller_peer_id, env_config, bitcoin_wallet.clone());
let mut swarm = let mut swarm =
@ -251,7 +279,7 @@ async fn main() -> Result<()> {
EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?; EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?;
let handle = tokio::spawn(event_loop.run()); 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( let swap = Swap::from_db(
db, db,
swap_id, swap_id,
@ -260,7 +288,8 @@ async fn main() -> Result<()> {
env_config, env_config,
event_loop_handle, event_loop_handle,
monero_receive_address, monero_receive_address,
)?; )
.await?;
tokio::select! { tokio::select! {
event_loop_result = handle => { event_loop_result = handle => {
@ -277,8 +306,7 @@ async fn main() -> Result<()> {
bitcoin_target_block, bitcoin_target_block,
} => { } => {
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
let db = Database::open(data_dir.join("database").as_path()) let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
.context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .context("Failed to read in seed file")?;
@ -300,8 +328,7 @@ async fn main() -> Result<()> {
bitcoin_target_block, bitcoin_target_block,
} => { } => {
cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?;
let db = Database::open(data_dir.join("database").as_path()) let db = open_db(data_dir.join("database"), data_dir.join("sqlite"), sled).await?;
.context("Failed to open database")?;
let seed = Seed::from_file_or_generate(data_dir.as_path()) let seed = Seed::from_file_or_generate(data_dir.as_path())
.context("Failed to read in seed file")?; .context("Failed to read in seed file")?;

View File

@ -1,16 +1,17 @@
use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Txid, Wallet}; use crate::bitcoin::{parse_rpc_error_code, RpcErrorCode, Txid, Wallet};
use crate::database::{Database, Swap};
use crate::protocol::bob::BobState; use crate::protocol::bob::BobState;
use crate::protocol::Database;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
pub async fn cancel( pub async fn cancel(
swap_id: Uuid, swap_id: Uuid,
bitcoin_wallet: Arc<Wallet>, bitcoin_wallet: Arc<Wallet>,
db: Database, db: Arc<dyn Database>,
) -> Result<(Txid, BobState)> { ) -> 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 { let state6 = match state {
BobState::BtcLocked(state3) => state3.cancel(), BobState::BtcLocked(state3) => state3.cancel(),
@ -48,8 +49,8 @@ pub async fn cancel(
}; };
let state = BobState::BtcCancelled(state6); let state = BobState::BtcCancelled(state6);
let db_state = state.clone().into(); db.insert_latest_state(swap_id, state.clone().into())
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?; .await?;
Ok((txid, state)) Ok((txid, state))
} }

View File

@ -33,6 +33,7 @@ pub struct Arguments {
pub env_config: env::Config, pub env_config: env::Config,
pub debug: bool, pub debug: bool,
pub json: bool, pub json: bool,
pub sled: bool,
pub data_dir: PathBuf, pub data_dir: PathBuf,
pub cmd: Command, pub cmd: Command,
} }
@ -66,6 +67,7 @@ where
let debug = args.debug; let debug = args.debug;
let json = args.json; let json = args.json;
let sled = args.sled;
let is_testnet = args.testnet; let is_testnet = args.testnet;
let data = args.data; let data = args.data;
@ -90,6 +92,7 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::BuyXmr { cmd: Command::BuyXmr {
seller, seller,
@ -106,9 +109,18 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::History, 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 } => { RawCommand::Balance { bitcoin } => {
let (bitcoin_electrum_rpc_url, bitcoin_target_block) = let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
bitcoin.apply_defaults(is_testnet)?; bitcoin.apply_defaults(is_testnet)?;
@ -117,6 +129,7 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Balance { cmd: Command::Balance {
bitcoin_electrum_rpc_url, bitcoin_electrum_rpc_url,
@ -136,6 +149,7 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::WithdrawBtc { cmd: Command::WithdrawBtc {
bitcoin_electrum_rpc_url, bitcoin_electrum_rpc_url,
@ -159,6 +173,7 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Resume { cmd: Command::Resume {
swap_id, swap_id,
@ -180,6 +195,7 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Cancel { cmd: Command::Cancel {
swap_id, swap_id,
@ -199,6 +215,7 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Refund { cmd: Command::Refund {
swap_id, swap_id,
@ -214,6 +231,7 @@ where
env_config: env_config_from(is_testnet), env_config: env_config_from(is_testnet),
debug, debug,
json, json,
sled,
data_dir: data::data_dir_from(data, is_testnet)?, data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::ListSellers { cmd: Command::ListSellers {
rendezvous_point, rendezvous_point,
@ -238,6 +256,7 @@ pub enum Command {
tor_socks5_port: u16, tor_socks5_port: u16,
}, },
History, History,
Config,
WithdrawBtc { WithdrawBtc {
bitcoin_electrum_rpc_url: Url, bitcoin_electrum_rpc_url: Url,
bitcoin_target_block: usize, bitcoin_target_block: usize,
@ -304,6 +323,13 @@ struct RawArguments {
)] )]
json: bool, 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)] #[structopt(subcommand)]
cmd: RawCommand, cmd: RawCommand,
} }
@ -338,6 +364,8 @@ enum RawCommand {
}, },
/// Show a list of past, ongoing and completed swaps /// Show a list of past, ongoing and completed swaps
History, History,
#[structopt(about = "Prints the current config")]
Config,
#[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")] #[structopt(about = "Allows withdrawing BTC from the internal Bitcoin wallet.")]
WithdrawBtc { WithdrawBtc {
#[structopt(flatten)] #[structopt(flatten)]
@ -1105,6 +1133,7 @@ mod tests {
env_config: env::Testnet::get_config(), env_config: env::Testnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(TESTNET), data_dir: data_dir_path_cli().join(TESTNET),
cmd: Command::BuyXmr { cmd: Command::BuyXmr {
seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(), seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(),
@ -1125,6 +1154,7 @@ mod tests {
env_config: env::Mainnet::get_config(), env_config: env::Mainnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(MAINNET), data_dir: data_dir_path_cli().join(MAINNET),
cmd: Command::BuyXmr { cmd: Command::BuyXmr {
seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(), seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(),
@ -1144,6 +1174,7 @@ mod tests {
env_config: env::Testnet::get_config(), env_config: env::Testnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(TESTNET), data_dir: data_dir_path_cli().join(TESTNET),
cmd: Command::Resume { cmd: Command::Resume {
swap_id: Uuid::from_str(SWAP_ID).unwrap(), swap_id: Uuid::from_str(SWAP_ID).unwrap(),
@ -1161,6 +1192,7 @@ mod tests {
env_config: env::Mainnet::get_config(), env_config: env::Mainnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(MAINNET), data_dir: data_dir_path_cli().join(MAINNET),
cmd: Command::Resume { cmd: Command::Resume {
swap_id: Uuid::from_str(SWAP_ID).unwrap(), swap_id: Uuid::from_str(SWAP_ID).unwrap(),
@ -1177,6 +1209,7 @@ mod tests {
env_config: env::Testnet::get_config(), env_config: env::Testnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(TESTNET), data_dir: data_dir_path_cli().join(TESTNET),
cmd: Command::Cancel { cmd: Command::Cancel {
swap_id: Uuid::from_str(SWAP_ID).unwrap(), swap_id: Uuid::from_str(SWAP_ID).unwrap(),
@ -1192,6 +1225,7 @@ mod tests {
env_config: env::Mainnet::get_config(), env_config: env::Mainnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(MAINNET), data_dir: data_dir_path_cli().join(MAINNET),
cmd: Command::Cancel { cmd: Command::Cancel {
swap_id: Uuid::from_str(SWAP_ID).unwrap(), swap_id: Uuid::from_str(SWAP_ID).unwrap(),
@ -1206,6 +1240,7 @@ mod tests {
env_config: env::Testnet::get_config(), env_config: env::Testnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(TESTNET), data_dir: data_dir_path_cli().join(TESTNET),
cmd: Command::Refund { cmd: Command::Refund {
swap_id: Uuid::from_str(SWAP_ID).unwrap(), swap_id: Uuid::from_str(SWAP_ID).unwrap(),
@ -1221,6 +1256,7 @@ mod tests {
env_config: env::Mainnet::get_config(), env_config: env::Mainnet::get_config(),
debug: false, debug: false,
json: false, json: false,
sled: false,
data_dir: data_dir_path_cli().join(MAINNET), data_dir: data_dir_path_cli().join(MAINNET),
cmd: Command::Refund { cmd: Command::Refund {
swap_id: Uuid::from_str(SWAP_ID).unwrap(), swap_id: Uuid::from_str(SWAP_ID).unwrap(),

View File

@ -1,12 +1,17 @@
use crate::bitcoin::Wallet; use crate::bitcoin::Wallet;
use crate::database::{Database, Swap};
use crate::protocol::bob::BobState; use crate::protocol::bob::BobState;
use crate::protocol::Database;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::convert::TryInto;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
pub async fn refund(swap_id: Uuid, bitcoin_wallet: Arc<Wallet>, db: Database) -> Result<BobState> { pub async fn refund(
let state = db.get_state(swap_id)?.try_into_bob()?.into(); 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 { let state6 = match state {
BobState::BtcLocked(state3) => state3.cancel(), 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?; state6.publish_refund_btc(bitcoin_wallet.as_ref()).await?;
let state = BobState::BtcRefunded(state6); let state = BobState::BtcRefunded(state6);
let db_state = state.clone().into(); db.insert_latest_state(swap_id, state.clone().into())
.await?;
db.insert_latest_state(swap_id, Swap::Bob(db_state)).await?;
Ok(state) Ok(state)
} }

View File

@ -1,18 +1,20 @@
pub use self::sled::SledDatabase;
pub use alice::Alice; pub use alice::Alice;
pub use bob::Bob; pub use bob::Bob;
pub use sqlite::SqliteDatabase;
use anyhow::{anyhow, bail, Context, Result}; use crate::fs::ensure_directory_exists;
use itertools::Itertools; use crate::protocol::{Database, State};
use libp2p::{Multiaddr, PeerId}; use anyhow::{bail, Result};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::sync::Arc;
use uuid::Uuid;
mod alice; mod alice;
mod bob; mod bob;
mod sled;
mod sqlite;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Swap { pub enum Swap {
@ -20,6 +22,24 @@ pub enum Swap {
Bob(Bob), 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 { impl From<Alice> for Swap {
fn from(from: Alice) -> Self { fn from(from: Alice) -> Self {
Swap::Alice(from) Swap::Alice(from)
@ -65,413 +85,74 @@ impl Swap {
} }
} }
pub struct Database { pub async fn open_db(
swaps: sled::Tree, sled_path: impl AsRef<Path>,
peers: sled::Tree, sqlite_path: impl AsRef<Path>,
addresses: sled::Tree, force_sled: bool,
monero_addresses: sled::Tree, ) -> 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?;
ensure_directory_exists(sqlite_path.as_ref())?;
tokio::fs::File::create(&sqlite_path).await?;
let sqlite = SqliteDatabase::open(sqlite_path).await?;
let swap_states = sled_db.all().await?;
for (swap_id, state) in swap_states.iter() {
sqlite.insert_latest_state(*swap_id, state.clone()).await?;
} }
impl Database { let monero_addresses = sled_db.get_all_monero_addresses();
pub fn open(path: &Path) -> Result<Self> { for (swap_id, monero_address) in monero_addresses.flatten() {
tracing::debug!("Opening database at {}", path.display()); sqlite
.insert_monero_address(swap_id, monero_address)
let db = .await?;
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(Database {
swaps,
peers,
addresses,
monero_addresses,
})
} }
pub async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> { let peer_addresses = sled_db.get_all_addresses();
let peer_id_str = peer_id.to_string(); for (peer_id, addresses) in peer_addresses.flatten() {
for address in addresses {
let key = serialize(&swap_id)?; sqlite.insert_address(peer_id, address).await?;
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)?
}
None => serialize(&[address])?,
})
};
self.addresses
.compare_and_swap(key, existing_addresses, new_addresses)??;
self.addresses
.flush_async()
.await
.map(|_| ())
.context("Could not flush db")
}
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>> let peers = sled_db.get_all_peers();
where for (swap_id, peer_id) in peers.flatten() {
T: Serialize, sqlite.insert_peer_id(swap_id, peer_id).await?;
{
Ok(serde_cbor::to_vec(t)?)
} }
pub fn deserialize<T>(v: &[u8]) -> Result<T> tracing::info!("Sucessfully migrated data to sqlite! Using sqlite.");
where
T: DeserializeOwned, Ok(Arc::new(sqlite))
{
Ok(serde_cbor::from_slice(&v)?)
} }
(_, false, false) => {
#[cfg(test)] tracing::debug!("Creating and using new sqlite database.");
mod tests { ensure_directory_exists(sqlite_path.as_ref())?;
use super::*; tokio::fs::File::create(&sqlite_path).await?;
use crate::database::alice::{Alice, AliceEndState}; let sqlite = SqliteDatabase::open(sqlite_path).await?;
use crate::database::bob::{Bob, BobEndState}; Ok(Arc::new(sqlite))
#[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);
} }
(_, true, false) => {
#[tokio::test] tracing::debug!("Using existing sqlite database.");
async fn can_write_twice_to_one_key() { let sqlite = SqliteDatabase::open(sqlite_path).await?;
let db_dir = tempfile::tempdir().unwrap(); Ok(Arc::new(sqlite))
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);
} }
(false, _, true) => {
#[tokio::test] bail!("Sled database does not exist at specified location")
async fn all_swaps_as_alice() { }
let db_dir = tempfile::tempdir().unwrap(); (true, _, true) => {
let db = Database::open(db_dir.path()).unwrap(); tracing::debug!("Sled flag set. Using sled database.");
let sled = SledDatabase::open(sled_path.as_ref()).await?;
let alice_state = Alice::Done(AliceEndState::BtcPunished); Ok(Arc::new(sled))
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?;
}
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?;
}
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(())
} }
} }

View File

@ -78,8 +78,8 @@ pub enum AliceEndState {
BtcPunished, BtcPunished,
} }
impl From<&AliceState> for Alice { impl From<AliceState> for Alice {
fn from(alice_state: &AliceState) -> Self { fn from(alice_state: AliceState) -> Self {
match alice_state { match alice_state {
AliceState::Started { state3 } => Alice::Started { AliceState::Started { state3 } => Alice::Started {
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
@ -95,8 +95,8 @@ impl From<&AliceState> for Alice {
transfer_proof, transfer_proof,
state3, state3,
} => Alice::XmrLockTransactionSent { } => Alice::XmrLockTransactionSent {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
}, },
AliceState::XmrLocked { AliceState::XmrLocked {
@ -104,8 +104,8 @@ impl From<&AliceState> for Alice {
transfer_proof, transfer_proof,
state3, state3,
} => Alice::XmrLocked { } => Alice::XmrLocked {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
}, },
AliceState::XmrLockTransferProofSent { AliceState::XmrLockTransferProofSent {
@ -113,8 +113,8 @@ impl From<&AliceState> for Alice {
transfer_proof, transfer_proof,
state3, state3,
} => Alice::XmrLockTransferProofSent { } => Alice::XmrLockTransferProofSent {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
}, },
AliceState::EncSigLearned { AliceState::EncSigLearned {
@ -123,10 +123,10 @@ impl From<&AliceState> for Alice {
state3, state3,
encrypted_signature, encrypted_signature,
} => Alice::EncSigLearned { } => Alice::EncSigLearned {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
encrypted_signature: *encrypted_signature.clone(), encrypted_signature: encrypted_signature.as_ref().clone(),
}, },
AliceState::BtcRedeemTransactionPublished { state3 } => { AliceState::BtcRedeemTransactionPublished { state3 } => {
Alice::BtcRedeemTransactionPublished { Alice::BtcRedeemTransactionPublished {
@ -139,8 +139,8 @@ impl From<&AliceState> for Alice {
transfer_proof, transfer_proof,
state3, state3,
} => Alice::BtcCancelled { } => Alice::BtcCancelled {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
}, },
AliceState::BtcRefunded { AliceState::BtcRefunded {
@ -149,9 +149,9 @@ impl From<&AliceState> for Alice {
spend_key, spend_key,
state3, state3,
} => Alice::BtcRefunded { } => Alice::BtcRefunded {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
spend_key: *spend_key, spend_key,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
}, },
AliceState::BtcPunishable { AliceState::BtcPunishable {
@ -159,8 +159,8 @@ impl From<&AliceState> for Alice {
transfer_proof, transfer_proof,
state3, state3,
} => Alice::BtcPunishable { } => Alice::BtcPunishable {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
}, },
AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded), AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded),
@ -169,8 +169,8 @@ impl From<&AliceState> for Alice {
transfer_proof, transfer_proof,
state3, state3,
} => Alice::CancelTimelockExpired { } => Alice::CancelTimelockExpired {
monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, monero_wallet_restore_blockheight,
transfer_proof: transfer_proof.clone(), transfer_proof,
state3: state3.as_ref().clone(), state3: state3.as_ref().clone(),
}, },
AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished), AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished),

400
swap/src/database/sled.rs Normal file
View 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
View 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)
}
}

View File

@ -1,10 +1,11 @@
use crate::asb; use crate::asb;
use crate::bitcoin::{CancelTimelock, PunishTimelock}; use crate::bitcoin::{CancelTimelock, PunishTimelock};
use serde::Serialize;
use std::cmp::max; use std::cmp::max;
use std::time::Duration; use std::time::Duration;
use time::ext::NumericalStdDuration; use time::ext::NumericalStdDuration;
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Serialize)]
pub struct Config { pub struct Config {
pub bitcoin_lock_mempool_timeout: Duration, pub bitcoin_lock_mempool_timeout: Duration,
pub bitcoin_lock_confirmed_timeout: Duration, pub bitcoin_lock_confirmed_timeout: Duration,
@ -15,6 +16,7 @@ pub struct Config {
pub bitcoin_network: bitcoin::Network, pub bitcoin_network: bitcoin::Network,
pub monero_avg_block_time: Duration, pub monero_avg_block_time: Duration,
pub monero_finality_confirmations: u64, pub monero_finality_confirmations: u64,
#[serde(with = "monero_network")]
pub monero_network: 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -234,6 +234,15 @@ pub mod monero_private_key {
let mut s = s; let mut s = s;
PrivateKey::consensus_decode(&mut s).map_err(|err| E::custom(format!("{:?}", err))) 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> pub fn serialize<S>(x: &PrivateKey, s: S) -> Result<S::Ok, S::Error>
@ -243,8 +252,12 @@ pub mod monero_private_key {
let mut bytes = Cursor::new(vec![]); let mut bytes = Cursor::new(vec![]);
x.consensus_encode(&mut bytes) x.consensus_encode(&mut bytes)
.map_err(|err| S::Error::custom(format!("{:?}", err)))?; .map_err(|err| S::Error::custom(format!("{:?}", err)))?;
if s.is_human_readable() {
s.serialize_str(&hex::encode(bytes.into_inner()))
} else {
s.serialize_bytes(bytes.into_inner().as_ref()) s.serialize_bytes(bytes.into_inner().as_ref())
} }
}
pub fn deserialize<'de, D>( pub fn deserialize<'de, D>(
deserializer: D, deserializer: D,
@ -252,7 +265,13 @@ pub mod monero_private_key {
where where
D: Deserializer<'de>, 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) Ok(key)
} }
} }
@ -351,7 +370,17 @@ mod tests {
pub struct MoneroAmount(#[serde(with = "monero_amount")] crate::monero::Amount); pub struct MoneroAmount(#[serde(with = "monero_amount")] crate::monero::Amount);
#[test] #[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( let key = MoneroPrivateKey(monero::PrivateKey::from_scalar(
crate::monero::Scalar::random(&mut OsRng), crate::monero::Scalar::random(&mut OsRng),
)); ));

View File

@ -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 crate::{bitcoin, monero};
use anyhow::Result;
use async_trait::async_trait;
use conquer_once::Lazy; use conquer_once::Lazy;
use ecdsa_fun::fun::marker::Mark; use ecdsa_fun::fun::marker::Mark;
use libp2p::{Multiaddr, PeerId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::Sha256; use sha2::Sha256;
use sigma_fun::ext::dl_secp256k1_ed25519_eq::{CrossCurveDLEQ, CrossCurveDLEQProof}; use sigma_fun::ext::dl_secp256k1_ed25519_eq::{CrossCurveDLEQ, CrossCurveDLEQProof};
use sigma_fun::HashTranscript; use sigma_fun::HashTranscript;
use std::convert::TryInto;
use uuid::Uuid; use uuid::Uuid;
pub mod alice; pub mod alice;
@ -65,3 +73,74 @@ pub struct Message4 {
tx_punish_sig: bitcoin::Signature, tx_punish_sig: bitcoin::Signature,
tx_cancel_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)>>;
}

View File

@ -1,7 +1,7 @@
//! Run an XMR/BTC swap in the role of Alice. //! Run an XMR/BTC swap in the role of Alice.
//! Alice holds XMR and wishes receive BTC. //! Alice holds XMR and wishes receive BTC.
use crate::database::Database;
use crate::env::Config; use crate::env::Config;
use crate::protocol::Database;
use crate::{asb, bitcoin, monero}; use crate::{asb, bitcoin, monero};
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
@ -19,5 +19,5 @@ pub struct Swap {
pub monero_wallet: Arc<monero::Wallet>, pub monero_wallet: Arc<monero::Wallet>,
pub env_config: Config, pub env_config: Config,
pub swap_id: Uuid, pub swap_id: Uuid,
pub db: Arc<Database>, pub db: Arc<dyn Database + Send + Sync>,
} }

View File

@ -16,7 +16,7 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof;
use std::fmt; use std::fmt;
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
pub enum AliceState { pub enum AliceState {
Started { Started {
state3: Box<State3>, state3: Box<State3>,

View File

@ -4,7 +4,7 @@ use crate::asb::{EventLoopHandle, LatestRate};
use crate::bitcoin::ExpiredTimelocks; use crate::bitcoin::ExpiredTimelocks;
use crate::env::Config; use crate::env::Config;
use crate::protocol::alice::{AliceState, Swap}; use crate::protocol::alice::{AliceState, Swap};
use crate::{bitcoin, database, monero}; use crate::{bitcoin, monero};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use tokio::select; use tokio::select;
use tokio::time::timeout; use tokio::time::timeout;
@ -40,9 +40,8 @@ where
) )
.await?; .await?;
let db_state = (&current_state).into();
swap.db swap.db
.insert_latest_state(swap.swap_id, database::Swap::Alice(db_state)) .insert_latest_state(swap.swap_id, current_state.clone().into())
.await?; .await?;
} }
@ -398,7 +397,7 @@ where
}) })
} }
fn is_complete(state: &AliceState) -> bool { pub(crate) fn is_complete(state: &AliceState) -> bool {
matches!( matches!(
state, state,
AliceState::XmrRefunded AliceState::XmrRefunded

View File

@ -3,11 +3,12 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use uuid::Uuid; use uuid::Uuid;
use crate::database::Database; use crate::protocol::Database;
use crate::{bitcoin, cli, env, monero}; use crate::{bitcoin, cli, env, monero};
pub use self::state::*; pub use self::state::*;
pub use self::swap::{run, run_until}; pub use self::swap::{run, run_until};
use std::convert::TryInto;
pub mod state; pub mod state;
pub mod swap; pub mod swap;
@ -15,7 +16,7 @@ pub mod swap;
pub struct Swap { pub struct Swap {
pub state: BobState, pub state: BobState,
pub event_loop_handle: cli::EventLoopHandle, pub event_loop_handle: cli::EventLoopHandle,
pub db: Database, pub db: Arc<dyn Database + Send + Sync>,
pub bitcoin_wallet: Arc<bitcoin::Wallet>, pub bitcoin_wallet: Arc<bitcoin::Wallet>,
pub monero_wallet: Arc<monero::Wallet>, pub monero_wallet: Arc<monero::Wallet>,
pub env_config: env::Config, pub env_config: env::Config,
@ -26,7 +27,7 @@ pub struct Swap {
impl Swap { impl Swap {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
db: Database, db: Arc<dyn Database + Send + Sync>,
id: Uuid, id: Uuid,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
@ -52,8 +53,8 @@ impl Swap {
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn from_db( pub async fn from_db(
db: Database, db: Arc<dyn Database + Send + Sync>,
id: Uuid, id: Uuid,
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
@ -61,7 +62,7 @@ impl Swap {
event_loop_handle: cli::EventLoopHandle, event_loop_handle: cli::EventLoopHandle,
monero_receive_address: monero::Address, monero_receive_address: monero::Address,
) -> Result<Self> { ) -> Result<Self> {
let state = db.get_state(id)?.try_into_bob()?.into(); let state = db.get_state(id).await?.try_into()?;
Ok(Self { Ok(Self {
state, state,

View File

@ -21,7 +21,7 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof;
use std::fmt; use std::fmt;
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum BobState { pub enum BobState {
Started { Started {
btc_amount: bitcoin::Amount, btc_amount: bitcoin::Amount,

View File

@ -1,6 +1,5 @@
use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund}; use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund};
use crate::cli::EventLoopHandle; use crate::cli::EventLoopHandle;
use crate::database::Swap;
use crate::network::swap_setup::bob::NewSwap; use crate::network::swap_setup::bob::NewSwap;
use crate::protocol::bob; use crate::protocol::bob;
use crate::protocol::bob::state::*; use crate::protocol::bob::state::*;
@ -33,7 +32,7 @@ pub async fn run_until(
while !is_target_state(&current_state) { while !is_target_state(&current_state) {
current_state = next_state( current_state = next_state(
swap.id, swap.id,
current_state, current_state.clone(),
&mut swap.event_loop_handle, &mut swap.event_loop_handle,
swap.bitcoin_wallet.as_ref(), swap.bitcoin_wallet.as_ref(),
swap.monero_wallet.as_ref(), swap.monero_wallet.as_ref(),
@ -41,9 +40,8 @@ pub async fn run_until(
) )
.await?; .await?;
let db_state = current_state.clone().into();
swap.db swap.db
.insert_latest_state(swap.id, Swap::Bob(db_state)) .insert_latest_state(swap.id, current_state.clone().into())
.await?; .await?;
} }

View File

@ -16,15 +16,16 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use swap::asb::FixedRate; use swap::asb::FixedRate;
use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund}; use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund};
use swap::database::Database; use swap::database::SqliteDatabase;
use swap::env::{Config, GetConfig}; use swap::env::{Config, GetConfig};
use swap::fs::ensure_directory_exists;
use swap::network::swarm; use swap::network::swarm;
use swap::protocol::alice::{AliceState, Swap}; use swap::protocol::alice::{AliceState, Swap};
use swap::protocol::bob::BobState; use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob}; use swap::protocol::{alice, bob};
use swap::seed::Seed; use swap::seed::Seed;
use swap::{asb, bitcoin, cli, env, monero}; use swap::{asb, bitcoin, cli, env, monero};
use tempfile::tempdir; use tempfile::{tempdir, NamedTempFile};
use testcontainers::clients::Cli; use testcontainers::clients::Cli;
use testcontainers::{Container, Docker, RunArgs}; use testcontainers::{Container, Docker, RunArgs};
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -82,7 +83,7 @@ where
.parse() .parse()
.expect("failed to parse Alice's address"); .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( let (alice_handle, alice_swap_handle) = start_alice(
&alice_seed, &alice_seed,
alice_db_path.clone(), alice_db_path.clone(),
@ -110,7 +111,7 @@ where
let bob_params = BobParams { let bob_params = BobParams {
seed: Seed::random().unwrap(), 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(), bitcoin_wallet: bob_bitcoin_wallet.clone(),
monero_wallet: bob_monero_wallet.clone(), monero_wallet: bob_monero_wallet.clone(),
alice_address: alice_listen_address.clone(), alice_address: alice_listen_address.clone(),
@ -222,7 +223,13 @@ async fn start_alice(
bitcoin_wallet: Arc<bitcoin::Wallet>, bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>, monero_wallet: Arc<monero::Wallet>,
) -> (AliceApplicationHandle, Receiver<alice::Swap>) { ) -> (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 min_buy = bitcoin::Amount::from_sat(u64::MIN);
let max_buy = bitcoin::Amount::from_sat(u64::MAX); let max_buy = bitcoin::Amount::from_sat(u64::MAX);
@ -402,7 +409,14 @@ struct BobParams {
impl BobParams { impl BobParams {
pub async fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, cli::EventLoop)> { 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 (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( let swap = bob::Swap::from_db(
db, db,
@ -412,7 +426,8 @@ impl BobParams {
self.env_config, self.env_config,
handle, handle,
self.monero_wallet.get_main_address(), self.monero_wallet.get_main_address(),
)?; )
.await?;
Ok((swap, event_loop)) Ok((swap, event_loop))
} }
@ -424,7 +439,14 @@ impl BobParams {
let swap_id = Uuid::new_v4(); let swap_id = Uuid::new_v4();
let (event_loop, handle) = self.new_eventloop(swap_id).await?; 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( let swap = bob::Swap::new(
db, db,