From 6e4a03f1089fe1666c1acd572edea3ea3608f456 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 11 Mar 2026 11:59:45 -0500 Subject: [PATCH 1/5] chore: align bdk-ffi with bdk_wallet 3.0.0-rc.1 --- bdk-ffi/Cargo.lock | 8 +- bdk-ffi/Cargo.toml | 6 +- bdk-ffi/src/descriptor.rs | 31 +-- bdk-ffi/src/error.rs | 2 +- bdk-ffi/src/keys.rs | 2 +- bdk-ffi/src/types.rs | 3 +- bdk-ffi/src/wallet.rs | 7 - vendor/bdk_kyoto/.github/workflows/ci.yml | 36 +++ vendor/bdk_kyoto/Cargo.toml | 35 +++ vendor/bdk_kyoto/LICENSE | 7 + vendor/bdk_kyoto/LICENSE-APACHE | 201 ++++++++++++++++ vendor/bdk_kyoto/LICENSE-MIT | 16 ++ vendor/bdk_kyoto/README.md | 19 ++ vendor/bdk_kyoto/src/builder.rs | 152 ++++++++++++ vendor/bdk_kyoto/src/lib.rs | 267 ++++++++++++++++++++++ 15 files changed, 761 insertions(+), 31 deletions(-) create mode 100644 vendor/bdk_kyoto/.github/workflows/ci.yml create mode 100644 vendor/bdk_kyoto/Cargo.toml create mode 100644 vendor/bdk_kyoto/LICENSE create mode 100644 vendor/bdk_kyoto/LICENSE-APACHE create mode 100644 vendor/bdk_kyoto/LICENSE-MIT create mode 100644 vendor/bdk_kyoto/README.md create mode 100644 vendor/bdk_kyoto/src/builder.rs create mode 100644 vendor/bdk_kyoto/src/lib.rs diff --git a/bdk-ffi/Cargo.lock b/bdk-ffi/Cargo.lock index 1c534061..f5b8114b 100644 --- a/bdk-ffi/Cargo.lock +++ b/bdk-ffi/Cargo.lock @@ -119,7 +119,7 @@ dependencies = [ [[package]] name = "bdk-ffi" -version = "2.3.0-alpha.0" +version = "3.0.0-rc.1" dependencies = [ "assert_matches", "bdk_electrum", @@ -177,8 +177,6 @@ dependencies = [ [[package]] name = "bdk_kyoto" version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f35b9f8b3aa8c4647bec7a92b050c496742d955e0ac1edcb4e7c2deabf63c54" dependencies = [ "bdk_wallet", "bip157", @@ -186,9 +184,9 @@ dependencies = [ [[package]] name = "bdk_wallet" -version = "2.3.0" +version = "3.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03f1e31ccc562f600981f747d2262b84428cbff52c9c9cdf14d15fb15bd2286" +checksum = "70031c96c223c780525f4b9c52813e4f5bd8000a5712aac3248d00838af1e436" dependencies = [ "bdk_chain", "bip39", diff --git a/bdk-ffi/Cargo.toml b/bdk-ffi/Cargo.toml index 662ffe67..017033f5 100644 --- a/bdk-ffi/Cargo.toml +++ b/bdk-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bdk-ffi" -version = "2.4.0-alpha.0" +version = "3.0.0-rc.1" homepage = "https://bitcoindevkit.org" repository = "https://github.com/bitcoindevkit/bdk" edition = "2018" @@ -15,10 +15,10 @@ name = "uniffi-bindgen" path = "uniffi-bindgen.rs" [dependencies] -bdk_wallet = { version = "2.3.0", features = ["all-keys", "keys-bip39", "rusqlite"] } +bdk_wallet = { version = "=3.0.0-rc.1", features = ["all-keys", "keys-bip39", "rusqlite"] } bdk_esplora = { version = "0.22.1", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] } bdk_electrum = { version = "0.23.2", default-features = false, features = ["use-rustls-ring"] } -bdk_kyoto = { version = "0.15.4" } +bdk_kyoto = { path = "../vendor/bdk_kyoto" } uniffi = { version = "=0.30.0", features = ["cli"]} thiserror = "2.0.17" diff --git a/bdk-ffi/src/descriptor.rs b/bdk-ffi/src/descriptor.rs index db705391..f4e84f89 100644 --- a/bdk-ffi/src/descriptor.rs +++ b/bdk-ffi/src/descriptor.rs @@ -38,7 +38,8 @@ impl Descriptor { #[uniffi::constructor] pub fn new(descriptor: String, network: Network) -> Result { let secp = Secp256k1::new(); - let (extended_descriptor, key_map) = descriptor.into_wallet_descriptor(&secp, network)?; + let (extended_descriptor, key_map) = + descriptor.into_wallet_descriptor(&secp, network.into())?; Ok(Self { extended_descriptor, key_map, @@ -60,8 +61,9 @@ impl Descriptor { } BdkDescriptorSecretKey::XPrv(descriptor_x_key) => { let derivable_key = descriptor_x_key.xkey; - let (extended_descriptor, key_map, _) = - Bip44(derivable_key, keychain_kind).build(network).unwrap(); + let (extended_descriptor, key_map, _) = Bip44(derivable_key, keychain_kind) + .build(network.into()) + .unwrap(); Self { extended_descriptor, key_map, @@ -96,7 +98,7 @@ impl Descriptor { let derivable_key = descriptor_x_key.xkey; let (extended_descriptor, key_map, _) = Bip44Public(derivable_key, fingerprint, keychain_kind) - .build(network) + .build(network.into()) .map_err(DescriptorError::from)?; Ok(Self { @@ -125,8 +127,9 @@ impl Descriptor { } BdkDescriptorSecretKey::XPrv(descriptor_x_key) => { let derivable_key = descriptor_x_key.xkey; - let (extended_descriptor, key_map, _) = - Bip49(derivable_key, keychain_kind).build(network).unwrap(); + let (extended_descriptor, key_map, _) = Bip49(derivable_key, keychain_kind) + .build(network.into()) + .unwrap(); Self { extended_descriptor, key_map, @@ -161,7 +164,7 @@ impl Descriptor { let derivable_key = descriptor_x_key.xkey; let (extended_descriptor, key_map, _) = Bip49Public(derivable_key, fingerprint, keychain_kind) - .build(network) + .build(network.into()) .map_err(DescriptorError::from)?; Ok(Self { @@ -190,8 +193,9 @@ impl Descriptor { } BdkDescriptorSecretKey::XPrv(descriptor_x_key) => { let derivable_key = descriptor_x_key.xkey; - let (extended_descriptor, key_map, _) = - Bip84(derivable_key, keychain_kind).build(network).unwrap(); + let (extended_descriptor, key_map, _) = Bip84(derivable_key, keychain_kind) + .build(network.into()) + .unwrap(); Self { extended_descriptor, key_map, @@ -226,7 +230,7 @@ impl Descriptor { let derivable_key = descriptor_x_key.xkey; let (extended_descriptor, key_map, _) = Bip84Public(derivable_key, fingerprint, keychain_kind) - .build(network) + .build(network.into()) .map_err(DescriptorError::from)?; Ok(Self { @@ -255,8 +259,9 @@ impl Descriptor { } BdkDescriptorSecretKey::XPrv(descriptor_x_key) => { let derivable_key = descriptor_x_key.xkey; - let (extended_descriptor, key_map, _) = - Bip86(derivable_key, keychain_kind).build(network).unwrap(); + let (extended_descriptor, key_map, _) = Bip86(derivable_key, keychain_kind) + .build(network.into()) + .unwrap(); Self { extended_descriptor, key_map, @@ -291,7 +296,7 @@ impl Descriptor { let derivable_key = descriptor_x_key.xkey; let (extended_descriptor, key_map, _) = Bip86Public(derivable_key, fingerprint, keychain_kind) - .build(network) + .build(network.into()) .map_err(DescriptorError::from)?; Ok(Self { diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 10b6d8b1..f64a614b 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -1561,7 +1561,7 @@ impl From for SignerError { BdkSignerError::MissingKey => SignerError::MissingKey, BdkSignerError::InvalidKey => SignerError::InvalidKey, BdkSignerError::UserCanceled => SignerError::UserCanceled, - BdkSignerError::InputIndexOutOfRange => SignerError::InputIndexOutOfRange, + BdkSignerError::InputIndexOutOfRange(_) => SignerError::InputIndexOutOfRange, BdkSignerError::MissingNonWitnessUtxo => SignerError::MissingNonWitnessUtxo, BdkSignerError::InvalidNonWitnessUtxo => SignerError::InvalidNonWitnessUtxo, BdkSignerError::MissingWitnessUtxo => SignerError::MissingWitnessUtxo, diff --git a/bdk-ffi/src/keys.rs b/bdk-ffi/src/keys.rs index 34bdf0b1..0cc904a3 100644 --- a/bdk-ffi/src/keys.rs +++ b/bdk-ffi/src/keys.rs @@ -147,7 +147,7 @@ impl DescriptorSecretKey { let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap(); let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey { origin: None, - xkey: xkey.into_xprv(network).unwrap(), + xkey: xkey.into_xprv(network.into()).unwrap(), derivation_path: BdkDerivationPath::master(), wildcard: Wildcard::Unhardened, }); diff --git a/bdk-ffi/src/types.rs b/bdk-ffi/src/types.rs index f432988a..903f2917 100644 --- a/bdk-ffi/src/types.rs +++ b/bdk-ffi/src/types.rs @@ -23,13 +23,13 @@ use bdk_wallet::descriptor::policy::{ Condition as BdkCondition, PkOrF as BdkPkOrF, Policy as BdkPolicy, Satisfaction as BdkSatisfaction, SatisfiableItem as BdkSatisfiableItem, }; -use bdk_wallet::event::WalletEvent as BdkWalletEvent; #[allow(deprecated)] use bdk_wallet::signer::{SignOptions as BdkSignOptions, TapLeavesOptions}; use bdk_wallet::AddressInfo as BdkAddressInfo; use bdk_wallet::Balance as BdkBalance; use bdk_wallet::LocalOutput as BdkLocalOutput; use bdk_wallet::Update as BdkUpdate; +use bdk_wallet::WalletEvent as BdkWalletEvent; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::convert::TryFrom; @@ -1314,6 +1314,7 @@ impl From for bdk_wallet::ChangeSet { local_chain, tx_graph, indexer, + locked_outpoints: Default::default(), } } } diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 47da506c..17b01319 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -205,13 +205,6 @@ impl Wallet { }) } - /// Informs the wallet that you no longer intend to broadcast a tx that was built from it. - /// - /// This frees up the change address used when creating the tx for use in future transactions. - pub fn cancel_tx(&self, tx: &Transaction) { - self.get_wallet().cancel_tx(&tx.into()) - } - /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the /// wallet's database. pub fn get_utxo(&self, op: OutPoint) -> Option { diff --git a/vendor/bdk_kyoto/.github/workflows/ci.yml b/vendor/bdk_kyoto/.github/workflows/ci.yml new file mode 100644 index 00000000..f37a1ea7 --- /dev/null +++ b/vendor/bdk_kyoto/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: Build & Test + +on: + push: + branches: + - master + pull_request: + +jobs: + check: + strategy: + matrix: + toolchain: [stable, beta, nightly] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + components: clippy,rustfmt + - run: just check + integration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: just test integration + msrv: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: just test msrv diff --git a/vendor/bdk_kyoto/Cargo.toml b/vendor/bdk_kyoto/Cargo.toml new file mode 100644 index 00000000..e8755b23 --- /dev/null +++ b/vendor/bdk_kyoto/Cargo.toml @@ -0,0 +1,35 @@ +[package] +edition = "2021" +rust-version = "1.84.0" +name = "bdk_kyoto" +version = "0.15.4" +authors = [ + "Rob rob@2140.dev", + "Bitcoin Dev Kit Developers", +] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "BDK blockchain integration using P2P light client Kyoto" +documentation = "https://docs.rs/bdk-kyoto" +readme = "README.md" +keywords = [ + "bitcoin", + "peer-to-peer", + "light-client", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/bitcoindevkit/bdk-kyoto" + +[lib] +name = "bdk_kyoto" +path = "src/lib.rs" + +[dependencies.bdk_wallet] +version = "3.0.0-rc.1" + +[dependencies.bip157] +version = "0.3.4" diff --git a/vendor/bdk_kyoto/LICENSE b/vendor/bdk_kyoto/LICENSE new file mode 100644 index 00000000..313ad056 --- /dev/null +++ b/vendor/bdk_kyoto/LICENSE @@ -0,0 +1,7 @@ +This software is licensed under Apache 2.0 or MIT, at your option. + +Some files retain their own copyright notice, however, for full authorship information, see version control history. + +Except as otherwise noted in individual files, all files in this repository are licensed under the Apache License, Version 2.0 or the MIT license , at your option. + +You may not use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software or any files in this repository except in accordance with one or both of these licenses. \ No newline at end of file diff --git a/vendor/bdk_kyoto/LICENSE-APACHE b/vendor/bdk_kyoto/LICENSE-APACHE new file mode 100644 index 00000000..f49a4e16 --- /dev/null +++ b/vendor/bdk_kyoto/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/vendor/bdk_kyoto/LICENSE-MIT b/vendor/bdk_kyoto/LICENSE-MIT new file mode 100644 index 00000000..f8da085d --- /dev/null +++ b/vendor/bdk_kyoto/LICENSE-MIT @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/bdk_kyoto/README.md b/vendor/bdk_kyoto/README.md new file mode 100644 index 00000000..f4d62499 --- /dev/null +++ b/vendor/bdk_kyoto/README.md @@ -0,0 +1,19 @@ +# BDK Kyoto + +BDK-Kyoto is an extension of [Kyoto](https://github.com/2140-dev/kyoto), a client-side implementation of BIP157/BIP158. +These proposals define a way for users to fetch transactions privately, using _compact block filters_. +You may want to read the specification [here](https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki). +Kyoto runs as a psuedo-node, sending messages over the Bitcoin peer-to-peer layer, finding new peers to connect to, and managing a +light-weight database of Bitcoin block headers. As such, developing a wallet application using this crate is distinct from a typical +client/server relationship. Esplora and Electrum offer _proactive_ APIs, in that the servers will respond to events as they are requested. + +In the case of running a node as a background process, the developer experience is far more _reactive_, in that the node may emit any number of events, and the application may respond to them. BDK-Kyoto curates these events into structures that are easily handled by BDK APIs, making integration of compact block filters easily understood. + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/vendor/bdk_kyoto/src/builder.rs b/vendor/bdk_kyoto/src/builder.rs new file mode 100644 index 00000000..dcbe85f8 --- /dev/null +++ b/vendor/bdk_kyoto/src/builder.rs @@ -0,0 +1,152 @@ +//! Construct a [`LightClient`] by using a reference to a [`Wallet`]. +//! +//! ## Details +//! +//! The node has a number of configurations. Notably, the height of in the blockchain to start a +//! wallet recovery and the nodes on the peer-to-peer network are both configurable. +//! +//! ```no_run +//! # const RECEIVE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/0/*)"; +//! # const CHANGE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/1/*)"; +//! use std::net::{IpAddr, Ipv4Addr}; +//! use std::path::PathBuf; +//! use std::time::Duration; +//! use bdk_wallet::Wallet; +//! use bdk_kyoto::bip157::{Network, TrustedPeer}; +//! use bdk_kyoto::builder::{Builder, BuilderExt}; +//! use bdk_kyoto::{LightClient, ScanType}; +//! +//! #[tokio::main] +//! async fn main() -> anyhow::Result<()> { +//! // Add specific peers to connect to. +//! let peer = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); +//! let trusted = TrustedPeer::from_ip(peer); +//! +//! let db_path = ".".parse::()?; +//! +//! let mut wallet = Wallet::create(RECEIVE, CHANGE) +//! .network(Network::Signet) +//! .create_wallet_no_persist()?; +//! +//! let scan_type = ScanType::Sync; +//! +//! let LightClient { +//! requester, +//! info_subscriber, +//! warning_subscriber, +//! update_subscriber, +//! node +//! } = Builder::new(Network::Signet) +//! // A node may handle multiple connections +//! .required_peers(2) +//! // Choose where to store node data +//! .data_dir(db_path) +//! // How long peers have to respond messages +//! .response_timeout(Duration::from_secs(2)) +//! // Added trusted peers to initialize the sync +//! .add_peer(trusted) +//! .build_with_wallet(&wallet, scan_type)?; +//! Ok(()) +//! } +//! ``` + +use std::fmt::Display; + +use bdk_wallet::{ + chain::{CheckPoint, IndexedTxGraph}, + Wallet, +}; +pub use bip157::Builder; +use bip157::{chain::ChainState, HeaderCheckpoint}; + +use crate::{LightClient, ScanType, UpdateSubscriber}; + +const IMPOSSIBLE_REORG_DEPTH: usize = 7; + +/// Build a compact block filter client and node for a specified wallet +pub trait BuilderExt { + /// Attempt to build the node with scripts from a [`Wallet`] and following a [`ScanType`]. + fn build_with_wallet( + self, + wallet: &Wallet, + scan_type: ScanType, + ) -> Result; +} + +impl BuilderExt for Builder { + fn build_with_wallet( + mut self, + wallet: &Wallet, + scan_type: ScanType, + ) -> Result { + let network = wallet.network(); + if self.network().ne(&network) { + return Err(BuilderError::NetworkMismatch); + } + match scan_type { + ScanType::Sync => { + let current_cp = wallet.latest_checkpoint(); + let sync_start = walk_back_max_reorg(current_cp); + self = self.chain_state(ChainState::Checkpoint(sync_start)); + } + ScanType::Recovery { + used_script_index: _, + checkpoint, + } => self = self.chain_state(ChainState::Checkpoint(checkpoint)), + } + let (node, client) = self.build(); + let bip157::Client { + requester, + info_rx, + warn_rx, + event_rx, + } = client; + let indexed_graph = IndexedTxGraph::new(wallet.spk_index().clone()); + let update_subscriber = UpdateSubscriber::new( + requester.clone(), + scan_type, + event_rx, + wallet.latest_checkpoint(), + indexed_graph, + ); + Ok(LightClient { + requester, + info_subscriber: info_rx, + warning_subscriber: warn_rx, + update_subscriber, + node, + }) + } +} + +/// Walk back 7 blocks in case the last sync was an orphan block. +fn walk_back_max_reorg(checkpoint: CheckPoint) -> HeaderCheckpoint { + let mut ret_cp = HeaderCheckpoint::new(checkpoint.height(), checkpoint.hash()); + let cp_iter = checkpoint.iter(); + for (index, next) in cp_iter.enumerate() { + if index > IMPOSSIBLE_REORG_DEPTH { + return ret_cp; + } + ret_cp = HeaderCheckpoint::new(next.height(), next.hash()); + } + ret_cp +} + +/// Errors the builder may throw. +#[derive(Debug)] +pub enum BuilderError { + /// The wallet network and node network do not match. + NetworkMismatch, +} + +impl Display for BuilderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BuilderError::NetworkMismatch => { + write!(f, "wallet network and node network do not match") + } + } + } +} + +impl std::error::Error for BuilderError {} diff --git a/vendor/bdk_kyoto/src/lib.rs b/vendor/bdk_kyoto/src/lib.rs new file mode 100644 index 00000000..2721a996 --- /dev/null +++ b/vendor/bdk_kyoto/src/lib.rs @@ -0,0 +1,267 @@ +#![doc = include_str!("../README.md")] +//! ## Examples +//! +//! If you have an existing project that leverages `bdk_wallet`, building the compact block filter +//! _node_ and _client_ is simple. You may construct and configure a node to integrate with your +//! wallet by using the [`BuilderExt`](crate::builder) and [`Builder`](crate::builder). +//! +//! ```no_run +//! # const RECEIVE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/0/*)"; +//! # const CHANGE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/1/*)"; +//! use bdk_wallet::Wallet; +//! use bdk_wallet::bitcoin::Network; +//! use bdk_kyoto::builder::{Builder, BuilderExt}; +//! use bdk_kyoto::{LightClient, ScanType}; +//! +//! #[tokio::main] +//! async fn main() -> anyhow::Result<()> { +//! let mut wallet = Wallet::create(RECEIVE, CHANGE) +//! .network(Network::Signet) +//! .create_wallet_no_persist()?; +//! +//! let LightClient { +//! requester, +//! info_subscriber: _, +//! warning_subscriber: _, +//! mut update_subscriber, +//! node +//! } = Builder::new(Network::Signet).build_with_wallet(&wallet, ScanType::Sync)?; +//! +//! tokio::task::spawn(async move { node.run().await }); +//! +//! loop { +//! let update = update_subscriber.update().await?; +//! wallet.apply_update(update)?; +//! return Ok(()); +//! } +//! } +//! ``` + +#![warn(missing_docs)] +use std::collections::HashSet; + +use bdk_wallet::chain::BlockId; +use bdk_wallet::chain::CheckPoint; +pub use bdk_wallet::Update; + +use bdk_wallet::chain::{keychain_txout::KeychainTxOutIndex, IndexedTxGraph}; +use bdk_wallet::chain::{ConfirmationBlockTime, TxUpdate}; +use bdk_wallet::KeychainKind; + +pub extern crate bip157; + +use bip157::chain::BlockHeaderChanges; +use bip157::ScriptBuf; +#[doc(inline)] +pub use bip157::{ + BlockHash, ClientError, FeeRate, HeaderCheckpoint, Info, Node, RejectPayload, RejectReason, + Requester, TrustedPeer, TxBroadcast, TxBroadcastPolicy, Warning, Wtxid, +}; +use bip157::{Event, SyncUpdate}; + +#[doc(inline)] +pub use bip157::Receiver; +#[doc(inline)] +pub use bip157::UnboundedReceiver; + +#[doc(inline)] +pub use builder::BuilderExt; +pub mod builder; + +#[derive(Debug)] +/// A node and associated structs to send and receive events to and from the node. +pub struct LightClient { + /// Send events to a running node (i.e. broadcast a transaction). + pub requester: Requester, + /// Receive informational messages as the node runs. + pub info_subscriber: Receiver, + /// Receive warnings from the node as it runs. + pub warning_subscriber: UnboundedReceiver, + /// Receive wallet updates from a node. + pub update_subscriber: UpdateSubscriber, + /// The underlying node that must be run to fetch blocks from peers. + pub node: Node, +} + +/// Interpret events from a node that is running to apply +/// updates to an underlying wallet. +#[derive(Debug)] +pub struct UpdateSubscriber { + // request information from the client + requester: Requester, + // channel receiver + receiver: UnboundedReceiver, + // changes to local chain + cp: CheckPoint, + // receive graph + graph: IndexedTxGraph>, + // queued blocks to fetch + queued_blocks: Vec, + // queued scripts to check filters + spk_cache: HashSet, +} + +impl UpdateSubscriber { + fn new( + requester: Requester, + scan_type: ScanType, + receiver: UnboundedReceiver, + cp: CheckPoint, + graph: IndexedTxGraph>, + ) -> Self { + let spk_cache = match scan_type { + ScanType::Sync => Self::peek_scripts(&graph.index, graph.index.lookahead()), + ScanType::Recovery { + used_script_index, + checkpoint: _, + } => Self::peek_scripts(&graph.index, used_script_index), + }; + Self { + requester, + receiver, + cp, + graph, + queued_blocks: Vec::new(), + spk_cache, + } + } + /// Return the most recent update from the node once it has synced to the network's tip. + /// This may take a significant portion of time during wallet recoveries or dormant wallets. + /// Note that you may call this method in a loop as long as the node is running. + /// + /// **Warning** + /// + /// This method is _not_ cancel safe. You cannot use it within a `tokio::select` arm. + pub async fn update(&mut self) -> Result { + let mut cp = self.cp.clone(); + while let Some(message) = self.receiver.recv().await { + match message { + Event::IndexedFilter(filter) => { + let block_hash = filter.block_hash(); + if filter.contains_any(self.spk_cache.iter()) { + self.queued_blocks.push(block_hash); + } + } + // these are emitted for every block + Event::ChainUpdate(BlockHeaderChanges::Connected(at)) => { + let block_id = BlockId { + hash: at.block_hash(), + height: at.height, + }; + cp = cp.insert(block_id); + } + Event::ChainUpdate(BlockHeaderChanges::Reorganized { + accepted, + reorganized: _, + }) => { + for header in accepted { + let block_id = BlockId { + hash: header.block_hash(), + height: header.height, + }; + cp = cp.insert(block_id); + } + } + Event::FiltersSynced(SyncUpdate { + tip: _, + recent_history: _, + }) => { + for hash in core::mem::take(&mut self.queued_blocks) { + let block = self + .requester + .get_block(hash) + .await + .map_err(|_| UpdateError::NodeStopped)?; + let height = block.height; + let block = block.block; + let _ = self.graph.apply_block_relevant(&block, height); + } + self.cp = cp; + self.spk_cache.extend(Self::peek_scripts( + &self.graph.index, + self.graph.index.lookahead(), + )); + return Ok(self.get_scan_response()); + } + _ => (), + } + } + Err(UpdateError::NodeStopped) + } + + // When the client is believed to have synced to the chain tip of most work, + // we can return a wallet update. + fn get_scan_response(&mut self) -> Update { + let tx_update = TxUpdate::from(self.graph.graph().clone()); + let graph = core::mem::take(&mut self.graph); + let last_active_indices = graph.index.last_used_indices(); + self.graph = IndexedTxGraph::new(graph.index); + Update { + tx_update, + last_active_indices, + chain: Some(self.cp.clone()), + } + } + + fn peek_scripts( + keychain: &KeychainTxOutIndex, + to_index: u32, + ) -> HashSet { + let mut spk_cache = HashSet::new(); + // We pre-compute an SPK cache so as to not call `unbounded_spk_iter` for each filter + let last_revealed = keychain.last_revealed_indices(); + let ext_index = last_revealed + .get(&KeychainKind::External) + .copied() + .unwrap_or(0); + let unbounded_ext_spk_iter = keychain + .unbounded_spk_iter(KeychainKind::External) + .expect("wallet must have external keychain"); + let bound = (ext_index + to_index) as usize; + let bounded_ext_iter = unbounded_ext_spk_iter.take(bound).map(|(_, script)| script); + spk_cache.extend(bounded_ext_iter); + let int_index = last_revealed + .get(&KeychainKind::Internal) + .copied() + .unwrap_or(0); + let unbounded_int_spk_iter = keychain.unbounded_spk_iter(KeychainKind::Internal); + if let Some(int_spk_iter) = unbounded_int_spk_iter { + let bound = (int_index + to_index) as usize; + let bounded_int_iter = int_spk_iter.take(bound).map(|(_, script)| script); + spk_cache.extend(bounded_int_iter); + } + spk_cache + } +} + +/// Errors encountered when attempting to construct a wallet update. +#[derive(Debug, Clone, Copy)] +pub enum UpdateError { + /// The node has stopped running. + NodeStopped, +} + +impl std::fmt::Display for UpdateError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UpdateError::NodeStopped => write!(f, "the node halted execution."), + } + } +} + +impl std::error::Error for UpdateError {} + +/// How to scan compact block filters on start up. +#[derive(Debug, Clone, Copy, Default)] +pub enum ScanType { + /// Sync the wallet from the last known wallet checkpoint to the rest of the network. + #[default] + Sync, + /// Recover an old wallet by scanning after the specified height. + Recovery { + /// The amount of scripts used by the wallet that is being recovered. + used_script_index: u32, + /// The height in the block chain to begin searching for transactions. + checkpoint: HeaderCheckpoint, + }, +} From 8c4248f56a315a49af7dd25b458d132d46af12f5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 11 Mar 2026 13:43:11 -0500 Subject: [PATCH 2/5] chore: replace vendored kyoto with kyoto rc1 --- bdk-ffi/Cargo.lock | 32 ++- bdk-ffi/Cargo.toml | 2 +- bdk-ffi/src/kyoto.rs | 24 +- vendor/bdk_kyoto/.github/workflows/ci.yml | 36 --- vendor/bdk_kyoto/Cargo.toml | 35 --- vendor/bdk_kyoto/LICENSE | 7 - vendor/bdk_kyoto/LICENSE-APACHE | 201 ---------------- vendor/bdk_kyoto/LICENSE-MIT | 16 -- vendor/bdk_kyoto/README.md | 19 -- vendor/bdk_kyoto/src/builder.rs | 152 ------------ vendor/bdk_kyoto/src/lib.rs | 267 ---------------------- 11 files changed, 41 insertions(+), 750 deletions(-) delete mode 100644 vendor/bdk_kyoto/.github/workflows/ci.yml delete mode 100644 vendor/bdk_kyoto/Cargo.toml delete mode 100644 vendor/bdk_kyoto/LICENSE delete mode 100644 vendor/bdk_kyoto/LICENSE-APACHE delete mode 100644 vendor/bdk_kyoto/LICENSE-MIT delete mode 100644 vendor/bdk_kyoto/README.md delete mode 100644 vendor/bdk_kyoto/src/builder.rs delete mode 100644 vendor/bdk_kyoto/src/lib.rs diff --git a/bdk-ffi/Cargo.lock b/bdk-ffi/Cargo.lock index f5b8114b..9dc68e4a 100644 --- a/bdk-ffi/Cargo.lock +++ b/bdk-ffi/Cargo.lock @@ -177,6 +177,7 @@ dependencies = [ [[package]] name = "bdk_kyoto" version = "0.15.4" +source = "git+https://github.com/reez/bdk-kyoto?branch=rc1#7303e1e2d2d944a7ba2b13bcc3d92c605b0f1afa" dependencies = [ "bdk_wallet", "bip157", @@ -205,13 +206,14 @@ checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bip157" -version = "0.3.4" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88df5c18baaea9be4219679afbd4fc26491606f89f6ecdaffcdcabd67635b07b" +checksum = "8af7987396c76633777e335345347395cd383b46bd95c1534691ab717859482e" dependencies = [ "bip324", "bitcoin", "bitcoin-address-book", + "bitcoin_hashes 0.20.0", "tokio", ] @@ -267,6 +269,15 @@ dependencies = [ "bitcoin", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d7ca3dc8ff835693ad73bf1596240c06f974a31eeb3f611aaedf855f1f2725" +dependencies = [ + "bitcoin-internals 0.5.0", +] + [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -282,6 +293,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90bbbfa552b49101a230fb2668f3f9ef968c81e6f83cf577e1d4b80f689e1aa" +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" + [[package]] name = "bitcoin-io" version = "0.1.4" @@ -328,6 +345,17 @@ dependencies = [ "hex-conservative 0.3.2", ] +[[package]] +name = "bitcoin_hashes" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a45c2b41c457a9a9e4670422fcbdf109afb3b22bc920b4045e8bdfd788a3d" +dependencies = [ + "bitcoin-consensus-encoding", + "bitcoin-internals 0.5.0", + "hex-conservative 0.3.2", +] + [[package]] name = "bitflags" version = "2.11.0" diff --git a/bdk-ffi/Cargo.toml b/bdk-ffi/Cargo.toml index 017033f5..05d5f714 100644 --- a/bdk-ffi/Cargo.toml +++ b/bdk-ffi/Cargo.toml @@ -18,7 +18,7 @@ path = "uniffi-bindgen.rs" bdk_wallet = { version = "=3.0.0-rc.1", features = ["all-keys", "keys-bip39", "rusqlite"] } bdk_esplora = { version = "0.22.1", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] } bdk_electrum = { version = "0.23.2", default-features = false, features = ["use-rustls-ring"] } -bdk_kyoto = { path = "../vendor/bdk_kyoto" } +bdk_kyoto = { git = "https://github.com/reez/bdk-kyoto", branch = "rc1" } uniffi = { version = "=0.30.0", features = ["cli"]} thiserror = "2.0.17" diff --git a/bdk-ffi/src/kyoto.rs b/bdk-ffi/src/kyoto.rs index 2323e94d..18c6cb05 100644 --- a/bdk-ffi/src/kyoto.rs +++ b/bdk-ffi/src/kyoto.rs @@ -7,7 +7,6 @@ use bdk_kyoto::bip157::ServiceFlags; use bdk_kyoto::builder::Builder as BDKCbfBuilder; use bdk_kyoto::builder::BuilderExt; use bdk_kyoto::HeaderCheckpoint; -use bdk_kyoto::LightClient as BDKLightClient; use bdk_kyoto::Receiver; use bdk_kyoto::RejectReason; use bdk_kyoto::Requester; @@ -16,7 +15,7 @@ use bdk_kyoto::UnboundedReceiver; use bdk_kyoto::UpdateSubscriber; use bdk_kyoto::Warning as Warn; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -238,18 +237,15 @@ impl CbfBuilder { if let Some(proxy) = &self.socks5_proxy { let port = proxy.port; let addr = proxy.address.inner; - builder = builder.socks5_proxy((addr, port)); + builder = builder.socks5_proxy(SocketAddr::new(addr, port)); } - let BDKLightClient { - requester, - info_subscriber, - warning_subscriber, - update_subscriber, - node, - } = builder + let (client, logging, update_subscriber) = builder .build_with_wallet(&wallet, scan_type) - .expect("networks match by definition"); + .expect("networks match by definition") + .subscribe(); + let (client, node) = client.managed_start(); + let requester = client.requester(); let node = CbfNode { node: std::sync::Mutex::new(Some(node)), @@ -257,8 +253,8 @@ impl CbfBuilder { let client = CbfClient { sender: Arc::new(requester), - info_rx: Mutex::new(info_subscriber), - warning_rx: Mutex::new(warning_subscriber), + info_rx: Mutex::new(logging.info_subscriber), + warning_rx: Mutex::new(logging.warning_subscriber), update_rx: Mutex::new(update_subscriber), }; @@ -308,7 +304,7 @@ impl CbfClient { pub async fn broadcast(&self, transaction: &Transaction) -> Result, CbfError> { let tx = transaction.into(); self.sender - .broadcast_random(tx) + .broadcast_tx(tx) .await .map_err(From::from) .map(|wtxid| Arc::new(Wtxid(wtxid))) diff --git a/vendor/bdk_kyoto/.github/workflows/ci.yml b/vendor/bdk_kyoto/.github/workflows/ci.yml deleted file mode 100644 index f37a1ea7..00000000 --- a/vendor/bdk_kyoto/.github/workflows/ci.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Build & Test - -on: - push: - branches: - - master - pull_request: - -jobs: - check: - strategy: - matrix: - toolchain: [stable, beta, nightly] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: extractions/setup-just@v3 - - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - components: clippy,rustfmt - - run: just check - integration: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: extractions/setup-just@v3 - - uses: actions-rust-lang/setup-rust-toolchain@v1 - - run: just test integration - msrv: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: extractions/setup-just@v3 - - uses: actions-rust-lang/setup-rust-toolchain@v1 - - run: just test msrv diff --git a/vendor/bdk_kyoto/Cargo.toml b/vendor/bdk_kyoto/Cargo.toml deleted file mode 100644 index e8755b23..00000000 --- a/vendor/bdk_kyoto/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -edition = "2021" -rust-version = "1.84.0" -name = "bdk_kyoto" -version = "0.15.4" -authors = [ - "Rob rob@2140.dev", - "Bitcoin Dev Kit Developers", -] -build = false -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "BDK blockchain integration using P2P light client Kyoto" -documentation = "https://docs.rs/bdk-kyoto" -readme = "README.md" -keywords = [ - "bitcoin", - "peer-to-peer", - "light-client", -] -license = "MIT OR Apache-2.0" -repository = "https://github.com/bitcoindevkit/bdk-kyoto" - -[lib] -name = "bdk_kyoto" -path = "src/lib.rs" - -[dependencies.bdk_wallet] -version = "3.0.0-rc.1" - -[dependencies.bip157] -version = "0.3.4" diff --git a/vendor/bdk_kyoto/LICENSE b/vendor/bdk_kyoto/LICENSE deleted file mode 100644 index 313ad056..00000000 --- a/vendor/bdk_kyoto/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -This software is licensed under Apache 2.0 or MIT, at your option. - -Some files retain their own copyright notice, however, for full authorship information, see version control history. - -Except as otherwise noted in individual files, all files in this repository are licensed under the Apache License, Version 2.0 or the MIT license , at your option. - -You may not use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software or any files in this repository except in accordance with one or both of these licenses. \ No newline at end of file diff --git a/vendor/bdk_kyoto/LICENSE-APACHE b/vendor/bdk_kyoto/LICENSE-APACHE deleted file mode 100644 index f49a4e16..00000000 --- a/vendor/bdk_kyoto/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/vendor/bdk_kyoto/LICENSE-MIT b/vendor/bdk_kyoto/LICENSE-MIT deleted file mode 100644 index f8da085d..00000000 --- a/vendor/bdk_kyoto/LICENSE-MIT +++ /dev/null @@ -1,16 +0,0 @@ -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/bdk_kyoto/README.md b/vendor/bdk_kyoto/README.md deleted file mode 100644 index f4d62499..00000000 --- a/vendor/bdk_kyoto/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# BDK Kyoto - -BDK-Kyoto is an extension of [Kyoto](https://github.com/2140-dev/kyoto), a client-side implementation of BIP157/BIP158. -These proposals define a way for users to fetch transactions privately, using _compact block filters_. -You may want to read the specification [here](https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki). -Kyoto runs as a psuedo-node, sending messages over the Bitcoin peer-to-peer layer, finding new peers to connect to, and managing a -light-weight database of Bitcoin block headers. As such, developing a wallet application using this crate is distinct from a typical -client/server relationship. Esplora and Electrum offer _proactive_ APIs, in that the servers will respond to events as they are requested. - -In the case of running a node as a background process, the developer experience is far more _reactive_, in that the node may emit any number of events, and the application may respond to them. BDK-Kyoto curates these events into structures that are easily handled by BDK APIs, making integration of compact block filters easily understood. - -## License - -Licensed under either of - -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. diff --git a/vendor/bdk_kyoto/src/builder.rs b/vendor/bdk_kyoto/src/builder.rs deleted file mode 100644 index dcbe85f8..00000000 --- a/vendor/bdk_kyoto/src/builder.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Construct a [`LightClient`] by using a reference to a [`Wallet`]. -//! -//! ## Details -//! -//! The node has a number of configurations. Notably, the height of in the blockchain to start a -//! wallet recovery and the nodes on the peer-to-peer network are both configurable. -//! -//! ```no_run -//! # const RECEIVE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/0/*)"; -//! # const CHANGE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/1/*)"; -//! use std::net::{IpAddr, Ipv4Addr}; -//! use std::path::PathBuf; -//! use std::time::Duration; -//! use bdk_wallet::Wallet; -//! use bdk_kyoto::bip157::{Network, TrustedPeer}; -//! use bdk_kyoto::builder::{Builder, BuilderExt}; -//! use bdk_kyoto::{LightClient, ScanType}; -//! -//! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! // Add specific peers to connect to. -//! let peer = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); -//! let trusted = TrustedPeer::from_ip(peer); -//! -//! let db_path = ".".parse::()?; -//! -//! let mut wallet = Wallet::create(RECEIVE, CHANGE) -//! .network(Network::Signet) -//! .create_wallet_no_persist()?; -//! -//! let scan_type = ScanType::Sync; -//! -//! let LightClient { -//! requester, -//! info_subscriber, -//! warning_subscriber, -//! update_subscriber, -//! node -//! } = Builder::new(Network::Signet) -//! // A node may handle multiple connections -//! .required_peers(2) -//! // Choose where to store node data -//! .data_dir(db_path) -//! // How long peers have to respond messages -//! .response_timeout(Duration::from_secs(2)) -//! // Added trusted peers to initialize the sync -//! .add_peer(trusted) -//! .build_with_wallet(&wallet, scan_type)?; -//! Ok(()) -//! } -//! ``` - -use std::fmt::Display; - -use bdk_wallet::{ - chain::{CheckPoint, IndexedTxGraph}, - Wallet, -}; -pub use bip157::Builder; -use bip157::{chain::ChainState, HeaderCheckpoint}; - -use crate::{LightClient, ScanType, UpdateSubscriber}; - -const IMPOSSIBLE_REORG_DEPTH: usize = 7; - -/// Build a compact block filter client and node for a specified wallet -pub trait BuilderExt { - /// Attempt to build the node with scripts from a [`Wallet`] and following a [`ScanType`]. - fn build_with_wallet( - self, - wallet: &Wallet, - scan_type: ScanType, - ) -> Result; -} - -impl BuilderExt for Builder { - fn build_with_wallet( - mut self, - wallet: &Wallet, - scan_type: ScanType, - ) -> Result { - let network = wallet.network(); - if self.network().ne(&network) { - return Err(BuilderError::NetworkMismatch); - } - match scan_type { - ScanType::Sync => { - let current_cp = wallet.latest_checkpoint(); - let sync_start = walk_back_max_reorg(current_cp); - self = self.chain_state(ChainState::Checkpoint(sync_start)); - } - ScanType::Recovery { - used_script_index: _, - checkpoint, - } => self = self.chain_state(ChainState::Checkpoint(checkpoint)), - } - let (node, client) = self.build(); - let bip157::Client { - requester, - info_rx, - warn_rx, - event_rx, - } = client; - let indexed_graph = IndexedTxGraph::new(wallet.spk_index().clone()); - let update_subscriber = UpdateSubscriber::new( - requester.clone(), - scan_type, - event_rx, - wallet.latest_checkpoint(), - indexed_graph, - ); - Ok(LightClient { - requester, - info_subscriber: info_rx, - warning_subscriber: warn_rx, - update_subscriber, - node, - }) - } -} - -/// Walk back 7 blocks in case the last sync was an orphan block. -fn walk_back_max_reorg(checkpoint: CheckPoint) -> HeaderCheckpoint { - let mut ret_cp = HeaderCheckpoint::new(checkpoint.height(), checkpoint.hash()); - let cp_iter = checkpoint.iter(); - for (index, next) in cp_iter.enumerate() { - if index > IMPOSSIBLE_REORG_DEPTH { - return ret_cp; - } - ret_cp = HeaderCheckpoint::new(next.height(), next.hash()); - } - ret_cp -} - -/// Errors the builder may throw. -#[derive(Debug)] -pub enum BuilderError { - /// The wallet network and node network do not match. - NetworkMismatch, -} - -impl Display for BuilderError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BuilderError::NetworkMismatch => { - write!(f, "wallet network and node network do not match") - } - } - } -} - -impl std::error::Error for BuilderError {} diff --git a/vendor/bdk_kyoto/src/lib.rs b/vendor/bdk_kyoto/src/lib.rs deleted file mode 100644 index 2721a996..00000000 --- a/vendor/bdk_kyoto/src/lib.rs +++ /dev/null @@ -1,267 +0,0 @@ -#![doc = include_str!("../README.md")] -//! ## Examples -//! -//! If you have an existing project that leverages `bdk_wallet`, building the compact block filter -//! _node_ and _client_ is simple. You may construct and configure a node to integrate with your -//! wallet by using the [`BuilderExt`](crate::builder) and [`Builder`](crate::builder). -//! -//! ```no_run -//! # const RECEIVE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/0/*)"; -//! # const CHANGE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/1/*)"; -//! use bdk_wallet::Wallet; -//! use bdk_wallet::bitcoin::Network; -//! use bdk_kyoto::builder::{Builder, BuilderExt}; -//! use bdk_kyoto::{LightClient, ScanType}; -//! -//! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! let mut wallet = Wallet::create(RECEIVE, CHANGE) -//! .network(Network::Signet) -//! .create_wallet_no_persist()?; -//! -//! let LightClient { -//! requester, -//! info_subscriber: _, -//! warning_subscriber: _, -//! mut update_subscriber, -//! node -//! } = Builder::new(Network::Signet).build_with_wallet(&wallet, ScanType::Sync)?; -//! -//! tokio::task::spawn(async move { node.run().await }); -//! -//! loop { -//! let update = update_subscriber.update().await?; -//! wallet.apply_update(update)?; -//! return Ok(()); -//! } -//! } -//! ``` - -#![warn(missing_docs)] -use std::collections::HashSet; - -use bdk_wallet::chain::BlockId; -use bdk_wallet::chain::CheckPoint; -pub use bdk_wallet::Update; - -use bdk_wallet::chain::{keychain_txout::KeychainTxOutIndex, IndexedTxGraph}; -use bdk_wallet::chain::{ConfirmationBlockTime, TxUpdate}; -use bdk_wallet::KeychainKind; - -pub extern crate bip157; - -use bip157::chain::BlockHeaderChanges; -use bip157::ScriptBuf; -#[doc(inline)] -pub use bip157::{ - BlockHash, ClientError, FeeRate, HeaderCheckpoint, Info, Node, RejectPayload, RejectReason, - Requester, TrustedPeer, TxBroadcast, TxBroadcastPolicy, Warning, Wtxid, -}; -use bip157::{Event, SyncUpdate}; - -#[doc(inline)] -pub use bip157::Receiver; -#[doc(inline)] -pub use bip157::UnboundedReceiver; - -#[doc(inline)] -pub use builder::BuilderExt; -pub mod builder; - -#[derive(Debug)] -/// A node and associated structs to send and receive events to and from the node. -pub struct LightClient { - /// Send events to a running node (i.e. broadcast a transaction). - pub requester: Requester, - /// Receive informational messages as the node runs. - pub info_subscriber: Receiver, - /// Receive warnings from the node as it runs. - pub warning_subscriber: UnboundedReceiver, - /// Receive wallet updates from a node. - pub update_subscriber: UpdateSubscriber, - /// The underlying node that must be run to fetch blocks from peers. - pub node: Node, -} - -/// Interpret events from a node that is running to apply -/// updates to an underlying wallet. -#[derive(Debug)] -pub struct UpdateSubscriber { - // request information from the client - requester: Requester, - // channel receiver - receiver: UnboundedReceiver, - // changes to local chain - cp: CheckPoint, - // receive graph - graph: IndexedTxGraph>, - // queued blocks to fetch - queued_blocks: Vec, - // queued scripts to check filters - spk_cache: HashSet, -} - -impl UpdateSubscriber { - fn new( - requester: Requester, - scan_type: ScanType, - receiver: UnboundedReceiver, - cp: CheckPoint, - graph: IndexedTxGraph>, - ) -> Self { - let spk_cache = match scan_type { - ScanType::Sync => Self::peek_scripts(&graph.index, graph.index.lookahead()), - ScanType::Recovery { - used_script_index, - checkpoint: _, - } => Self::peek_scripts(&graph.index, used_script_index), - }; - Self { - requester, - receiver, - cp, - graph, - queued_blocks: Vec::new(), - spk_cache, - } - } - /// Return the most recent update from the node once it has synced to the network's tip. - /// This may take a significant portion of time during wallet recoveries or dormant wallets. - /// Note that you may call this method in a loop as long as the node is running. - /// - /// **Warning** - /// - /// This method is _not_ cancel safe. You cannot use it within a `tokio::select` arm. - pub async fn update(&mut self) -> Result { - let mut cp = self.cp.clone(); - while let Some(message) = self.receiver.recv().await { - match message { - Event::IndexedFilter(filter) => { - let block_hash = filter.block_hash(); - if filter.contains_any(self.spk_cache.iter()) { - self.queued_blocks.push(block_hash); - } - } - // these are emitted for every block - Event::ChainUpdate(BlockHeaderChanges::Connected(at)) => { - let block_id = BlockId { - hash: at.block_hash(), - height: at.height, - }; - cp = cp.insert(block_id); - } - Event::ChainUpdate(BlockHeaderChanges::Reorganized { - accepted, - reorganized: _, - }) => { - for header in accepted { - let block_id = BlockId { - hash: header.block_hash(), - height: header.height, - }; - cp = cp.insert(block_id); - } - } - Event::FiltersSynced(SyncUpdate { - tip: _, - recent_history: _, - }) => { - for hash in core::mem::take(&mut self.queued_blocks) { - let block = self - .requester - .get_block(hash) - .await - .map_err(|_| UpdateError::NodeStopped)?; - let height = block.height; - let block = block.block; - let _ = self.graph.apply_block_relevant(&block, height); - } - self.cp = cp; - self.spk_cache.extend(Self::peek_scripts( - &self.graph.index, - self.graph.index.lookahead(), - )); - return Ok(self.get_scan_response()); - } - _ => (), - } - } - Err(UpdateError::NodeStopped) - } - - // When the client is believed to have synced to the chain tip of most work, - // we can return a wallet update. - fn get_scan_response(&mut self) -> Update { - let tx_update = TxUpdate::from(self.graph.graph().clone()); - let graph = core::mem::take(&mut self.graph); - let last_active_indices = graph.index.last_used_indices(); - self.graph = IndexedTxGraph::new(graph.index); - Update { - tx_update, - last_active_indices, - chain: Some(self.cp.clone()), - } - } - - fn peek_scripts( - keychain: &KeychainTxOutIndex, - to_index: u32, - ) -> HashSet { - let mut spk_cache = HashSet::new(); - // We pre-compute an SPK cache so as to not call `unbounded_spk_iter` for each filter - let last_revealed = keychain.last_revealed_indices(); - let ext_index = last_revealed - .get(&KeychainKind::External) - .copied() - .unwrap_or(0); - let unbounded_ext_spk_iter = keychain - .unbounded_spk_iter(KeychainKind::External) - .expect("wallet must have external keychain"); - let bound = (ext_index + to_index) as usize; - let bounded_ext_iter = unbounded_ext_spk_iter.take(bound).map(|(_, script)| script); - spk_cache.extend(bounded_ext_iter); - let int_index = last_revealed - .get(&KeychainKind::Internal) - .copied() - .unwrap_or(0); - let unbounded_int_spk_iter = keychain.unbounded_spk_iter(KeychainKind::Internal); - if let Some(int_spk_iter) = unbounded_int_spk_iter { - let bound = (int_index + to_index) as usize; - let bounded_int_iter = int_spk_iter.take(bound).map(|(_, script)| script); - spk_cache.extend(bounded_int_iter); - } - spk_cache - } -} - -/// Errors encountered when attempting to construct a wallet update. -#[derive(Debug, Clone, Copy)] -pub enum UpdateError { - /// The node has stopped running. - NodeStopped, -} - -impl std::fmt::Display for UpdateError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UpdateError::NodeStopped => write!(f, "the node halted execution."), - } - } -} - -impl std::error::Error for UpdateError {} - -/// How to scan compact block filters on start up. -#[derive(Debug, Clone, Copy, Default)] -pub enum ScanType { - /// Sync the wallet from the last known wallet checkpoint to the rest of the network. - #[default] - Sync, - /// Recover an old wallet by scanning after the specified height. - Recovery { - /// The amount of scripts used by the wallet that is being recovered. - used_script_index: u32, - /// The height in the block chain to begin searching for transactions. - checkpoint: HeaderCheckpoint, - }, -} From 01e562082ba81025e284f3c92f639ec3725e5b49 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 19 Mar 2026 10:45:57 -0500 Subject: [PATCH 3/5] feat(wallet): expose apply_unconfirmed_txs_events --- bdk-ffi/src/wallet.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 17b01319..ea8b3a75 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -341,6 +341,29 @@ impl Wallet { ) } + /// Apply relevant unconfirmed transactions to the wallet and returns events. + /// + /// See [`apply_unconfirmed_txs`] for more information. + /// + /// See [`apply_update_events`] for more information on the returned [`WalletEvent`]s. + /// + /// [`apply_unconfirmed_txs`]: Self::apply_unconfirmed_txs + /// [`apply_update_events`]: Self::apply_update_events + pub fn apply_unconfirmed_txs_events( + &self, + unconfirmed_txs: Vec, + ) -> Vec { + self.get_wallet() + .apply_unconfirmed_txs_events( + unconfirmed_txs + .into_iter() + .map(|utx| (Arc::new(utx.tx.as_ref().into()), utx.last_seen)), + ) + .into_iter() + .map(|event| event.into()) + .collect() + } + /// Apply transactions that have been evicted from the mempool. /// Transactions may be evicted for paying too-low fee, or for being malformed. /// Irrelevant transactions are ignored. From ce0e4baa55cb2acddeda6b24ff3d5f56f42a6767 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 19 Mar 2026 10:50:35 -0500 Subject: [PATCH 4/5] feat(wallet): expose apply_evicted_txs_events --- bdk-ffi/src/wallet.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index ea8b3a75..d0b7ae7a 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -377,6 +377,27 @@ impl Wallet { ); } + /// Apply evictions of the given transaction IDs with their associated timestamps and returns + /// events. + /// + /// See [`apply_evicted_txs`] for more information. + /// + /// See [`apply_update_events`] for more information on the returned [`WalletEvent`]s. + /// + /// [`apply_evicted_txs`]: Self::apply_evicted_txs + /// [`apply_update_events`]: Self::apply_update_events + pub fn apply_evicted_txs_events(&self, evicted_txs: Vec) -> Vec { + self.get_wallet() + .apply_evicted_txs_events( + evicted_txs + .into_iter() + .map(|etx| (etx.txid.0, etx.evicted_at)), + ) + .into_iter() + .map(|event| event.into()) + .collect() + } + /// The derivation index of this wallet. It will return `None` if it has not derived any addresses. /// Otherwise, it will return the index of the highest address it has derived. pub fn derivation_index(&self, keychain: KeychainKind) -> Option { From f38f63013a750512e45b853907439826f576f0b5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 20 Mar 2026 10:48:03 -0500 Subject: [PATCH 5/5] feat(wallet): expose checkpoints --- bdk-ffi/src/wallet.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index d0b7ae7a..0140901d 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -642,6 +642,14 @@ impl Wallet { self.get_wallet().latest_checkpoint().block_id().into() } + /// Get all the checkpoints the wallet is currently storing indexed by height. + pub fn checkpoints(&self) -> Vec { + self.get_wallet() + .checkpoints() + .map(|checkpoint| checkpoint.block_id().into()) + .collect() + } + /// Get the [`TxDetails`] of a wallet transaction. pub fn tx_details(&self, txid: Arc) -> Option { self.get_wallet()