From 4a6f9f7ece994e52de384efc554de8e566a32f47 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 | 580 +++++++++++++----- bdk-ffi/Cargo.toml | 2 + bdk-ffi/src/bdk.udl | 221 +++++++ bdk-ffi/src/error.rs | 26 + bdk-ffi/src/kyoto.rs | 349 +++++++++++ bdk-ffi/src/lib.rs | 13 + bdk-jvm/lib/build.gradle.kts | 2 + .../kotlin/org/bitcoindevkit/LiveKyotoTest.kt | 62 ++ bdk-python/scripts/generate-linux.sh | 2 +- bdk-python/tests/test_live_kyoto.py | 62 ++ .../BitcoinDevKitTests/LiveKyotoTests.swift | 61 ++ bdk-swift/justfile | 2 +- 15 files changed, 1246 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 af77e398..1d7ace29 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 1f5b7866..738bbc5b 100644 --- a/bdk-android/lib/build.gradle.kts +++ b/bdk-android/lib/build.gradle.kts @@ -55,6 +55,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 638e6827..a14aeabb 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.8.11" @@ -16,9 +31,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", @@ -31,49 +46,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" @@ -124,9 +139,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" @@ -134,7 +164,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", ] @@ -167,9 +197,11 @@ dependencies = [ "bdk_core", "bdk_electrum", "bdk_esplora", + "bdk_kyoto", "bdk_wallet", "bitcoin-ffi", "thiserror", + "tokio", "uniffi", ] @@ -218,6 +250,16 @@ dependencies = [ "miniscript", ] +[[package]] +name = "bdk_kyoto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb96d038b25728d3b8b533dbf2de51871df2fb091e17f1c112dda922336504" +dependencies = [ + "bdk_wallet", + "kyoto-cbf", +] + [[package]] name = "bdk_wallet" version = "1.0.0" @@ -248,31 +290,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", @@ -288,6 +343,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" @@ -297,27 +358,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" @@ -325,16 +405,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" @@ -344,15 +434,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", ] @@ -382,9 +472,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" @@ -392,11 +485,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", @@ -404,9 +503,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", @@ -416,9 +515,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", @@ -428,15 +527,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" @@ -448,7 +547,7 @@ dependencies = [ "byteorder", "libc", "log", - "rustls 0.23.10", + "rustls 0.23.14", "serde", "serde_json", "webpki-roots", @@ -462,7 +561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0da3c186d286e046253ccdc4bb71aa87ef872e4eff2045947c0c4fe3d2b2efc" dependencies = [ "bitcoin", - "hex-conservative", + "hex-conservative 0.2.1", "log", "minreq", "serde", @@ -500,6 +599,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" @@ -542,6 +647,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" @@ -551,6 +668,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" @@ -559,9 +685,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" @@ -569,11 +695,23 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "kyoto-cbf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6943c874dd9f43175b3751d091d11f43a0d4c9a9bc10751c0f19a70c1862d64e" +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" @@ -588,9 +726,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" @@ -606,9 +744,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", @@ -622,20 +760,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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +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", @@ -647,6 +794,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" @@ -657,11 +815,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" @@ -669,11 +846,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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + [[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" @@ -683,24 +866,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", ] @@ -747,7 +933,7 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -764,6 +950,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" @@ -778,24 +970,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" @@ -809,9 +1001,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", @@ -856,9 +1048,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", @@ -868,9 +1060,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", ] @@ -886,18 +1078,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", @@ -906,15 +1098,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" @@ -933,6 +1132,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -959,9 +1168,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", @@ -979,18 +1188,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", @@ -999,9 +1208,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", ] @@ -1012,6 +1221,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" @@ -1032,9 +1269,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" @@ -1097,9 +1334,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", @@ -1196,9 +1433,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" @@ -1243,93 +1480,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 5fe44fcc..8459fae7 100644 --- a/bdk-ffi/Cargo.toml +++ b/bdk-ffi/Cargo.toml @@ -22,10 +22,12 @@ bdk_wallet = { version = "1.0.0", features = ["all-keys", "keys-bip39", "rusqlit bdk_core = { version = "0.4.1" } bdk_esplora = { version = "0.20.1", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] } bdk_electrum = { version = "0.20.1", default-features = false, features = ["use-rustls-ring"] } +bdk_kyoto = { version = "0.7.0" } bitcoin-ffi = { git = "https://github.com/bitcoindevkit/bitcoin-ffi", tag = "v0.1.2" } uniffi = { version = "=0.28.0" } thiserror = "1.0.58" +tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thread", "sync" ] } [build-dependencies] uniffi = { version = "=0.28.0", features = ["build"] } diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 96b4c635..cf261ca9 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -315,6 +315,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 // ------------------------------------------------------------------------ @@ -1222,6 +1236,213 @@ 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 [`LightClient`] for a [`Wallet`]. + [Throws=LightClientBuilderError] + LightClient build([ByRef] Wallet wallet); +}; + +/// A [`Client`] handles wallet updates from a [`LightNode`]. +interface Client { + /// Return the next available log message from a node. + [Async] + Log next_log(); + + /// Return the next available warning message from a node. + [Async] + Warning next_warning(); + + /// Return an [`Update`]. This is method returns once the node syncs to the rest of + /// the network or a new block has been gossiped. + [Async] + Update? update(); + + /// Add scripts for the node to watch for as they are revealed. Typically used after creating + /// a transaction or revealing a receive address. + /// + /// Note that only future blocks will be checked for these scripts, not past blocks. + [Async, Throws=LightClientError] + void add_revealed_scripts([ByRef] Wallet wallet); + + /// The minimum fee rate required to broadcast a transcation to all connected peers. + [Async, Throws=LightClientError] + FeeRate min_broadcast_feerate(); + + /// Broadcast a transaction to the network, erroring if the node has stopped running. + [Async, Throws=LightClientError] + void broadcast([ByRef] Transaction transaction); + + /// Check if the node is still running in the background. + [Async] + boolean is_running(); + + /// Stop the [`LightNode`]. Errors if the node is already stopped. + [Async, Throws=LightClientError] + void shutdown(); +}; + +/// A [`LightNode`] gathers transactions for a [`Wallet`]. +/// To receive [`Update`] for [`Wallet`], refer to the +/// [`Client`]. The [`LightNode`] will run until instructed +/// to stop. +interface LightNode { + /// Start the node on a detached OS thread and immediately return. + void run(); +}; + +/// Receive a [`Client`] and [`LightNode`]. +dictionary LightClient { + /// Publish events to the node, like broadcasting transactions or adding scripts. + Client client; + + /// The node to run and fetch transactions for a [`Wallet`]. + LightNode node; +}; + +/// 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); +}; + +/// A log message from the node. +[Enum] +interface Log { + /// A human-readable debug message. + Dialog(string log); + + /// All the required connections have been met. This is subject to change. + ConnectionsMet(); + + /// A percentage value of filters that have been scanned. + Progress(f32 progress); + + /// A state in the node syncing process. + StateUpdate(NodeState node_state); + + /// A transaction was broadcast over the wire. + /// The transaction may or may not be rejected by recipient nodes. + TxSent(string txid); +}; + +/// Warnings a node may issue while running. +[Enum] +interface Warning { + /// The node is looking for connections to peers. + NeedConnections(); + + /// 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 starting height is deeper than the database history. + /// This should not occur under normal use. + InvalidStartHeight(); + + /// 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(string txid, string? reason); + + /// A database failed to persist some data and may retry again. + FailedPersistence(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); + + /// The node failed to respond to a message sent from the client. + RequestFailed(); +}; + +/// 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" +}; + // ------------------------------------------------------------------------ // bdk_wallet crate - bitcoin re-exports // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 0287398e..f5fa5fcd 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -754,6 +754,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 // ------------------------------------------------------------------------ @@ -1459,6 +1471,20 @@ impl From for SqliteError { } } +impl From for LightClientBuilderError { + fn from(value: bdk_kyoto::builder::SqlInitializationError) -> Self { + LightClientBuilderError::DatabaseError { + reason: value.to_string(), + } + } +} + +impl From for LightClientError { + fn from(_value: bdk_kyoto::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..0dc1a756 --- /dev/null +++ b/bdk-ffi/src/kyoto.rs @@ -0,0 +1,349 @@ +use bdk_kyoto::builder::LightClientBuilder as BDKLightClientBuilder; +use bdk_kyoto::builder::ServiceFlags; +use bdk_kyoto::builder::TrustedPeer; +use bdk_kyoto::kyoto::AddrV2; +use bdk_kyoto::kyoto::ScriptBuf; +use bdk_kyoto::LightClient as BDKLightClient; +use bdk_kyoto::LogSubscriber; +use bdk_kyoto::NodeDefault; +use bdk_kyoto::NodeState; +use bdk_kyoto::RejectReason; +use bdk_kyoto::Requester; +use bdk_kyoto::UpdateSubscriber; +use bdk_kyoto::WalletExt; +use bdk_kyoto::Warning as Warn; +use bdk_kyoto::WarningSubscriber; + +use bitcoin_ffi::FeeRate; + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; + +use tokio::sync::Mutex; + +use crate::bitcoin::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 { + pub client: Arc, + pub node: Arc, +} + +pub struct Client { + sender: Arc, + log_rx: Mutex, + warning_rx: Mutex, + update_rx: 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; + }) + }); + } +} + +#[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 = self + .data_dir + .clone() + .map(|path| PathBuf::from(&path)) + .unwrap_or(PathBuf::from(CWD_PATH)); + + let mut builder = BDKLightClientBuilder::new() + .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 BDKLightClient { + requester, + log_subscriber, + warning_subscriber, + update_subscriber, + node, + } = builder.build(&wallet)?; + + let node = LightNode { node }; + + let client = Client { + sender: Arc::new(requester), + log_rx: Mutex::new(log_subscriber), + warning_rx: Mutex::new(warning_subscriber), + update_rx: Mutex::new(update_subscriber), + }; + + Ok(LightClient { + client: Arc::new(client), + node: Arc::new(node), + }) + } +} + +impl Client { + pub async fn next_log(&self) -> Log { + let mut log_rx = self.log_rx.lock().await; + log_rx.next_log().await.into() + } + + pub async fn next_warning(&self) -> Warning { + let mut warn_rx = self.warning_rx.lock().await; + warn_rx.next_warning().await.into() + } + + pub async fn update(&self) -> Option> { + let update = self.update_rx.lock().await.update().await; + update.map(|update| Arc::new(Update(update))) + } + + pub async fn add_revealed_scripts(&self, wallet: &Wallet) -> Result<(), LightClientError> { + let script_iter: Vec = { + let wallet_lock = wallet.get_wallet(); + wallet_lock.peek_revealed_plus_lookahead().collect() + }; + for script in script_iter.into_iter() { + self.sender + .add_script(script) + .await + .map_err(|_| LightClientError::NodeStopped)? + } + Ok(()) + } + + pub async fn broadcast(&self, transaction: &Transaction) -> Result<(), LightClientError> { + let tx = transaction.into(); + self.sender.broadcast_random(tx).await.map_err(From::from) + } + + pub async fn min_broadcast_feerate(&self) -> Result, LightClientError> { + self.sender + .broadcast_min_feerate() + .await + .map_err(|_| LightClientError::NodeStopped) + .map(|fee| Arc::new(FeeRate(fee))) + } + + pub async fn is_running(&self) -> bool { + self.sender.is_running().await + } + + pub async fn shutdown(&self) -> Result<(), LightClientError> { + self.sender.shutdown().await.map_err(From::from) + } +} + +pub enum Log { + Dialog { log: String }, + ConnectionsMet, + Progress { progress: f32 }, + StateUpdate { node_state: NodeState }, + TxSent { txid: String }, +} + +impl From for Log { + fn from(value: bdk_kyoto::Log) -> Log { + match value { + bdk_kyoto::Log::Dialog(log) => Log::Dialog { log }, + bdk_kyoto::Log::ConnectionsMet => Log::ConnectionsMet, + bdk_kyoto::Log::Progress(progress) => Log::Progress { + progress: progress.percentage_complete(), + }, + bdk_kyoto::Log::TxSent(txid) => Log::TxSent { + txid: txid.to_string(), + }, + bdk_kyoto::Log::StateChange(state) => Log::StateUpdate { node_state: state }, + } + } +} + +pub enum Warning { + NeedConnections, + PeerTimedOut, + CouldNotConnect, + NoCompactFilters, + PotentialStaleTip, + UnsolicitedMessage, + InvalidStartHeight, + CorruptedHeaders, + TransactionRejected { + txid: String, + reason: Option, + }, + FailedPersistence { + warning: String, + }, + EvaluatingFork, + EmptyPeerDatabase, + UnexpectedSyncError { + warning: String, + }, + RequestFailed, +} + +impl From for Warning { + fn from(value: Warn) -> Warning { + match value { + Warn::NotEnoughConnections => Warning::NeedConnections, + Warn::PeerTimedOut => Warning::PeerTimedOut, + Warn::CouldNotConnect => Warning::CouldNotConnect, + Warn::NoCompactFilters => Warning::NoCompactFilters, + Warn::PotentialStaleTip => Warning::PotentialStaleTip, + Warn::UnsolicitedMessage => Warning::UnsolicitedMessage, + Warn::UnlinkableAnchor => Warning::InvalidStartHeight, + Warn::CorruptedHeaders => Warning::CorruptedHeaders, + Warn::TransactionRejected(reject) => { + let reason = reject.reason.map(|r| r.into_string()); + Warning::TransactionRejected { + txid: reject.txid.to_string(), + reason, + } + } + Warn::FailedPersistance { warning } => Warning::FailedPersistence { warning }, + Warn::EvaluatingFork => Warning::EvaluatingFork, + Warn::EmptyPeerDatabase => Warning::EmptyPeerDatabase, + Warn::UnexpectedSyncError { warning } => Warning::UnexpectedSyncError { warning }, + Warn::ChannelDropped => Warning::RequestFailed, + } + } +} + +#[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) + } +} + +trait DisplayExt { + fn into_string(self) -> String; +} + +impl DisplayExt for RejectReason { + fn into_string(self) -> String { + let message = match self { + RejectReason::Malformed => "Message could not be decoded.", + RejectReason::Invalid => "Transaction was invalid for some reason.", + RejectReason::Obsolete => "Client version is no longer supported.", + RejectReason::Duplicate => "Duplicate version message received.", + RejectReason::NonStandard => "Transaction was nonstandard.", + RejectReason::Dust => "One or more outputs are below the dust threshold.", + RejectReason::Fee => "Transaction does not have enough fee to be mined.", + RejectReason::Checkpoint => "Inconsistent with compiled checkpoint.", + }; + message.into() + } +} diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index c0b8b79f..e550d0cf 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; @@ -31,6 +32,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; @@ -87,4 +90,14 @@ use bdk_wallet::tx_builder::ChangeSpendPolicy; use bdk_wallet::ChangeSet; use bdk_wallet::KeychainKind; +use bdk_kyoto::NodeState; +use kyoto::Client; +use kyoto::IpAddress; +use kyoto::LightClient; +use kyoto::LightClientBuilder; +use kyoto::LightNode; +use kyoto::Log; +use kyoto::Peer; +use kyoto::Warning; + uniffi::include_scaffolding!("bdk"); diff --git a/bdk-jvm/lib/build.gradle.kts b/bdk-jvm/lib/build.gradle.kts index c1860daa..a9f89947 100644 --- a/bdk-jvm/lib/build.gradle.kts +++ b/bdk-jvm/lib/build.gradle.kts @@ -39,6 +39,7 @@ tasks.test { exclude("**/LiveTransactionTest.class") exclude("**/LiveTxBuilderTest.class") exclude("**/LiveWalletTest.class") + exclude("**/LiveKyotoTest.class") } } @@ -63,6 +64,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..aa0b3875 --- /dev/null +++ b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveKyotoTest.kt @@ -0,0 +1,62 @@ +package org.bitcoindevkit +import org.rustbitcoin.bitcoin.Network + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.launch +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 lightClient = LightClientBuilder() + .peers(peers) + .connections(1u) + .dataDir(persistenceFilePath.toString()) + .build(wallet) + val client = lightClient.client + val node = lightClient.node + println("Node running") + val logJob = launch { + while (true) { + val logMsg = client.nextLog() + println("$logMsg") + } + } + node.run() + val updateOpt: Update? = client.update() + 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." + } + logJob.cancelAndJoin() + 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..9bfb9c13 --- /dev/null +++ b/bdk-python/tests/test_live_kyoto.py @@ -0,0 +1,62 @@ +from bdkpython import * +from bdkpython.bitcoin import * +import unittest +import os +import asyncio + +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] + light_client: LightClient = LightClientBuilder().peers(peers).connections(1).build(wallet) + client: Client = light_client.client + node: LightNode = light_client.node + node.run() + async def log_loop(): + while True: + log = await client.next_log() + print(log) + + log_task = asyncio.create_task(log_loop()) + update: Update = await client.update() + 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." + ) + log_task.cancel() + 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..c4a39835 --- /dev/null +++ b/bdk-swift/Tests/BitcoinDevKitTests/LiveKyotoTests.swift @@ -0,0 +1,61 @@ +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 trustedPeer = Peer(address: peer, port: nil, v2Transport: false) + let lightClient = try LightClientBuilder() + .peers(peers: [trustedPeer]) + .connections(connections: 1) + .dataDir(dataDir: cwd) + .build(wallet: wallet) + let client = lightClient.client + let node = lightClient.node + node.run() + Task { + while true { + let log = await client.nextLog() + print("\(log)") + } + } + let update = await client.update() + 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") + try await client.shutdown() + } 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