From 123e0cc69bbda2f2f54fcfb47375f079d2f3492a Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 8 Feb 2021 15:53:05 +1100 Subject: [PATCH] WIP: Replace bitcoind wallet with bdk wallet Remove bitcoin-harness. Create and pair bitcoind and electrs testcontainers using the same docker network and volume. Happy path passing. --- Cargo.lock | 294 ++++++++++++++++++++++++------- rust-toolchain | 2 +- swap/Cargo.toml | 1 + swap/src/bitcoin.rs | 1 - swap/src/bitcoin/timelocks.rs | 10 +- swap/src/bitcoin/transactions.rs | 20 +-- swap/src/bitcoin/wallet.rs | 198 ++++++++++++++------- swap/src/main.rs | 12 +- swap/src/protocol/alice.rs | 1 + swap/src/protocol/alice/state.rs | 66 ------- swap/tests/happy_path.rs | 57 +++++- swap/tests/testutils/bitcoind.rs | 139 +++++++++++++++ swap/tests/testutils/electrs.rs | 164 +++++++++++++++++ swap/tests/testutils/mod.rs | 130 ++++++++++++-- 14 files changed, 864 insertions(+), 231 deletions(-) create mode 100644 swap/tests/testutils/bitcoind.rs create mode 100644 swap/tests/testutils/electrs.rs diff --git a/Cargo.lock b/Cargo.lock index 8e9949b8..2da4ad4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,7 +66,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -75,7 +75,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -132,7 +132,7 @@ dependencies = [ "polling", "vec-arena", "waker-fn", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -163,11 +163,11 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4401f0a3622dad2e0763fa79e0eb328bc70fb7dccfdd645341f00d671247d6" dependencies = [ - "bytes", + "bytes 1.0.1", "futures-sink", "futures-util", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.4", ] [[package]] @@ -187,7 +187,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -212,7 +212,7 @@ dependencies = [ "instant", "pin-project 1.0.4", "rand 0.8.2", - "tokio", + "tokio 1.0.2", ] [[package]] @@ -230,6 +230,15 @@ dependencies = [ "keccak-hash 0.1.2", ] +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + [[package]] name = "base64" version = "0.12.3" @@ -242,6 +251,37 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bdk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2fd4c84e2baef750794e7c3f317e37c0c611ef7b29c9a9f18c7e51940dbfdb5" +dependencies = [ + "async-trait", + "bdk-macros", + "bitcoin", + "electrum-client", + "js-sys", + "log", + "miniscript", + "rand 0.7.3", + "serde", + "serde_json", + "sled", + "tokio 0.2.25", +] + +[[package]] +name = "bdk-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f62874901df222eb0fc3bad6e425bc2a935287b8110be0d1ad6d729af86cf6e1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bech32" version = "0.7.2" @@ -283,7 +323,7 @@ dependencies = [ "serde_json", "testcontainers", "thiserror", - "tokio", + "tokio 1.0.2", "tracing", "url", ] @@ -428,6 +468,12 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + [[package]] name = "bytes" version = "1.0.1" @@ -442,9 +488,9 @@ checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "cc" -version = "1.0.66" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" [[package]] name = "cfg-if" @@ -757,7 +803,7 @@ checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", "redox_users", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -806,6 +852,22 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "electrum-client" +version = "0.5.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedfb48f66ab17ba3b2c69f8ff32f68d8b5dbc7839c0ca4e94237b835ca608dd" +dependencies = [ + "bitcoin", + "log", + "rustls", + "serde", + "serde_json", + "socks", + "webpki", + "webpki-roots", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -952,7 +1014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1021,7 +1083,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite", + "pin-project-lite 0.2.4", "waker-fn", ] @@ -1071,7 +1133,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.4", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -1119,8 +1181,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1150,7 +1214,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b67e66362108efccd8ac053abafc8b7a8d86a37e6e48fc4f6f7485eb5e9e6a5" dependencies = [ - "bytes", + "bytes 1.0.1", "fnv", "futures-core", "futures-sink", @@ -1158,7 +1222,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio", + "tokio 1.0.2", "tokio-util", "tracing", "tracing-futures", @@ -1185,7 +1249,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1274,7 +1338,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" dependencies = [ - "bytes", + "bytes 1.0.1", "fnv", "itoa", ] @@ -1285,7 +1349,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" dependencies = [ - "bytes", + "bytes 1.0.1", "http", ] @@ -1307,7 +1371,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" dependencies = [ - "bytes", + "bytes 1.0.1", "futures-channel", "futures-core", "futures-util", @@ -1319,7 +1383,7 @@ dependencies = [ "itoa", "pin-project 1.0.4", "socket2", - "tokio", + "tokio 1.0.2", "tower-service", "tracing", "want", @@ -1331,10 +1395,10 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes", + "bytes 1.0.1", "hyper", "native-tls", - "tokio", + "tokio 1.0.2", "tokio-native-tls", ] @@ -1357,7 +1421,7 @@ checksum = "28538916eb3f3976311f5dfbe67b5362d0add1293d0a9cad17debf86f8e3aa48" dependencies = [ "if-addrs-sys", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1383,7 +1447,7 @@ dependencies = [ "ipnet", "libc", "log", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1505,7 +1569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5133112ce42be9482f6a87be92a605dd6bbc9e93c297aee77d172ff06908f3a" dependencies = [ "atomic", - "bytes", + "bytes 1.0.1", "futures", "lazy_static", "libp2p-core", @@ -1586,7 +1650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2705dc94b01ab9e3779b42a09bbf3712e637ed213e875c30face247291a85af0" dependencies = [ "asynchronous-codec", - "bytes", + "bytes 1.0.1", "futures", "libp2p-core", "log", @@ -1603,7 +1667,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aca322b52a0c5136142a7c3971446fb1e9964923a526c9cc6ef3b7c94e57778" dependencies = [ - "bytes", + "bytes 1.0.1", "curve25519-dalek 3.0.2", "futures", "lazy_static", @@ -1626,7 +1690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37637a4b33b5390322ccc068a33897d0aa541daf4fec99f6a7efbf37295346e" dependencies = [ "async-trait", - "bytes", + "bytes 1.0.1", "futures", "libp2p-core", "libp2p-swarm", @@ -1671,7 +1735,7 @@ dependencies = [ "libp2p-core", "log", "socket2", - "tokio", + "tokio 1.0.2", ] [[package]] @@ -1818,7 +1882,7 @@ dependencies = [ "log", "miow", "ntapi", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1828,7 +1892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ "socket2", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1862,7 +1926,7 @@ dependencies = [ "serde_json", "spectral", "testcontainers", - "tokio", + "tokio 1.0.2", "tracing", "tracing-log", "tracing-subscriber", @@ -1908,7 +1972,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10ddc0eb0117736f19d556355464fc87efc8ad98b29e3fd84f02531eb6e90840" dependencies = [ - "bytes", + "bytes 1.0.1", "futures", "log", "pin-project 1.0.4", @@ -1941,7 +2005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1956,7 +2020,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2156,7 +2220,7 @@ dependencies = [ "libc", "redox_syscall 0.1.57", "smallvec", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2226,6 +2290,12 @@ dependencies = [ "syn", ] +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + [[package]] name = "pin-project-lite" version = "0.2.4" @@ -2254,7 +2324,7 @@ dependencies = [ "libc", "log", "wepoll-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2375,7 +2445,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" dependencies = [ - "bytes", + "bytes 1.0.1", "prost-derive", ] @@ -2385,7 +2455,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" dependencies = [ - "bytes", + "bytes 1.0.1", "heck", "itertools", "log", @@ -2416,7 +2486,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" dependencies = [ - "bytes", + "bytes 1.0.1", "prost", ] @@ -2445,7 +2515,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2458,7 +2528,7 @@ dependencies = [ "fuchsia-cprng", "libc", "rand_core 0.3.1", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2477,7 +2547,7 @@ dependencies = [ "rand_os", "rand_pcg", "rand_xorshift", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2612,7 +2682,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2626,7 +2696,7 @@ dependencies = [ "libc", "rand_core 0.4.2", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2714,7 +2784,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2724,7 +2794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd281b1030aa675fb90aa994d07187645bb3c8fc756ca766e7c3070b439de9de" dependencies = [ "base64 0.13.0", - "bytes", + "bytes 1.0.1", "encoding_rs", "futures-core", "futures-util", @@ -2739,11 +2809,11 @@ dependencies = [ "mime", "native-tls", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.4", "serde", "serde_json", "serde_urlencoded", - "tokio", + "tokio 1.0.2", "tokio-native-tls", "url", "wasm-bindgen", @@ -2764,7 +2834,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2810,6 +2880,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" +dependencies = [ + "base64 0.10.1", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "rw-stream-sink" version = "0.2.1" @@ -2834,7 +2917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2843,6 +2926,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secp256k1" version = "0.19.0" @@ -3095,7 +3188,19 @@ checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "socks" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f86c7635fadf2814201a4f67efefb0007588ae7422ce299f354ab5c97f61ae" +dependencies = [ + "byteorder", + "libc", + "winapi 0.2.8", + "ws2_32-sys", ] [[package]] @@ -3266,6 +3371,7 @@ dependencies = [ "atty", "backoff", "base64 0.12.3", + "bdk", "bitcoin", "bitcoin-harness", "conquer-once", @@ -3301,7 +3407,7 @@ dependencies = [ "testcontainers", "thiserror", "time", - "tokio", + "tokio 1.0.2", "tracing", "tracing-core", "tracing-futures", @@ -3346,7 +3452,7 @@ dependencies = [ "rand 0.8.2", "redox_syscall 0.2.4", "remove_dir_all", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -3357,7 +3463,7 @@ checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", "dirs", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -3426,7 +3532,7 @@ dependencies = [ "stdweb", "time-macros", "version_check", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -3476,6 +3582,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "pin-project-lite 0.1.11", + "slab", +] + [[package]] name = "tokio" version = "1.0.2" @@ -3483,12 +3600,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca04cec6ff2474c638057b65798f60ac183e5e79d3448bb7163d36a39cff6ec" dependencies = [ "autocfg 1.0.1", - "bytes", + "bytes 1.0.1", "libc", "memchr", "mio", "num_cpus", - "pin-project-lite", + "pin-project-lite 0.2.4", "tokio-macros", ] @@ -3510,7 +3627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio", + "tokio 1.0.2", ] [[package]] @@ -3520,8 +3637,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd" dependencies = [ "futures-core", - "pin-project-lite", - "tokio", + "pin-project-lite 0.2.4", + "tokio 1.0.2", ] [[package]] @@ -3530,12 +3647,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ae4751faa60b9f96dd8344d74592e5a17c0c9a220413dbc6942d14139bbfcc" dependencies = [ - "bytes", + "bytes 1.0.1", "futures-core", "futures-sink", "log", - "pin-project-lite", - "tokio", + "pin-project-lite 0.2.4", + "tokio 1.0.2", "tokio-stream", ] @@ -3561,7 +3678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" dependencies = [ "cfg-if 1.0.0", - "pin-project-lite", + "pin-project-lite 0.2.4", "tracing-attributes", "tracing-core", ] @@ -3720,7 +3837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35581ff83d4101e58b582e607120c7f5ffb17e632a980b1f38334d76b36908b2" dependencies = [ "asynchronous-codec", - "bytes", + "bytes 1.0.1", "futures-io", "futures-util", ] @@ -3904,6 +4021,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739" +dependencies = [ + "webpki", +] + [[package]] name = "wepoll-sys" version = "3.0.1" @@ -3923,6 +4059,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -3933,6 +4075,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -3951,7 +4099,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", ] [[package]] diff --git a/rust-toolchain b/rust-toolchain index a66b2431..73c05f9b 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-08-13 +nightly-2021-02-01 diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 2d7b7bff..b36d6710 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -14,6 +14,7 @@ backoff = { git = "https://github.com/ihrwein/backoff", rev = "9d03992a83dfdc596 base64 = "0.12" bitcoin = { version = "0.25", features = ["rand", "use-serde"] } bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "ae2f6cd547496e680941c0910018bbe884128799" } +bdk = { version = "0.3"} conquer-once = "0.3" cross-curve-dleq = { git = "https://github.com/comit-network/cross-curve-dleq", rev = "eddcdea1d1f16fa33ef581d1744014ece535c920", features = ["serde"] } curve25519-dalek = "2" diff --git a/swap/src/bitcoin.rs b/swap/src/bitcoin.rs index c4efba5b..9e0bdc48 100644 --- a/swap/src/bitcoin.rs +++ b/swap/src/bitcoin.rs @@ -231,7 +231,6 @@ pub trait GetRawTransaction { async fn get_raw_transaction(&self, txid: Txid) -> Result; } -#[async_trait] pub trait GetNetwork { fn get_network(&self) -> Network; } diff --git a/swap/src/bitcoin/timelocks.rs b/swap/src/bitcoin/timelocks.rs index a4dc332c..c850906a 100644 --- a/swap/src/bitcoin/timelocks.rs +++ b/swap/src/bitcoin/timelocks.rs @@ -1,4 +1,4 @@ -use std::ops::Add; +use std::ops::{Add, Sub}; use serde::{Deserialize, Serialize}; @@ -49,6 +49,14 @@ impl Add for BlockHeight { } } +impl Sub for BlockHeight { + type Output = BlockHeight; + + fn sub(self, rhs: BlockHeight) -> Self::Output { + BlockHeight(self.0 - rhs.0) + } +} + #[derive(Debug, Clone, Copy)] pub enum ExpiredTimelocks { None, diff --git a/swap/src/bitcoin/transactions.rs b/swap/src/bitcoin/transactions.rs index 6ac7c9de..371adf07 100644 --- a/swap/src/bitcoin/transactions.rs +++ b/swap/src/bitcoin/transactions.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TxLock { - inner: Transaction, + inner: PartiallySignedTransaction, output_descriptor: Descriptor<::bitcoin::PublicKey>, } @@ -31,35 +31,33 @@ impl TxLock { // We construct a psbt for convenience let psbt = wallet.build_tx_lock_psbt(address, amount).await?; - // We don't take advantage of psbt functionality yet, instead we convert to a - // raw transaction - let inner = psbt.extract_tx(); - Ok(Self { - inner, + inner: psbt, output_descriptor: lock_output_descriptor, }) } pub fn lock_amount(&self) -> Amount { - Amount::from_sat(self.inner.output[self.lock_output_vout()].value) + Amount::from_sat(self.inner.clone().extract_tx().output[self.lock_output_vout()].value) } pub fn txid(&self) -> Txid { - self.inner.txid() + self.inner.clone().extract_tx().txid() } pub fn as_outpoint(&self) -> OutPoint { // This is fine because a transaction that has that many outputs is not // realistic #[allow(clippy::cast_possible_truncation)] - OutPoint::new(self.inner.txid(), self.lock_output_vout() as u32) + OutPoint::new(self.txid(), self.lock_output_vout() as u32) } /// Retreive the index of the locked output in the transaction outputs /// vector fn lock_output_vout(&self) -> usize { self.inner + .clone() + .extract_tx() .output .iter() .position(|output| { @@ -83,7 +81,7 @@ impl TxLock { }; let tx_out = TxOut { - value: self.inner.output[self.lock_output_vout()].value - TX_FEE, + value: self.inner.clone().extract_tx().output[self.lock_output_vout()].value - TX_FEE, script_pubkey: spend_address.script_pubkey(), }; @@ -98,7 +96,7 @@ impl TxLock { impl From for PartiallySignedTransaction { fn from(from: TxLock) -> Self { - PartiallySignedTransaction::from_unsigned_tx(from.inner).expect("to be unsigned") + from.inner } } diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 44a1e24e..9a596b6a 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -7,55 +7,93 @@ use crate::{ config::Config, }; use ::bitcoin::{util::psbt::PartiallySignedTransaction, Txid}; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Result}; use async_trait::async_trait; use backoff::{backoff::Constant as ConstantBackoff, tokio::retry}; -use bitcoin_harness::{bitcoind_rpc::PsbtBase64, BitcoindRpcApi}; -use reqwest::Url; -use std::time::Duration; -use tokio::time::interval; +use bdk::{ + blockchain::{noop_progress, Blockchain, ElectrumBlockchain}, + electrum_client::{Client, ElectrumApi}, + keys::GeneratableDefaultOptions, + FeeRate, +}; +use reqwest::{Method, Url}; +use serde::{Deserialize, Serialize}; +use std::{path::Path, sync::Arc, time::Duration}; +use tokio::{sync::Mutex, time::interval}; +use tracing::debug; -#[derive(Debug)] pub struct Wallet { - pub inner: bitcoin_harness::Wallet, + pub inner: Arc>>, pub network: bitcoin::Network, + pub http_url: Url, + pub rpc_url: Url, } impl Wallet { - pub async fn new(name: &str, url: Url, network: bitcoin::Network) -> Result { - let wallet = bitcoin_harness::Wallet::new(name, url).await?; + pub async fn new( + _name: &str, + rpc_url: Url, + http_url: Url, + network: bitcoin::Network, + datadir: &Path, + ) -> Result { + let client = Client::new(rpc_url.as_str()).expect("Failed to init electrum rpc client"); + + let db = bdk::sled::open(datadir).expect("could not open sled db"); + let db = db.open_tree("default_tree").expect("could not open tree"); + + // todo: make key generation configurable using a descriptor + let p_key = ::bitcoin::PrivateKey::generate_default()?; + let bdk_wallet = bdk::Wallet::new( + bdk::template::P2WPKH(p_key), + None, + network, + db, + ElectrumBlockchain::from(client), + )?; Ok(Self { - inner: wallet, + inner: Arc::new(Mutex::new(bdk_wallet)), network, + http_url, + rpc_url, }) } pub async fn balance(&self) -> Result { - let balance = self.inner.balance().await?; - Ok(balance) + self.inner.lock().await.sync(noop_progress(), None)?; + let balance = self.inner.lock().await.get_balance()?; + Ok(Amount::from_sat(balance)) } pub async fn new_address(&self) -> Result
{ - self.inner.new_address().await.map_err(Into::into) + self.inner + .lock() + .await + .get_new_address() + .map_err(Into::into) + } + + pub async fn get_tx(&self, txid: Txid) -> Result> { + let tx = self.inner.lock().await.client().get_tx(&txid)?; + Ok(tx) } pub async fn transaction_fee(&self, txid: Txid) -> Result { - let fee = self + self.inner.lock().await.sync(noop_progress(), None)?; + let fees = self .inner - .get_wallet_transaction(txid) + .lock() .await - .map(|res| { - res.fee.map(|signed_amount| { - signed_amount - .abs() - .to_unsigned() - .expect("Absolute value is always positive") - }) + .list_transactions(true)? + .iter() + .find(|tx| tx.txid == txid) + .ok_or_else(|| { + anyhow!("Could not find tx in bdk wallet when trying to determine fees") })? - .context("Rpc response did not contain a fee")?; + .fees; - Ok(fee) + Ok(Amount::from_sat(fees)) } } @@ -66,11 +104,19 @@ impl BuildTxLockPsbt for Wallet { output_address: Address, output_amount: Amount, ) -> Result { - let psbt = self.inner.fund_psbt(output_address, output_amount).await?; - let as_hex = base64::decode(psbt)?; - - let psbt = bitcoin::consensus::deserialize(&as_hex)?; + debug!("syncing wallet"); + self.inner.lock().await.sync(noop_progress(), None)?; + debug!("building tx lock"); + let (psbt, _details) = self.inner.lock().await.create_tx( + bdk::TxBuilder::with_recipients(vec![( + output_address.script_pubkey(), + output_amount.as_sat(), + )]) + // todo: get actual fee + .fee_rate(FeeRate::from_sat_per_vb(5.0)), + )?; + debug!("tx lock built"); Ok(psbt) } } @@ -78,22 +124,15 @@ impl BuildTxLockPsbt for Wallet { #[async_trait] impl SignTxLock for Wallet { async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result { + debug!("syncing wallet"); + self.inner.lock().await.sync(noop_progress(), None)?; + + debug!("signing tx lock"); let psbt = PartiallySignedTransaction::from(tx_lock); - - let psbt = bitcoin::consensus::serialize(&psbt); - let as_base64 = base64::encode(psbt); - - let psbt = self - .inner - .wallet_process_psbt(PsbtBase64(as_base64)) - .await?; - let PsbtBase64(signed_psbt) = PsbtBase64::from(psbt); - - let as_hex = base64::decode(signed_psbt)?; - let psbt: PartiallySignedTransaction = bitcoin::consensus::deserialize(&as_hex)?; - - let tx = psbt.extract_tx(); - + let (signed_psbt, finalized) = self.inner.lock().await.sign(psbt, None)?; + assert!(finalized); + let tx = signed_psbt.extract_tx(); + debug!("signed tx lock"); Ok(tx) } } @@ -101,19 +140,24 @@ impl SignTxLock for Wallet { #[async_trait] impl BroadcastSignedTransaction for Wallet { async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result { - let txid = self.inner.send_raw_transaction(transaction).await?; - tracing::info!("Bitcoin tx broadcasted! TXID = {}", txid); - Ok(txid) + debug!("attempting to broadcast tx"); + self.inner.lock().await.broadcast(transaction.clone())?; + tracing::info!("Bitcoin tx broadcasted! TXID = {}", transaction.txid()); + Ok(transaction.txid()) } } -// TODO: For retry, use `backoff::ExponentialBackoff` in production as opposed -// to `ConstantBackoff`. #[async_trait] impl WatchForRawTransaction for Wallet { async fn watch_for_raw_transaction(&self, txid: Txid) -> Transaction { + // debug!("syncing wallet"); + // self.inner.lock().await.sync(noop_progress(), None).unwrap(); + debug!("watching for tx: {}", txid); retry(ConstantBackoff::new(Duration::from_secs(1)), || async { - Ok(self.inner.get_raw_transaction(txid).await?) + let client = Client::new(self.rpc_url.as_ref())?; + let tx = client.transaction_get(&txid)?; + debug!("found tx: {}", txid); + Ok(tx) }) .await .expect("transient errors to be retried") @@ -122,9 +166,10 @@ impl WatchForRawTransaction for Wallet { #[async_trait] impl GetRawTransaction for Wallet { - // todo: potentially replace with option async fn get_raw_transaction(&self, txid: Txid) -> Result { - Ok(self.inner.get_raw_transaction(txid).await?) + self.get_tx(txid) + .await? + .ok_or_else(|| anyhow!("Could not get raw tx with id: {}", txid)) } } @@ -132,7 +177,7 @@ impl GetRawTransaction for Wallet { impl GetBlockHeight for Wallet { async fn get_block_height(&self) -> BlockHeight { let height = retry(ConstantBackoff::new(Duration::from_secs(1)), || async { - Ok(self.inner.client.getblockcount().await?) + Ok(self.inner.lock().await.client().get_height()?) }) .await .expect("transient errors to be retried"); @@ -144,21 +189,39 @@ impl GetBlockHeight for Wallet { #[async_trait] impl TransactionBlockHeight for Wallet { async fn transaction_block_height(&self, txid: Txid) -> BlockHeight { + #[derive(Serialize, Deserialize, Debug, Copy, Clone)] + struct TransactionStatus { + block_height: Option, + } #[derive(Debug)] enum Error { Io, NotYetMined, + JsonDeserialisation, + UrlDeserialisation, } - let height = retry(ConstantBackoff::new(Duration::from_secs(1)), || async { - let block_height = self - .inner - .transaction_block_height(txid) + let path = &format!("/tx/{}/status", txid); + let url = self + .http_url + .clone() + .join(path) + .map_err(|_| backoff::Error::Transient(Error::UrlDeserialisation))?; + + let resp = reqwest::Client::new() + .request(Method::GET, url) + .send() .await .map_err(|_| backoff::Error::Transient(Error::Io))?; - let block_height = - block_height.ok_or_else(|| backoff::Error::Transient(Error::NotYetMined))?; + let tx_status: TransactionStatus = resp + .json() + .await + .map_err(|_| backoff::Error::Permanent(Error::JsonDeserialisation))?; + + let block_height = tx_status + .block_height + .ok_or_else(|| backoff::Error::Transient(Error::NotYetMined))?; Result::<_, backoff::Error>::Ok(block_height) }) @@ -172,18 +235,22 @@ impl TransactionBlockHeight for Wallet { #[async_trait] impl WaitForTransactionFinality for Wallet { async fn wait_for_transaction_finality(&self, txid: Txid, config: Config) -> Result<()> { - // TODO(Franck): This assumes that bitcoind runs with txindex=1 + debug!("waiting for tx finality: {}", txid); + // TODO(Franck): This assumes that bitcoind runs with txindex=1 // Divide by 4 to not check too often yet still be aware of the new block early // on. let mut interval = interval(config.bitcoin_avg_block_time / 4); loop { - let tx = self.inner.client.get_raw_transaction_verbose(txid).await?; - if let Some(confirmations) = tx.confirmations { - if confirmations >= config.bitcoin_finality_confirmations { - break; - } + debug!("syncing wallet"); + self.inner.lock().await.sync(noop_progress(), None)?; + let tx_block_height = self.transaction_block_height(txid).await; + let block_height = self.get_block_height().await; + let confirmations = block_height - tx_block_height; + debug!("confirmations: {:?}", confirmations); + if confirmations >= BlockHeight::new(config.bitcoin_finality_confirmations) { + break; } interval.tick().await; } @@ -192,7 +259,10 @@ impl WaitForTransactionFinality for Wallet { } } +#[async_trait] impl GetNetwork for Wallet { + // todo: Get this from the electrum and instead of storing it in the Wallet + // struct fn get_network(&self) -> bitcoin::Network { self.network } diff --git a/swap/src/main.rs b/swap/src/main.rs index 1e22d901..3d0d1f50 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -232,8 +232,16 @@ async fn setup_wallets( monero_wallet_rpc_url: url::Url, config: Config, ) -> Result<(bitcoin::Wallet, monero::Wallet)> { - let bitcoin_wallet = - bitcoin::Wallet::new(bitcoin_wallet_name, bitcoind_url, config.bitcoin_network).await?; + // todo: use electrum rpc url for the rpc url + // todo: pass datadir path as arg + let bitcoin_wallet = bitcoin::Wallet::new( + bitcoin_wallet_name, + bitcoind_url.clone(), + bitcoind_url, + config.bitcoin_network, + unimplemented!(), + ) + .await?; let bitcoin_balance = bitcoin_wallet.balance().await?; info!( "Connection to Bitcoin wallet succeeded, balance: {}", diff --git a/swap/src/protocol/alice.rs b/swap/src/protocol/alice.rs index b2ea0a16..8b69d56a 100644 --- a/swap/src/protocol/alice.rs +++ b/swap/src/protocol/alice.rs @@ -216,6 +216,7 @@ impl Builder { } } +#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum OutEvent { ConnectionEstablished(PeerId), diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index c88e23c0..6fcee3b6 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -7,7 +7,6 @@ use crate::{ TxRefund, WatchForRawTransaction, }, monero, - monero::CreateWalletForOutput, protocol::{alice, alice::TransferProof, bob, bob::EncryptedSignature, SwapAmounts}, }; use anyhow::{anyhow, Context, Result}; @@ -300,36 +299,6 @@ pub struct State3 { } impl State3 { - pub async fn watch_for_lock_btc(self, bitcoin_wallet: &W) -> Result - where - W: bitcoin::WatchForRawTransaction, - { - tracing::info!("watching for lock btc with txid: {}", self.tx_lock.txid()); - let tx = bitcoin_wallet - .watch_for_raw_transaction(self.tx_lock.txid()) - .await; - - tracing::info!("tx lock seen with txid: {}", tx.txid()); - - Ok(State4 { - a: self.a, - B: self.B, - s_a: self.s_a, - S_b_monero: self.S_b_monero, - S_b_bitcoin: self.S_b_bitcoin, - v: self.v, - xmr: self.xmr, - cancel_timelock: self.cancel_timelock, - punish_timelock: self.punish_timelock, - refund_address: self.refund_address, - redeem_address: self.redeem_address, - punish_address: self.punish_address, - tx_lock: self.tx_lock, - tx_punish_sig_bob: self.tx_punish_sig_bob, - tx_cancel_sig_bob: self.tx_cancel_sig_bob, - }) - } - pub async fn wait_for_cancel_timelock_to_expire(&self, bitcoin_wallet: &W) -> Result<()> where W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight, @@ -497,41 +466,6 @@ impl State5 { lock_xmr_fee: self.lock_xmr_fee, } } - - // watch for refund on btc, recover s_b and refund xmr - pub async fn refund_xmr(self, bitcoin_wallet: &B, monero_wallet: &M) -> Result<()> - where - B: WatchForRawTransaction, - M: CreateWalletForOutput, - { - let tx_cancel = - bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.a.public(), self.B); - - let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address); - - let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_refund.digest()); - - let tx_refund_candidate = bitcoin_wallet - .watch_for_raw_transaction(tx_refund.txid()) - .await; - - let tx_refund_sig = - tx_refund.extract_signature_by_key(tx_refund_candidate, self.a.public())?; - - let s_b = bitcoin::recover(self.S_b_bitcoin, tx_refund_sig, tx_refund_encsig)?; - let s_b = monero::private_key_from_secp256k1_scalar(s_b.into()); - - let s = s_b.scalar + self.s_a.into_ed25519(); - - // TODO: Optimized rescan height should be passed for refund as well. - // NOTE: This actually generates and opens a new wallet, closing the currently - // open one. - monero_wallet - .create_and_load_wallet_for_output(monero::PrivateKey::from_scalar(s), self.v, None) - .await?; - - Ok(()) - } } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 548aee86..3d396c30 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -1,8 +1,14 @@ pub mod testutils; use swap::protocol::{alice, bob}; -use testutils::SlowCancelConfig; +use testcontainers::clients::Cli; +use testutils::{init_tracing, SlowCancelConfig}; use tokio::join; +use futures::future; +use bdk::keys::GeneratableDefaultOptions; +use bdk::database::MemoryDatabase; +use bdk::blockchain::noop_progress; +use url::Url; /// Run the following tests with RUST_MIN_STACK=10000000 @@ -22,3 +28,52 @@ async fn happy_path() { }) .await; } +// +// const ELECTRUM_RPC_PORT: u16 = 60401; +// +// #[tokio::test] +// async fn happy_path() { +// let cli = Cli::default(); +// +// let _guard = init_tracing(); +// +// // let config = C::get_config(); +// +// let _c = testutils::init_electrs_container(&cli).await; +// +// +// let bdk_url = { +// let input = format!("tcp://@localhost:{}", ELECTRUM_RPC_PORT); +// Url::parse(&input).unwrap() +// }; +// +// let client = bdk::electrum_client::Client::new(bdk_url.as_str()).unwrap(); +// +// let blockchain = bdk::blockchain::ElectrumBlockchain::from(client); +// +// // let bdk_wallet = bdk::Wallet::new( +// // "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/0/0/*)", +// // None, +// // Network::Regtest, +// // sled::open("/tmp/bdk").expect("could not create sled db").open_tree("default_tree").expect("could not open tree"), +// // blockchain).unwrap(); +// +// let p_key = ::bitcoin::PrivateKey::generate_default().expect("could not generate priv key"); +// let bdk_wallet = bdk::Wallet::new( +// bdk::template::P2WPKH(p_key), +// None, +// ::bitcoin::Network::Regtest, +// MemoryDatabase::default(), +// blockchain, +// ).unwrap(); +// +// +// let address = bdk_wallet.get_new_address().unwrap(); +// println!("funded address: {}", address); +// +// bdk_wallet.sync(noop_progress(), None).unwrap(); +// +// let balance = bdk_wallet.get_balance().unwrap(); +// assert_eq!(0, balance); +// //future::pending::<()>().await; +// } diff --git a/swap/tests/testutils/bitcoind.rs b/swap/tests/testutils/bitcoind.rs new file mode 100644 index 00000000..7b846935 --- /dev/null +++ b/swap/tests/testutils/bitcoind.rs @@ -0,0 +1,139 @@ +use bitcoin::Network; +use std::collections::HashMap; +use testcontainers::{ + core::{Container, Docker, Port, WaitForMessage}, + Image, +}; + +#[derive(Debug)] +pub struct Bitcoind { + tag: String, + args: BitcoindArgs, + ports: Option>, + entrypoint: Option, + volume: String, +} + +impl Image for Bitcoind { + type Args = BitcoindArgs; + type EnvVars = HashMap; + type Volumes = HashMap; + type EntryPoint = str; + + fn descriptor(&self) -> String { + format!("coblox/bitcoin-core:{}", self.tag) + } + + fn wait_until_ready(&self, container: &Container<'_, D, Self>) { + container + .logs() + .stdout + .wait_for_message(&"init message: Done loading") + .unwrap(); + } + + fn args(&self) -> ::Args { + self.args.clone() + } + + fn volumes(&self) -> Self::Volumes { + let mut volumes = HashMap::new(); + volumes.insert(self.volume.clone(), "/home/bdk-test".to_owned()); + volumes + } + + fn env_vars(&self) -> Self::EnvVars { + HashMap::new() + } + + fn ports(&self) -> Option> { + self.ports.clone() + } + + fn with_args(self, args: ::Args) -> Self { + Bitcoind { args, ..self } + } + + fn with_entrypoint(self, entrypoint: &Self::EntryPoint) -> Self { + Self { + entrypoint: Some(entrypoint.to_string()), + ..self + } + } + + fn entrypoint(&self) -> Option { + self.entrypoint.to_owned() + } +} + +impl Default for Bitcoind { + fn default() -> Self { + Bitcoind { + tag: "v0.19.1".into(), + args: BitcoindArgs::default(), + ports: None, + entrypoint: Some("/usr/bin/bitcoind".into()), + volume: uuid::Uuid::new_v4().to_string(), + } + } +} + +impl Bitcoind { + pub fn with_tag(self, tag_str: &str) -> Self { + Bitcoind { + tag: tag_str.to_string(), + ..self + } + } + + pub fn with_mapped_port>(mut self, port: P) -> Self { + let mut ports = self.ports.unwrap_or_default(); + ports.push(port.into()); + self.ports = Some(ports); + self + } + + pub fn with_volume(mut self, volume: String) -> Self { + self.volume = volume; + self + } +} + +#[derive(Debug, Clone)] +pub struct BitcoindArgs; + +/// Sane defaults for a mainnet regtest instance. +impl Default for BitcoindArgs { + fn default() -> Self { + BitcoindArgs + } +} + +impl IntoIterator for BitcoindArgs { + type Item = String; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> ::IntoIter { + let mut args = Vec::new(); + + args.push("-server".to_string()); + args.push("-regtest".to_string()); + args.push("-txindex=1".to_string()); + args.push("-listen=1".to_string()); + args.push("-prune=0".to_string()); + args.push("-rpcallowip=0.0.0.0/0".to_string()); + args.push("-rpcbind=0.0.0.0".to_string()); + args.push("-rpcuser=admin".to_string()); + args.push("-rpcpassword=123".to_string()); + args.push("-printtoconsole".to_string()); + args.push("-rest".to_string()); + args.push("-fallbackfee=0.0002".to_string()); + args.push("-datadir=/home/bdk-test".to_string()); + args.push("-rpcport=18443".to_string()); + args.push("-port=18886".to_string()); + args.push("-rest".to_string()); + + + args.into_iter() + } +} diff --git a/swap/tests/testutils/electrs.rs b/swap/tests/testutils/electrs.rs new file mode 100644 index 00000000..010dce0d --- /dev/null +++ b/swap/tests/testutils/electrs.rs @@ -0,0 +1,164 @@ +use bitcoin::Network; +use std::collections::HashMap; +use testcontainers::{ + core::{Container, Docker, Port, WaitForMessage}, + Image, +}; +// pub const ELECTRS_CONTAINER_NAME: &str = "electrs"; +// pub const ELECTRS_DEFAULT_NETWORK: &str = "electrs_network"; +// pub const ELECTRS_RPC_PORT: u16 = 48081; +// pub const ELECTRS_HTTP_PORT: u16 = 48083; + +#[derive(Debug)] +pub struct Electrs { + tag: String, + args: ElectrsArgs, + ports: Option>, + entrypoint: Option, + wait_for_message: String, + volume: String, + bitcoind_container_name: String, +} + +impl Image for Electrs { + type Args = ElectrsArgs; + type EnvVars = HashMap; + type Volumes = HashMap; + type EntryPoint = str; + + fn descriptor(&self) -> String { + format!("vulpemventures/electrs:{}", self.tag) + } + + fn wait_until_ready(&self, container: &Container<'_, D, Self>) { + container + .logs() + .stderr + .wait_for_message(&self.wait_for_message) + .unwrap(); + } + + fn args(&self) -> ::Args { + self.args.clone() + } + + fn volumes(&self) -> Self::Volumes { + let mut volumes = HashMap::new(); + volumes.insert(self.volume.clone(), "/home/bdk-test".to_owned()); + volumes + } + + fn env_vars(&self) -> Self::EnvVars { + HashMap::new() + } + + fn ports(&self) -> Option> { + self.ports.clone() + } + + fn with_args(self, args: ::Args) -> Self { + Electrs { args, ..self } + } + + fn with_entrypoint(self, entrypoint: &Self::EntryPoint) -> Self { + Self { + entrypoint: Some(entrypoint.to_string()), + ..self + } + } + + fn entrypoint(&self) -> Option { + self.entrypoint.to_owned() + } +} + +impl Default for Electrs { + fn default() -> Self { + Electrs { + tag: "v0.16.0.3".into(), + args: ElectrsArgs::default(), + ports: None, + entrypoint: Some("/build/electrs".into()), + wait_for_message: "Running accept thread".to_string(), + volume: uuid::Uuid::new_v4().to_string(), + bitcoind_container_name: uuid::Uuid::new_v4().to_string() + } + } +} + +impl Electrs { + pub fn with_tag(self, tag_str: &str) -> Self { + Electrs { + tag: tag_str.to_string(), + ..self + } + } + + pub fn with_mapped_port>(mut self, port: P) -> Self { + let mut ports = self.ports.unwrap_or_default(); + ports.push(port.into()); + self.ports = Some(ports); + self + } + + pub fn with_volume(mut self, volume: String) -> Self { + self.volume = volume; + self + } + + pub fn with_daemon_rpc_addr(mut self, name: String) -> Self { + self.args.daemon_rpc_addr = name; + self + } +} + +#[derive(Debug, Clone)] +pub struct ElectrsArgs { + pub network: Network, + pub daemon_dir: String, + pub daemon_rpc_addr: String, + pub cookie: String, + pub http_addr: String, + pub electrum_rpc_addr: String, + pub cors: String, +} + +/// Sane defaults for a mainnet regtest instance. +impl Default for ElectrsArgs { + fn default() -> Self { + ElectrsArgs { + network: Network::Regtest, + daemon_dir: "/home/bdk-test/".to_string(), + daemon_rpc_addr: "0.0.0.0:18443".to_string(), + cookie: "admin:123".to_string(), + http_addr: "0.0.0.0:3002".to_string(), + electrum_rpc_addr: "0.0.0.0:60401".to_string(), + cors: "*".to_string(), + } + } +} + +impl IntoIterator for ElectrsArgs { + type Item = String; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> ::IntoIter { + let mut args = Vec::new(); + + match self.network { + Network::Testnet => args.push("--network=testnet".to_string()), + Network::Regtest => args.push("--network=regtest".to_string()), + Network::Bitcoin => {} + } + + args.push(format!("-vvvvv")); + args.push(format!("--daemon-dir=={}", self.daemon_dir.as_str())); + args.push(format!("--daemon-rpc-addr={}", self.daemon_rpc_addr)); + args.push(format!("--cookie={}", self.cookie)); + args.push(format!("--http-addr={}", self.http_addr)); + args.push(format!("--electrum-rpc-addr={}", self.electrum_rpc_addr)); + args.push(format!("--cors={}", self.cors)); + + args.into_iter() + } +} diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index fb2cef81..39b0a859 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -1,3 +1,6 @@ +mod electrs; +mod bitcoind; + use crate::testutils; use anyhow::Result; use bitcoin_harness::{BitcoindRpcApi, Client}; @@ -5,7 +8,11 @@ use futures::Future; use get_port::get_port; use libp2p::{core::Multiaddr, PeerId}; use monero_harness::{image, Monero}; -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; use swap::{ bitcoin, bitcoin::Timelock, @@ -16,12 +23,13 @@ use swap::{ seed::Seed, }; use tempfile::tempdir; -use testcontainers::{clients::Cli, Container}; +use testcontainers::{clients::Cli, core::Port, Container, Docker, RunArgs}; use tokio::task::JoinHandle; use tracing_core::dispatcher::DefaultGuard; use tracing_log::LogTracer; use url::Url; use uuid::Uuid; +use bdk::blockchain::noop_progress; const TEST_WALLET_NAME: &str = "testwallet"; @@ -59,7 +67,7 @@ impl AliceParams { } } -#[derive(Debug, Clone)] +#[derive(Clone)] struct BobParams { seed: Seed, db_path: PathBuf, @@ -344,6 +352,7 @@ where &monero, alice_starting_balances.clone(), config, + tempdir().unwrap().path(), ) .await; @@ -368,6 +377,7 @@ where &monero, bob_starting_balances.clone(), config, + tempdir().unwrap().path(), ) .await; @@ -397,20 +407,87 @@ where testfn(test).await } +fn random_prefix() -> String { + use rand::Rng; + const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; + const LEN: usize = 8; + let mut rng = rand::thread_rng(); + + let prefix: String = (0..LEN) + .map(|_| { + let idx = rng.gen_range(0, CHARSET.len()); + CHARSET[idx] as char + }) + .collect(); + prefix +} + async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) { - let bitcoind_url = init_bitcoind_container().await.unwrap(); + let bitcoind_name = format!("{}{}", random_prefix(), "bitcoind"); + let volume = random_prefix(); + let (bitcoind, bitcoind_url) = init_bitcoind_container(&cli, volume.clone(), bitcoind_name.clone(), volume.clone()) + .await + .expect("could not init bitcoind"); + let electrs = init_electrs_container(&cli, volume.clone(), bitcoind_name, volume).await; let (monero, monerods) = init_monero_container(&cli).await; (monero, Containers { bitcoind_url, + bitcoind, monerods, + electrs, }) } -async fn init_bitcoind_container() -> Result { - // let bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap(); - let node_url: Url = "".parse()?; - init_bitcoind(node_url.clone(), 5).await?; - Ok(node_url) +async fn init_bitcoind_container<'c>(cli: &'c Cli, volume: String, name: String, network: String) -> Result<(Container<'c, Cli, bitcoind::Bitcoind>, Url)> { + let image = bitcoind::Bitcoind::default() + .with_mapped_port(Port { + local: 7041, + internal: 18443, + }) + .with_mapped_port(Port { + local: 7042, + internal: 18886, + }) + .with_volume(volume) + .with_tag("0.19.1"); + + let run_args = RunArgs::default() + .with_name(name) + .with_network(network); + + let docker = cli.run_with_args(image, run_args); + + let bitcoind_url = { + let input = format!("http://{}:{}@localhost:{}", "admin", "123", 7041); + Url::parse(&input).unwrap() + }; + + init_bitcoind(bitcoind_url.clone(), 5).await?; + + Ok((docker, bitcoind_url.clone())) +} + +pub async fn init_electrs_container<'c>(cli: &'c Cli, volume: String, bitcoind_container_name: String, network: String) -> Container<'c, Cli, electrs::Electrs> { + let bitcoind_rpc_addr = format!("{}:18443", bitcoind_container_name); + let image = electrs::Electrs::default() + .with_mapped_port(Port { + local: 3012, + internal: 3002, + }) + .with_mapped_port(Port { + local: 60401, + internal: 60401, + }) + .with_volume(volume) + .with_daemon_rpc_addr(bitcoind_rpc_addr) + .with_tag("latest"); + + let run_args = RunArgs::default() + .with_network(network); + + let docker = cli.run_with_args(image, run_args); + + docker } async fn mine(bitcoind_client: Client, reward_address: bitcoin::Address) -> Result<()> { @@ -481,6 +558,7 @@ async fn init_wallets( monero: &Monero, starting_balances: StartingBalances, config: Config, + datadir: &Path, ) -> (Arc, Arc) { monero .init(vec![(name, starting_balances.xmr.as_piconero())]) @@ -492,20 +570,36 @@ async fn init_wallets( network: config.monero_network, }); + // todo: make these configurable + let electrum_rpc_url = { + let input = format!("tcp://@localhost:{}", 60401); + Url::parse(&input).unwrap() + }; + let electrum_http_url = { + let input = format!("http://@localhost:{}", 3012); + Url::parse(&input).unwrap() + }; + let btc_wallet = Arc::new( - swap::bitcoin::Wallet::new(name, bitcoind_url.clone(), config.bitcoin_network) - .await - .unwrap(), + swap::bitcoin::Wallet::new( + name, + electrum_rpc_url, + electrum_http_url, + config.bitcoin_network, + datadir, + ) + .await + .expect("could not init btc wallet"), ); if starting_balances.btc != bitcoin::Amount::ZERO { mint( bitcoind_url, - btc_wallet.inner.new_address().await.unwrap(), + btc_wallet.new_address().await.unwrap(), starting_balances.btc, ) .await - .unwrap(); + .expect("could not mint btc starting balance"); } (btc_wallet, xmr_wallet) @@ -515,7 +609,9 @@ async fn init_wallets( #[allow(dead_code)] struct Containers<'a> { bitcoind_url: Url, + bitcoind: Container<'a, Cli, bitcoind::Bitcoind>, monerods: Vec>, + electrs: Container<'a, Cli, electrs::Electrs> } /// Utility function to initialize logging in the test environment. @@ -524,7 +620,7 @@ struct Containers<'a> { /// ```rust /// let _guard = init_tracing(); /// ``` -fn init_tracing() -> DefaultGuard { +pub fn init_tracing() -> DefaultGuard { // converts all log records into tracing events // Note: Make sure to initialize without unwrapping, otherwise this causes // trouble when running multiple tests. @@ -535,16 +631,18 @@ fn init_tracing() -> DefaultGuard { let xmr_btc_filter = tracing::Level::DEBUG; let monero_harness_filter = tracing::Level::INFO; let bitcoin_harness_filter = tracing::Level::INFO; + let testcontainers_filter = tracing::Level::DEBUG; use tracing_subscriber::util::SubscriberInitExt as _; tracing_subscriber::fmt() .with_env_filter(format!( - "{},swap={},xmr_btc={},monero_harness={},bitcoin_harness={}", + "{},swap={},xmr_btc={},monero_harness={},bitcoin_harness={},testcontainers={}", global_filter, swap_filter, xmr_btc_filter, monero_harness_filter, bitcoin_harness_filter, + testcontainers_filter )) .set_default() }