From a813f2bb4058719dd73ec56b6f5eb68db473d09e Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 3 Jul 2024 15:12:12 -0400 Subject: [PATCH] feat: add compact block filter client Thanks to @thunderbiscuit for getting the work started initially. --- .github/workflows/test-swift.yaml | 2 +- .gitignore | 2 + bdk-android/lib/build.gradle.kts | 1 + bdk-ffi/Cargo.lock | 581 +++++++++++++----- bdk-ffi/Cargo.toml | 2 + bdk-ffi/src/bdk.udl | 243 ++++++++ bdk-ffi/src/error.rs | 26 + bdk-ffi/src/kyoto.rs | 233 +++++++ bdk-ffi/src/lib.rs | 14 + bdk-jvm/lib/build.gradle.kts | 2 + .../kotlin/org/bitcoindevkit/LiveKyotoTest.kt | 53 ++ bdk-python/scripts/generate-linux.sh | 2 +- bdk-python/tests/test_live_kyoto.py | 54 ++ .../BitcoinDevKitTests/LiveKyotoTests.swift | 54 ++ bdk-swift/justfile | 2 +- 15 files changed, 1130 insertions(+), 141 deletions(-) create mode 100644 bdk-ffi/src/kyoto.rs create mode 100644 bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveKyotoTest.kt create mode 100644 bdk-python/tests/test_live_kyoto.py create mode 100644 bdk-swift/Tests/BitcoinDevKitTests/LiveKyotoTests.swift diff --git a/.github/workflows/test-swift.yaml b/.github/workflows/test-swift.yaml index d2274221..c1bed743 100644 --- a/.github/workflows/test-swift.yaml +++ b/.github/workflows/test-swift.yaml @@ -24,4 +24,4 @@ jobs: - name: "Run Swift tests" working-directory: bdk-swift - run: swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests + run: swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests --skip LiveKyotoTests diff --git a/.gitignore b/.gitignore index ada663fe..444005ec 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ local.properties *.log *.dylib *.so +*.db +*/data/signet .DS_Store testdb xcuserdata diff --git a/bdk-android/lib/build.gradle.kts b/bdk-android/lib/build.gradle.kts index c1b64995..1fcfdb3e 100644 --- a/bdk-android/lib/build.gradle.kts +++ b/bdk-android/lib/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation("net.java.dev.jna:jna:5.14.0@aar") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7") implementation("androidx.appcompat:appcompat:1.4.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") implementation("androidx.core:core-ktx:1.7.0") api("org.slf4j:slf4j-api:1.7.30") diff --git a/bdk-ffi/Cargo.lock b/bdk-ffi/Cargo.lock index fee7d1a0..777b4709 100644 --- a/bdk-ffi/Cargo.lock +++ b/bdk-ffi/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.4.8" @@ -22,9 +37,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -37,49 +52,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "askama" @@ -130,9 +145,24 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] [[package]] name = "base58ck" @@ -140,7 +170,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin_hashes 0.14.0", ] @@ -173,9 +203,11 @@ dependencies = [ "bdk_core", "bdk_electrum", "bdk_esplora", + "bdk_kyoto", "bdk_wallet", "bitcoin-ffi", "thiserror", + "tokio", "uniffi", ] @@ -225,6 +257,17 @@ dependencies = [ "miniscript", ] +[[package]] +name = "bdk_kyoto" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff550e2637f3c51a155deed67269a714721c93d9825424a9646eec2ad8133b8" +dependencies = [ + "bdk_chain", + "bdk_wallet", + "kyoto-cbf", +] + [[package]] name = "bdk_wallet" version = "1.0.0-beta.5" @@ -255,31 +298,44 @@ dependencies = [ "serde", ] +[[package]] +name = "bip324" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b443a76f86143c093b211628be683ee592a097d316db6b90f723ed816bde1a49" +dependencies = [ + "bitcoin", + "bitcoin_hashes 0.15.0", + "chacha20-poly1305", + "rand", + "tokio", +] + [[package]] name = "bip39" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" dependencies = [ - "bitcoin_hashes 0.11.0", + "bitcoin_hashes 0.13.0", "serde", "unicode-normalization", ] [[package]] name = "bitcoin" -version = "0.32.2" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" +checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" dependencies = [ "base58ck", "base64 0.21.7", "bech32", - "bitcoin-internals", - "bitcoin-io", + "bitcoin-internals 0.3.0", + "bitcoin-io 0.1.2", "bitcoin-units", "bitcoin_hashes 0.14.0", - "hex-conservative", + "hex-conservative 0.2.1", "hex_lit", "secp256k1", "serde", @@ -295,6 +351,12 @@ dependencies = [ "uniffi", ] +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -304,27 +366,46 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b854212e29b96c8f0fe04cab11d57586c8f3257de0d146c76cb3b42b3eb9118" + [[package]] name = "bitcoin-io" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" +[[package]] +name = "bitcoin-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26792cd2bf245069a1c5acb06aa7ad7abe1de69b507c90b490bca81e0665d0ee" +dependencies = [ + "bitcoin-internals 0.4.0", +] + [[package]] name = "bitcoin-units" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb54da0b28892f3c52203a7191534033e051b6f4b52bc15480681b57b7e036f5" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "serde", ] [[package]] name = "bitcoin_hashes" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals 0.2.0", + "hex-conservative 0.1.2", +] [[package]] name = "bitcoin_hashes" @@ -332,16 +413,26 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ - "bitcoin-io", - "hex-conservative", + "bitcoin-io 0.1.2", + "hex-conservative 0.2.1", "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0982261c82a50d89d1a411602afee0498b3e0debe3d36693f0c661352809639" +dependencies = [ + "bitcoin-io 0.2.0", + "hex-conservative 0.3.0", +] + [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "byteorder" @@ -351,15 +442,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -389,9 +480,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -399,11 +493,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20-poly1305" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac8be588b1de2b7f1537ed39ba453a388d2cce60ce78ef5db449f71bebe58ba" + [[package]] name = "clap" -version = "4.5.7" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -411,9 +511,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -423,9 +523,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -435,15 +535,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "electrum-client" @@ -455,7 +555,7 @@ dependencies = [ "byteorder", "libc", "log", - "rustls 0.23.10", + "rustls 0.23.14", "serde", "serde_json", "webpki-roots", @@ -469,7 +569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23be31c97b2e505ac6af0d72a201caead71298a957639061a10314f6d4860cd7" dependencies = [ "bitcoin", - "hex-conservative", + "hex-conservative 0.2.1", "log", "minreq", "serde", @@ -507,6 +607,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "glob" version = "0.3.1" @@ -558,6 +664,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + [[package]] name = "hex-conservative" version = "0.2.1" @@ -567,6 +685,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" @@ -575,9 +702,9 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -585,11 +712,23 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "kyoto-cbf" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249c38ef600d49cc124fdb2f9f659df60df2f334405e8ab961adbf693ca1ab57" +dependencies = [ + "bip324", + "bitcoin", + "rusqlite", + "tokio", +] + [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libsqlite3-sys" @@ -604,9 +743,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -622,9 +761,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -638,20 +777,29 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniscript" -version = "12.0.0" +version = "12.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b59c67956fd276ceec0cf194fbf80754ef4d88a496d5cf5e4fdf33561466183d" +checksum = "add2d4aee30e4291ce5cffa3a322e441ff4d4bc57b38c8d9bf0e94faa50ab626" dependencies = [ "bech32", "bitcoin", "serde", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "minreq" -version = "2.11.2" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fdef521c74c2884a4f3570bcdb6d2a77b3c533feb6b27ac2ae72673cc221c64" +checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0" dependencies = [ "base64 0.12.3", "log", @@ -663,6 +811,17 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "nom" version = "7.1.3" @@ -673,11 +832,30 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "paste" @@ -685,11 +863,17 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plain" @@ -699,24 +883,27 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -763,7 +950,7 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -780,6 +967,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustls" version = "0.21.12" @@ -794,24 +987,24 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -825,9 +1018,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -872,9 +1065,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes 0.14.0", "rand", @@ -884,9 +1077,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] @@ -902,18 +1095,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -922,15 +1115,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "siphasher" version = "0.3.11" @@ -949,6 +1149,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -975,9 +1185,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.66" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -995,18 +1205,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -1015,9 +1225,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1028,6 +1238,34 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.5.11" @@ -1048,9 +1286,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -1113,9 +1351,9 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fcfa22f55829d3aaa7acfb1c5150224188fe0f27c59a8a3eddcaa24d1ffbe58" +checksum = "a22dbe67c1c957ac6e7611bdf605a6218aa86b0eebeb8be58b70ae85ad7d73dc" dependencies = [ "quote", "syn", @@ -1212,9 +1450,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1259,93 +1497,160 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", diff --git a/bdk-ffi/Cargo.toml b/bdk-ffi/Cargo.toml index 8974c853..ad32be2e 100644 --- a/bdk-ffi/Cargo.toml +++ b/bdk-ffi/Cargo.toml @@ -22,9 +22,11 @@ bdk_wallet = { version = "=1.0.0-beta.5", features = ["all-keys", "keys-bip39", bdk_core = { version = "0.3.0" } bdk_esplora = { version = "0.19.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] } bdk_electrum = { version = "0.19.0", default-features = false, features = ["use-rustls-ring"] } +bdk_kyoto = { version = "0.4.0", default-features = false, features = ["wallet", "rusqlite", "callbacks"] } bitcoin-ffi = { git = "https://github.com/bitcoindevkit/bitcoin-ffi", tag = "v0.1.2" } uniffi = { version = "=0.28.0" } +tokio = { version = "1", default-features = false, features = [ "rt-multi-thread", "sync" ] } thiserror = "1.0.58" [build-dependencies] diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index dc88fb79..09a7f5cd 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -314,6 +314,20 @@ interface TxidParseError { InvalidTxid(string txid); }; +/// Errors that may occur building a light client. +[Error] +interface LightClientBuilderError { + /// A database could not be opened or created. + DatabaseError(string reason); +}; + +/// Errors that may occur sending messages to a node. +[Error] +enum LightClientError { + /// The node is not currently running. + "NodeStopped", +}; + // ------------------------------------------------------------------------ // bdk_wallet crate - types module // ------------------------------------------------------------------------ @@ -1086,6 +1100,235 @@ dictionary KeychainAndIndex { u32 index; }; +// ------------------------------------------------------------------------ +// bdk-kyoto crate +// ------------------------------------------------------------------------ + +/// Build a BIP 157/158 light client to fetch transactions for a `Wallet`. +/// +/// Options: +/// * List of `Peer`: Bitcoin full-nodes for the light client to connect to. May be empty. +/// * `connections`: The number of connections for the light client to maintain. +/// * `recovery_height`: Optional height to start looking for scripts after, used in recoveries. +/// * `data_dir`: Optional directory to store block headers and peers. +/// +/// A note on recovering wallets. Developers should allow users to provide an +/// approximate recovery height and an estimated number of transactions for the +/// wallet. When determining how many scripts to check filters for, the `Wallet` +/// `lookahead` value will be used. To ensure all transactions are recovered, the +/// `lookahead` should be roughly the number of transactions in the wallet history. +interface LightClientBuilder { + /// Start a new [`LightClientBuilder`] + constructor(); + + /// The number of connections for the light client to maintain. Default is two. + LightClientBuilder connections(u8 connections); + + /// Directory to store block headers and peers. If none is provided, the current + /// working directory will be used. + LightClientBuilder data_dir(string data_dir); + + /// Height to start looking for scripts after, used in recoveries. + /// Note the height is non-inclusive. + LightClientBuilder recovery_height(u32 recovery_height); + + /// Bitcoin full-nodes to attempt a connection with. + LightClientBuilder peers(sequence peers); + + /// Construct a [`LightNode`] and [`LightClient`] for a [`Wallet`]. + [Throws=LightClientBuilderError] + NodePair build([ByRef] Wallet wallet); +}; + +/// A [`LightClient`] handles updates from a [`LightNode`] and allows +/// for transaction broadcasting. +interface LightClient { + /// Return an [`Update`] for the [`Wallet`] while responding to events + /// using a [`NodeEventHandler`] implementation. See the [`NodeEventHandler`] + /// documentation for more information. Note that the [`Update`] may be null + /// if there have been no new blocks mined since the last sync. + /// + /// If no [`NodeEventHandler`] is provided, messages of the node will print to + /// the console. + [Async] + Update? update(NodeEventHandler? event_handler); + + /// Broadcast a transaction to the network, erroring if the node has stopped running. + /// + /// The wallet is used to determine which recipients are change for the wallet, + /// so these scripts may be monitored by the node. + [Async, Throws=LightClientError] + void broadcast([ByRef] Wallet wallet, [ByRef] Transaction transaction); + + /// Add another [`Address`] to the [`LightNode`] to watch for inclusion in new blocks. + /// + /// Note that previous blocks will not be checked for inclusion of these scripts, + /// only future blocks. + [Async, Throws=LightClientError] + void watch_receive_address(Address address); + + /// Stop the [`LightNode`]. + [Async, Throws=LightClientError] + void shutdown(); +}; + +/// A [`LightNode`] gathers transactions for a [`Wallet`]. +/// To receive [`Update`] for [`Wallet`], refer to the +/// [`LightClient`]. The [`LightNode`] will run until instructed +/// to stop, which should occur on a different thread +/// than the underlying application. Use the [`run_node`] +/// function to run the node on a separate thread. +interface LightNode { + void run(); +}; + +/// Receive a [`LightClient`] and [`LightNode`]. +dictionary NodePair { + /// The node to run and fetch transactions for a [`Wallet`]. + LightNode node; + + /// The client to interact with a running node. + LightClient client; +}; + +/// A peer to connect to over the Bitcoin peer-to-peer network. +dictionary Peer { + /// The IP address to reach the node. + IpAddress address; + + /// The port to reach the node. If none is provided, the default + /// port for the selected network will be used. + u16? port; + + /// Does the remote node offer encrypted peer-to-peer connection. + boolean v2_transport; +}; + +/// An IP address to connect to over TCP. +interface IpAddress { + /// Build an IPv4 address. + [Name=from_ipv4] + constructor(u8 q1, u8 q2, u8 q3, u8 q4); + + /// Build an IPv6 address. + [Name=from_ipv6] + constructor(u16 a, u16 b, u16 c, u16 d, u16 e, u16 f, u16 g, u16 h); +}; + +/// Warnings a node may issue while running. +[Enum] +interface Warning { + /// The node is looking for connections to peers. + NotEnoughConnections(); + + /// A connection to a peer timed out. + PeerTimedOut(); + + /// The node was unable to connect to a peer in the database. + CouldNotConnect(); + + /// A connection was maintained, but the peer does not signal for compact block filers. + NoCompactFilters(); + + /// The node has been waiting for new inv and will find new peers to avoid block withholding. + PotentialStaleTip(); + + /// A peer sent us a peer-to-peer message the node did not request. + UnsolicitedMessage(); + + /// The provided anchor is deeper than the database history. + /// This should not occur under normal use. + UnlinkableAnchor(); + + /// The headers in the database do not link together. + /// Recoverable by deleting the database. + CorruptedHeaders(); + + /// A transaction got rejected, likely for being an insufficient fee or non-standard transaction. + TransactionRejected(); + + /// A database failed to persist some data and may retry again. + FailedPersistance(string warning); + + /// The peer sent us a potential fork. + EvaluatingFork(); + + /// The peer database has no values. + EmptyPeerDatabase(); + + /// An unexpected error occured processing a peer-to-peer message. + UnexpectedSyncError(string warning); + + /// A channel that was supposed to receive a message was dropped. + ChannelDropped(); +}; + +/// The state of the node with respect to connected peers. +enum NodeState { + /// Behind on block headers according to our peers. + "Behind", + + /// Downloading compact block filter headers. + "HeadersSynced", + + /// Scanning compact block filters. + "FilterHeadersSynced", + + /// Asking for blocks with matches. + "FiltersSynced", + + /// Found all known transactions to the wallet. + "TransactionsSynced" +}; + +/// A [`NodeEventHandler`] responds to messages from +/// a running [`LightNode`]. The primary use of a +/// [`NodeEventHandler`] is to inform users of changes +/// that occur as their wallet syncs with the network. +/// Normally, this simply means updating user interface +///components or writing to a log file. +[Trait, WithForeign] +interface NodeEventHandler { + /// The node is passing a debug message. + /// Useful for developers or advanced usage. + void dialog(string dialog); + + /// The node has sent a warning. + /// User interface components may react to + /// warnings to inform the user of the sync + /// status of their wallet. + void warning(Warning warning); + + /// The node is advancing in a sync + /// or was informed of a new block. + /// User interface components may react + /// to changes in state. + void state_changed(NodeState state); + + /// The node is connected to all required + /// peers. Users should be informed of this + /// event. + void connections_met(); + + /// A transaction was successfully sent over + /// TCP to at least one peer. Note that this + /// does not mean a transaction will be accepted. + void tx_sent(Txid txid); + + /// A transaction was rejected. + void tx_failed(Txid txid); + + /// One or more blocks were disconnected from the + /// chain of most work. No action is required, + /// however the user should be informed of such + /// an event. + void blocks_disconnected(sequence blocks); + + /// The node has synced blocks up to the given height. + void synced(u32 tip); +}; + + // ------------------------------------------------------------------------ // bdk_wallet crate - bitcoin re-exports // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 88805eb2..31041f6d 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -751,6 +751,18 @@ pub enum TxidParseError { InvalidTxid { txid: String }, } +#[derive(Debug, thiserror::Error)] +pub enum LightClientBuilderError { + #[error("the database could not be opened or created: {reason}")] + DatabaseError { reason: String }, +} + +#[derive(Debug, thiserror::Error)] +pub enum LightClientError { + #[error("the node is no longer running")] + NodeStopped, +} + // ------------------------------------------------------------------------ // error conversions // ------------------------------------------------------------------------ @@ -1450,6 +1462,20 @@ impl From for SqliteError { } } +impl From for LightClientBuilderError { + fn from(value: bdk_kyoto::builder::BuilderError) -> Self { + LightClientBuilderError::DatabaseError { + reason: value.to_string(), + } + } +} + +impl From for LightClientError { + fn from(_value: bdk_kyoto::ClientError) -> Self { + LightClientError::NodeStopped + } +} + // ------------------------------------------------------------------------ // error tests // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/kyoto.rs b/bdk-ffi/src/kyoto.rs new file mode 100644 index 00000000..fde7fcbe --- /dev/null +++ b/bdk-ffi/src/kyoto.rs @@ -0,0 +1,233 @@ +use bdk_core::bitcoin::p2p::address::AddrV2; +use bdk_kyoto::builder::LightClientBuilder as BDKLightClientBuilder; +use bdk_kyoto::logger::PrintLogger; +use bdk_kyoto::{Client, NodeEventHandler, TrustedPeer}; +use bdk_kyoto::{NodeDefault, ServiceFlags}; +use bdk_wallet::KeychainKind; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::Mutex; + +use crate::bitcoin::{Address, Transaction}; +use crate::error::{LightClientBuilderError, LightClientError}; +use crate::wallet::Wallet; +use crate::Update; + +const TIMEOUT: u64 = 10; +const DEFAULT_CONNECTIONS: u8 = 2; +const CWD_PATH: &str = "."; + +pub struct LightClient { + client: Mutex>, +} + +pub struct LightNode { + node: NodeDefault, +} + +impl LightNode { + pub fn run(self: Arc) { + std::thread::spawn(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async move { + let _ = self.node.run().await; + }) + }); + } +} + +pub struct NodePair { + pub node: Arc, + pub client: Arc, +} + +#[derive(Clone)] +pub struct LightClientBuilder { + connections: u8, + data_dir: Option, + recovery_height: Option, + peers: Vec, +} + +impl LightClientBuilder { + pub fn new() -> Self { + LightClientBuilder { + connections: DEFAULT_CONNECTIONS, + data_dir: None, + recovery_height: None, + peers: Vec::new(), + } + } + + pub fn connections(&self, connections: u8) -> Arc { + Arc::new(LightClientBuilder { + connections, + ..self.clone() + }) + } + + pub fn data_dir(&self, data_dir: String) -> Arc { + Arc::new(LightClientBuilder { + data_dir: Some(data_dir), + ..self.clone() + }) + } + + pub fn recovery_height(&self, recovery_height: u32) -> Arc { + Arc::new(LightClientBuilder { + recovery_height: Some(recovery_height), + ..self.clone() + }) + } + + pub fn peers(&self, peers: Vec) -> Arc { + Arc::new(LightClientBuilder { + peers, + ..self.clone() + }) + } + + pub fn build(&self, wallet: &Wallet) -> Result { + let wallet = wallet.get_wallet(); + + let mut trusted_peers = Vec::new(); + for peer in self.peers.iter() { + trusted_peers.push(peer.clone().into()); + } + let path_buf = match self.data_dir.clone() { + Some(path) => { + PathBuf::from_str(&path).map_err(|e| LightClientBuilderError::DatabaseError { + reason: e.to_string(), + })? + } + None => { + PathBuf::from_str(CWD_PATH).map_err(|e| LightClientBuilderError::DatabaseError { + reason: e.to_string(), + })? + } + }; + let mut builder = BDKLightClientBuilder::new(&wallet) + .connections(self.connections) + .data_dir(path_buf) + .timeout_duration(Duration::from_secs(TIMEOUT)) + .peers(trusted_peers); + + if let Some(recovery) = self.recovery_height { + builder = builder.scan_after(recovery) + } + + let (node, bdk_kyoto_client) = builder.build()?; + + let node = LightNode { + node, + }; + + let client = LightClient { + client: Mutex::new(bdk_kyoto_client), + }; + + Ok(NodePair { + node: Arc::new(node), + client: Arc::new(client), + }) + } +} + +impl LightClient { + pub async fn update( + &self, + event_handler: Option>, + ) -> Option> { + let logger = event_handler.unwrap_or(Arc::new(PrintLogger::new())); + let update = self.client.lock().await.update(logger.as_ref()).await; + update.map(|update| Arc::new(Update(update.into()))) + } + + pub async fn broadcast( + &self, + wallet: &Wallet, + transaction: &Transaction, + ) -> Result<(), LightClientError> { + let client = self.client.lock().await; + for output in transaction.output() { + let spk = output.script_pubkey; + if wallet.is_mine(Arc::clone(&spk)) { + client + .add_script(spk.0.clone()) + .await + .map_err(|_| LightClientError::NodeStopped)?; + } + } + let tx = transaction.into(); + client + .broadcast(tx, bdk_kyoto::TxBroadcastPolicy::RandomPeer) + .await + .map_err(From::from) + } + + pub async fn watch_receive_address( + &self, + address: Arc
, + ) -> Result<(), LightClientError> { + let client = self.client.lock().await; + let script = address.script_pubkey().0.clone(); + client.add_script(script).await.map_err(From::from) + } + + pub async fn shutdown(&self) -> Result<(), LightClientError> { + let client = self.client.lock().await; + client.shutdown().await.map_err(From::from) + } +} + +#[derive(Clone)] +pub struct Peer { + pub address: Arc, + pub port: Option, + pub v2_transport: bool, +} + +pub struct IpAddress { + inner: IpAddr, +} + +impl IpAddress { + pub fn from_ipv4(q1: u8, q2: u8, q3: u8, q4: u8) -> Self { + Self { + inner: IpAddr::V4(Ipv4Addr::new(q1, q2, q3, q4)), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn from_ipv6(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Self { + Self { + inner: IpAddr::V6(Ipv6Addr::new(a, b, c, d, e, f, g, h)), + } + } +} + +impl From for TrustedPeer { + fn from(peer: Peer) -> Self { + let services = if peer.v2_transport { + let mut services = ServiceFlags::P2P_V2; + services.add(ServiceFlags::NETWORK); + services.add(ServiceFlags::COMPACT_FILTERS); + services + } else { + let mut services = ServiceFlags::COMPACT_FILTERS; + services.add(ServiceFlags::NETWORK); + services + }; + let addr_v2 = match peer.address.inner { + IpAddr::V4(ipv4_addr) => AddrV2::Ipv4(ipv4_addr), + IpAddr::V6(ipv6_addr) => AddrV2::Ipv6(ipv6_addr), + }; + TrustedPeer::new(addr_v2, peer.port, services) + } +} diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 684d0fd0..1d8e0f00 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -4,6 +4,7 @@ mod electrum; mod error; mod esplora; mod keys; +mod kyoto; mod store; mod tx_builder; mod types; @@ -30,6 +31,8 @@ use crate::error::ElectrumError; use crate::error::EsploraError; use crate::error::ExtractTxError; use crate::error::FromScriptError; +use crate::error::LightClientBuilderError; +use crate::error::LightClientError; use crate::error::LoadWithPersistError; use crate::error::MiniscriptError; use crate::error::PersistenceError; @@ -79,10 +82,21 @@ use bitcoin_ffi::FeeRate; use bitcoin_ffi::Network; use bitcoin_ffi::OutPoint; use bitcoin_ffi::Script; +use bitcoin_ffi::Txid; use bdk_wallet::keys::bip39::WordCount; use bdk_wallet::tx_builder::ChangeSpendPolicy; use bdk_wallet::ChangeSet; use bdk_wallet::KeychainKind; +use bdk_kyoto::NodeEventHandler; +use bdk_kyoto::NodeState; +use bdk_kyoto::Warning; +use kyoto::IpAddress; +use kyoto::LightClient; +use kyoto::LightClientBuilder; +use kyoto::LightNode; +use kyoto::NodePair; +use kyoto::Peer; + uniffi::include_scaffolding!("bdk"); diff --git a/bdk-jvm/lib/build.gradle.kts b/bdk-jvm/lib/build.gradle.kts index 5731cd79..c7561f60 100644 --- a/bdk-jvm/lib/build.gradle.kts +++ b/bdk-jvm/lib/build.gradle.kts @@ -38,6 +38,7 @@ tasks.test { exclude("**/LiveTransactionTest.class") exclude("**/LiveTxBuilderTest.class") exclude("**/LiveWalletTest.class") + exclude("**/LiveKyotoTest.class") } } @@ -62,6 +63,7 @@ tasks.withType { dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom")) implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") implementation("net.java.dev.jna:jna:5.14.0") api("org.slf4j:slf4j-api:1.7.30") diff --git a/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveKyotoTest.kt b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveKyotoTest.kt new file mode 100644 index 00000000..0e84f06c --- /dev/null +++ b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveKyotoTest.kt @@ -0,0 +1,53 @@ +package org.bitcoindevkit +import org.rustbitcoin.bitcoin.Network + +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.AfterTest +import kotlin.test.assertNotNull + +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.deleteRecursively + +class LiveKyotoTest { + private val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET) + private val changeDescriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET) + private val ip: IpAddress = IpAddress.fromIpv4(68u, 47u, 229u, 218u) + private val peer: Peer = Peer(ip, null, false) + private val currentPath = Paths.get(".").toAbsolutePath().normalize() + private val persistenceFilePath = Files.createTempDirectory(currentPath, "tempDirPrefix_") + + @OptIn(ExperimentalPathApi::class) + @AfterTest + fun cleanup() { + persistenceFilePath.deleteRecursively() + } + + @Test + fun testKyoto() { + val conn: Connection = Connection.newInMemory() + val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn) + val peers = listOf(peer) + runBlocking { + val nodePair = LightClientBuilder() + .peers(peers) + .connections(1u) + .dataDir(persistenceFilePath.toString()) + .build(wallet) + val client = nodePair.client + val node = nodePair.node + println("Node running") + node.run() + val updateOpt: Update? = client.update(null) + val update = assertNotNull(updateOpt) + wallet.applyUpdate(update) + assert(wallet.balance().total.toSat() > 0uL) { + "Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again." + } + client.shutdown() + println("Test completed successfully") + } + } +} diff --git a/bdk-python/scripts/generate-linux.sh b/bdk-python/scripts/generate-linux.sh index c8d5e713..58afc287 100644 --- a/bdk-python/scripts/generate-linux.sh +++ b/bdk-python/scripts/generate-linux.sh @@ -16,4 +16,4 @@ cargo run --bin uniffi-bindgen generate --library ./target/release-smaller/libbd echo "Copying linux libbdkffi.so..." cp ./target/release-smaller/libbdkffi.so ../bdk-python/src/bdkpython/libbdkffi.so -echo "All done!" +echo "All done!" \ No newline at end of file diff --git a/bdk-python/tests/test_live_kyoto.py b/bdk-python/tests/test_live_kyoto.py new file mode 100644 index 00000000..e2eea434 --- /dev/null +++ b/bdk-python/tests/test_live_kyoto.py @@ -0,0 +1,54 @@ +from bdkpython import * +from bdkpython.bitcoin import * +import unittest +import os + +network: Network = Network.SIGNET + +ip: IpAddress = IpAddress.from_ipv4(68, 47, 229, 218) +peer: Peer = Peer(address=ip, port=None, v2_transport=False) + +descriptor: Descriptor = Descriptor( + "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", + network=network +) +change_descriptor: Descriptor = Descriptor( + "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", + network=network +) + +class LiveKyotoTest(unittest.IsolatedAsyncioTestCase): + + def tearDown(self) -> None: + if os.path.exists("./bdk_persistence.sqlite"): + os.remove("./bdk_persistence.sqlite") + if os.path.exists("./data/signet/headers.db"): + os.remove("./data/signet/headers.db") + if os.path.exists("./data/signet/peers.db"): + os.remove("./data/signet/peers.db") + + async def testKyoto(self) -> None: + connection: Connection = Connection.new_in_memory() + wallet: Wallet = Wallet( + descriptor, + change_descriptor, + network, + connection + ) + peers = [peer] + node_pair: NodePair = LightClientBuilder().peers(peers).connections(1).build(wallet) + client: LightClient = node_pair.client + node: LightNode = node_pair.node + node.run() + update: Update = await client.update(None) + self.assertIsNotNone(update, "Update is None. This should not be possible.") + wallet.apply_update(update) + self.assertGreater( + wallet.balance().total.to_sat(), + 0, + f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(KeychainKind.EXTERNAL).address} and try again." + ) + await client.shutdown() + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/bdk-swift/Tests/BitcoinDevKitTests/LiveKyotoTests.swift b/bdk-swift/Tests/BitcoinDevKitTests/LiveKyotoTests.swift new file mode 100644 index 00000000..0e580663 --- /dev/null +++ b/bdk-swift/Tests/BitcoinDevKitTests/LiveKyotoTests.swift @@ -0,0 +1,54 @@ +import XCTest +@testable import BitcoinDevKit + +final class LiveKyotoTests: XCTestCase { + private let descriptor = try! Descriptor( + descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", + network: Network.signet + ) + private let changeDescriptor = try! Descriptor( + descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", + network: Network.signet + ) + private let peer = IpAddress.fromIpv4(q1: 68, q2: 47, q3: 229, q4: 218) + private let cwd = FileManager.default.currentDirectoryPath.appending("/temp") + + override func tearDownWithError() throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: cwd) { + try fileManager.removeItem(atPath: cwd) + } + } + + func testSuccessfullySyncs() async throws { + let connection = try Connection.newInMemory() + let wallet = try Wallet( + descriptor: descriptor, + changeDescriptor: changeDescriptor, + network: Network.signet, + connection: connection + ) + let trusted_peer = Peer(address: peer, port: nil, v2Transport: false) + let nodePair = try LightClientBuilder() + .peers(peers: [trusted_peer]) + .connections(connections: 1) + .dataDir(dataDir: cwd) + .build(wallet: wallet) + let client = nodePair.client + let node = nodePair.node + node.run() + let update = await client.update(eventHandler: nil) + if let update = update { + try wallet.applyUpdate(update: update) + let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description + XCTAssertGreaterThan( + wallet.balance().total.toSat(), + UInt64(0), + "Wallet must have positive balance, please send funds to \(address)" + ) + print("Update applied correctly") + } else { + print("Update is nil. Ensure this test is ran infrequently.") + } + } +} diff --git a/bdk-swift/justfile b/bdk-swift/justfile index 519c3acd..d6c76a76 100644 --- a/bdk-swift/justfile +++ b/bdk-swift/justfile @@ -11,4 +11,4 @@ test: swift test test-offline: - swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests + swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests --skip LiveKyotoTests