Compare commits

...

26 Commits

Author SHA1 Message Date
志宇
0a2a57060b Merge bitcoindevkit/bdk#1282: Bump version to 1.0.0-alpha.4
d33acc1466 Bump version to 1.0.0-alpha.4 (Steve Myers)

Pull request description:

  ### Description

  Bump version to 1.0.0-alpha.4, also:

  bdk_chain to 0.8.0
  bdk_bitcoind_rpc to 0.3.0
  bdk_electrum to 0.6.0
  bdk_esplora to 0.6.0
  bdk_file_store to 0.4.0

  Fixes #1281.

  ### Notes to the reviewers

  Since the version of bdk_chain changed all crates that depend on it also need to have their versions bumped.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  evanlinjin:
    ACK d33acc1466

Tree-SHA512: ffb5c05b45e9d646ef796bd894353e4b58c3aae3dcf0921dfb9fcae45020ed4fc82274a7497494d6259bfffff9c2f97c530d2cbe6ace578babd04852af461f3a
2024-01-18 19:22:56 +08:00
Steve Myers
d33acc1466 Bump version to 1.0.0-alpha.4
bdk_chain to 0.8.0
bdk_bitcoind_rpc to 0.3.0
bdk_electrum to 0.6.0
bdk_esplora to 0.6.0
bdk_file_store to 0.4.0
2024-01-18 19:07:37 +08:00
Daniela Brozzoni
d1ea0ef3d1 Merge bitcoindevkit/bdk#1286: doc, example(bdk): fix derivation path in mnemonic_to_descriptors
d494f63d08 doc, example(bdk): fix derivation path in mnemonic_to_descriptors (vmammal)

Pull request description:

  This will help avoid confusion with mainnet descriptors.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  danielabrozzoni:
    ACK d494f63d08

Tree-SHA512: f0450d171bc53085204280495f2954e20f4b64a73cfbcc9a0c3be131c0b04ad53e936c83fa83c89fbd1ed1c4edd680e8437550453f75056049d36f84cae1df43
2024-01-18 12:05:33 +01:00
Daniela Brozzoni
60abd87a32 Merge bitcoindevkit/bdk#1269: Revamp KeychainTxOutIndex API to be safer
71fff1613d feat(chain): add txout methods to `KeychainTxOutIndex` (志宇)
83e7b7ec40 docs(chain): improve `KeychainTxOutIndex` docs (志宇)
9294e30943 docs(wallet): improve docs for unbounded spk iterator methods (志宇)
b74c2e2622 fix(wallet): use efficient peek address logic (志宇)
81aeaba48a feat(chain): add `SpkIterator::descriptor` method (志宇)
c7b47af72f refactor(chain)!: revamp `KeychainTxOutIndex` API (志宇)
705690ee8f feat(chain): make output of `SpkTxOutIndex::unused_spks` cloneable (志宇)

Pull request description:

  Closes #1268

  ### Description

  Previously `SpkTxOutIndex` methods can be called from `KeychainTxOutIndex` due to the `DeRef` implementation. However, the internal `SpkTxOut` will also contain lookahead spks resulting in an error-prone API.

  `SpkTxOutIndex` methods are now not directly callable from `KeychainTxOutIndex`. Methods of `KeychainTxOutIndex` are renamed for clarity. I.e. methods that return an unbounded spk iter are prefixed with `unbounded`.

  In addition to this, I also optimized the peek-address logic of `bdk::Wallet` using the optimized `<SpkIterator as Iterator>::nth` implementation.

  ### Notes to the reviewers

  This is mostly refactoring, but can also be considered a bug-fix (as the API before was very problematic).

  ### Changelog notice

  Changed
  * Wallet's peek-address logic is optimized by making use of `<SpkIterator as Iterator>::nth`.
  * `KeychainTxOutIndex` API is refactored to better differentiate between methods that return unbounded vs stored spks.
  * `KeychainTxOutIndex` no longer directly exposes `SpkTxOutIndex` methods via `DeRef`. This was problematic because `SpkTxOutIndex` also contains lookahead spks which we want to hide.

  Added
  * `SpkIterator::descriptor` method which gets a reference to the internal descriptor.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  ~* [ ] I've added tests for the new feature~
  * [x] I've added docs for the new feature

  #### Bugfixes:

  * [x] This pull request breaks the existing API
  * [ ] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  danielabrozzoni:
    ACK 71fff1613d

Tree-SHA512: f29c7d2311d0e81c4fe29b8f57c219c24db958194fad5de82bb6d42d562d37fd5d152be7ee03a3f00843be5760569ad29b848250267a548d7d15320fd5292a8f
2024-01-18 11:36:31 +01:00
志宇
71fff1613d feat(chain): add txout methods to KeychainTxOutIndex
`txouts` and `txouts_in_tx` are exposed from `SpkTxOutIndex`, but
modified to remove nested unions.

Add `keychain_outpoints_in_range` that iterates over outpoints of a
given keychain derivation range.
2024-01-18 14:30:29 +08:00
vmammal
d494f63d08 doc, example(bdk): fix derivation path in mnemonic_to_descriptors
This will help avoid confusion with mainnet descriptors.
2024-01-17 13:29:25 -05:00
志宇
83e7b7ec40 docs(chain): improve KeychainTxOutIndex docs
More clarity for revealed/lookahead spks and give usecases.
2024-01-17 13:30:28 +08:00
志宇
9294e30943 docs(wallet): improve docs for unbounded spk iterator methods 2024-01-17 13:29:16 +08:00
志宇
b74c2e2622 fix(wallet): use efficient peek address logic
Changes the peek address logic to use the optimized `Iterator::nth`
implementation of `SpkIterator`.

Additionally, docs are added for panics that will occur when the caller
requests for addresses with out-of-bound derivation indices (BIP32).
2024-01-17 11:17:25 +08:00
志宇
81aeaba48a feat(chain): add SpkIterator::descriptor method
Otherwise there will be no way to view the descriptor of the
`SpkIterator`.
2024-01-17 11:17:25 +08:00
志宇
c7b47af72f refactor(chain)!: revamp KeychainTxOutIndex API
Previously `SpkTxOutIndex` methods can be called from
`KeychainTxOutIndex` due to the `DeRef` implementation. However, the
internal `SpkTxOut` will also contain lookahead spks resulting in an
error-prone API.

`SpkTxOutIndex` methods are now not directly callable from
`KeychainTxOutIndex`. Methods of `KeychainTxOutIndex` are renamed for
clarity. I.e. methods that return an unbounded spk iter are prefixed
with `unbounded`.
2024-01-17 11:17:25 +08:00
Daniela Brozzoni
40f0765d30 Merge bitcoindevkit/bdk#1276: Add LocalChain::disconnect_from method
bf67519768 feat(chain): add `LocalChain::disconnect_from` method (志宇)

Pull request description:

  Closes #1271

  ### Description

  Add a method for disconnecting a chain of blocks starting from the given `BlockId`.

  ### Notes to the reviewers

  I want to have this for https://github.com/utreexo/utreexod/pull/110

  ### Changelog notice

  Added
  * `LocalChain::disconnect_from` method to evict a chain of blocks starting from a given `BlockId`.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  danielabrozzoni:
    ACK bf67519768

Tree-SHA512: e6bd213b49b553355370994567722ad2c3460d11fcd81adc65a85e5d03822d3c38e4a4d7f967044991cf0187845467b67d035bf8904871f9fcc4ea61be761ef7
2024-01-15 13:56:59 +01:00
志宇
bf67519768 feat(chain): add LocalChain::disconnect_from method 2024-01-15 17:48:36 +08:00
Lloyd Fournier
b6422f7ffc Merge pull request #1274 from evanlinjin/avoid_btreemap_append
Avoid using `BTreeMap::append`
2024-01-15 16:54:03 +11:00
志宇
eb1714aee0 fix(chain): avoid using BTreeMap::append
The implementation of `BTreeMap::append` is non-performant making
merging changesets very slow. We use `Extend::extend` instead.

Refer to:
https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420
2024-01-15 13:36:32 +08:00
志宇
705690ee8f feat(chain): make output of SpkTxOutIndex::unused_spks cloneable
This allows us to pass this to chain sources without calling
`Iterator::collect` first.
2024-01-13 20:07:02 +08:00
志宇
cd602430ee Merge bitcoindevkit/bdk#1264: fix(example_electrum): init LocalChain from genesis
5b77942993 fix(example_electrum): init LocalChain from genesis (vmammal)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  <!-- Describe the purpose of this PR, what's being adding and/or fixed -->

  This should fix #1252

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  evanlinjin:
    ACK 5b77942993

Tree-SHA512: bf049edb2ba22abb949dfd4cb38608589287f3f374f397c82f778b59c21010daa80b63ad85c442c4ee00a2608f89a8d50447b0db85d15caac7fd0b4fd8956e1a
2024-01-11 17:00:45 +08:00
志宇
264bb85efc Merge bitcoindevkit/bdk#1261: Refactor reveal_to_target and next_store_index
761189ab2b feat(chain): debug_assert non-wildcard desc. to only cache index 0 (志宇)
887e112e8f ref(chain): Refactor reveal_to_target (Daniela Brozzoni)
21d8875826 ref(chain): Refactor next_store_index (Daniela Brozzoni)

Pull request description:

  ### Description

  As a part of #1203, I found myself having to modify `reveal_to_target`, but had some troubles understanding exactly what the method was doing.

  This PR refactors `reveal_to_target` to be, in my opinion, a bit clearer. We now exist prematurely if `next_reveal_index` < `target_index`; this way, we don't need to keep track of `next_reveal_index`, as it would always be equal to `Some(target_index)`.
  As a part of trying to understand `reveal_to_target` I had to read through `next_store_index`, I slightly improved it for clarity reasons as well.

  ### Changelog notice

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  evanlinjin:
    ACK 761189ab2b

Tree-SHA512: 17f70bccbfa1cccf718ece0eeb61f4944c9108776cf1d606ef823ae9ff58bf52114750927ac99561644ece598f8131ee7f7605071d42c91e5e88e05035e74836
2024-01-11 11:00:50 +08:00
志宇
761189ab2b feat(chain): debug_assert non-wildcard desc. to only cache index 0 2024-01-10 22:47:53 +01:00
vmammal
5b77942993 fix(example_electrum): init LocalChain from genesis 2024-01-09 21:04:36 -05:00
Steve Myers
f9dad51ae1 Merge bitcoindevkit/bdk#1263: Bump bdk_esplora and bdk_file_store versions for 1.0.0-alpha.3 release
8f6dad76ef Bump bdk_esplora and bdk_file_store versions for 1.0.0-alpha.3 release (Steve Myers)

Pull request description:

  ### Description

  These crates need to have their versions bumped because the version of bdk_chain was bumped to 0.7.0 with the 1.0.0-alpha.3 release.

  bdk_esplora to 0.5.0
  bdk_file_store to 0.3.0

  After this PR is merged I'll publish new versions of these crates to crates.io.

  ### Notes to the reviewers

  I should have done this as part of the 1.0.0-alpha.3 release process.  This problem was found when @thunderbiscuit started updating the bdk-ffi project to alpha.3 and we got type mismatch errors for bdk_chain types used in bdk_esplora.

ACKs for top commit:
  thunderbiscuit:
    ACK 8f6dad76ef.

Tree-SHA512: 735094a5d78da082437118a38db085b12c26665c8cf8fd809d62dbe9a058ee04c59a165e454b3eb8baea4209330083a6a7bce8c303be1660f6593c80ccbe46b7
2024-01-09 19:28:05 -06:00
Steve Myers
8f6dad76ef Bump bdk_esplora and bdk_file_store versions for 1.0.0-alpha.3 release
These crates need to have their versions bumped because the version of bdk_chain was bumped to 0.7.0 with the 1.0.0-alpha.3 release.

bdk_esplora to 0.5.0
bdk_file_store to 0.3.0
2024-01-09 15:06:08 -06:00
Daniela Brozzoni
887e112e8f ref(chain): Refactor reveal_to_target
Simplify the `reveal_to_target` algorithm by exiting prematurely if
the `target_index` is already revealed.
Since the `reveal_to_index` variable was different from
`Some(target_index)` only if the target was already revealed, we can
getrid of the variable altogether.
2024-01-09 14:13:43 +01:00
Daniela Brozzoni
21d8875826 ref(chain): Refactor next_store_index
Rename `v` to `index` for better clarity, and add a comment explaining
the `range`
2024-01-09 14:13:42 +01:00
Steve Myers
6e6bad9223 Merge bitcoindevkit/bdk#1161: ref(hwi): Move hwi out of bdk
105d70e974 ref(hwi): Move hwi out of bdk (Daniela Brozzoni)

Pull request description:

  Fixes #872

  ### Description

  This commit moves the `hardwaresigner` outside of bdk and inside `bdk_hwi`

  ### Notes to the reviewers

  There are currently two issues with the code:
  - `TransactionSigner` dictates that `sign_transaction` must return a `SignerError` - but being `SignerError` defined inside of bdk, we can't modify it to include an hwi specific error! I don't know how we could fix this (other than getting rid of the trait altogether :)); for now I just added a `SignerError::Generic` variant, lmk if you have better ideas!
  - The hwi tests used the bdk utils to get a funded wallet for testing, which aren't available in `bdk_hwi`, which made me realize - maybe we should expose them so that we can use them across our crates, and also our users can use them to test their code?
  For now, I just left the test commented.

  ### Changelog notice

  - The old `hardwaresigner` module has been moved out of `bdk` and inside `bdk_hwi`.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  ~~* [ ] I've added tests for the new feature~~
  * [x] I've added docs for the new feature

ACKs for top commit:
  notmandatory:
    reACK 105d70e974

Tree-SHA512: 9ae3cd035cc27bd7b2831e89c104f40771c6f165cb3bfe1a9052f820050fca7c19f4dd68f171c630a71a7d18ccdee5f94adbc497c6475bd257c2d01cc08109a1
2024-01-08 09:59:51 -06:00
Daniela Brozzoni
105d70e974 ref(hwi): Move hwi out of bdk
Fixes #872
2024-01-08 13:33:56 +01:00
31 changed files with 672 additions and 296 deletions

View File

@@ -7,6 +7,7 @@ members = [
"crates/electrum",
"crates/esplora",
"crates/bitcoind_rpc",
"crates/hwi",
"example-crates/example_cli",
"example-crates/example_electrum",
"example-crates/example_esplora",

View File

@@ -1,7 +1,7 @@
[package]
name = "bdk"
homepage = "https://bitcoindevkit.org"
version = "1.0.0-alpha.3"
version = "1.0.0-alpha.4"
repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk"
description = "A modern, lightweight, descriptor-based wallet library"
@@ -18,10 +18,9 @@ miniscript = { version = "10.0.0", features = ["serde"], default-features = fals
bitcoin = { version = "0.30.0", features = ["serde", "base64", "rand-std"], default-features = false }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
bdk_chain = { path = "../chain", version = "0.7.0", features = ["miniscript", "serde"], default-features = false }
bdk_chain = { path = "../chain", version = "0.8.0", features = ["miniscript", "serde"], default-features = false }
# Optional dependencies
hwi = { version = "0.7.0", optional = true, features = [ "miniscript"] }
bip39 = { version = "2.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
@@ -34,8 +33,6 @@ std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
compiler = ["miniscript/compiler"]
all-keys = ["keys-bip39"]
keys-bip39 = ["bip39"]
hardware-signer = ["hwi"]
test-hardware-signer = ["hardware-signer"]
# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
# for libraries to explicitly include the "getrandom/js" feature, so we only do it when

View File

@@ -33,8 +33,8 @@ fn main() -> Result<(), anyhow::Error> {
let mnemonic_with_passphrase = (mnemonic, None);
// define external and internal derivation key path
let external_path = DerivationPath::from_str("m/86h/0h/0h/0").unwrap();
let internal_path = DerivationPath::from_str("m/86h/0h/0h/1").unwrap();
let external_path = DerivationPath::from_str("m/86h/1h/0h/0").unwrap();
let internal_path = DerivationPath::from_str("m/86h/1h/0h/1").unwrap();
// generate external and internal descriptor from mnemonic
let (external_descriptor, ext_keymap) =

View File

@@ -17,8 +17,6 @@ extern crate std;
pub extern crate alloc;
pub extern crate bitcoin;
#[cfg(feature = "hardware-signer")]
pub extern crate hwi;
pub extern crate miniscript;
extern crate serde;
extern crate serde_json;

View File

@@ -50,10 +50,6 @@ pub mod tx_builder;
pub(crate) mod utils;
pub mod error;
#[cfg(feature = "hardware-signer")]
#[cfg_attr(docsrs, doc(cfg(feature = "hardware-signer")))]
pub mod hardwaresigner;
pub use utils::IsDust;
#[allow(deprecated)]
@@ -266,6 +262,11 @@ where
/// Infallibly return a derived address using the external descriptor, see [`AddressIndex`] for
/// available address index selection strategies. If none of the keys in the descriptor are derivable
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
self.try_get_address(address_index).unwrap()
}
@@ -277,6 +278,11 @@ where
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
/// be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo {
self.try_get_internal_address(address_index).unwrap()
}
@@ -653,6 +659,11 @@ impl<D> Wallet<D> {
///
/// A `PersistBackend<ChangeSet>::WriteError` will result if unable to persist the new address
/// to the `PersistBackend`.
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn try_get_address(
&mut self,
address_index: AddressIndex,
@@ -673,6 +684,11 @@ impl<D> Wallet<D> {
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
/// be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn try_get_internal_address(
&mut self,
address_index: AddressIndex,
@@ -695,6 +711,11 @@ impl<D> Wallet<D> {
/// See [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will
/// always be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
fn _get_address(
&mut self,
keychain: KeychainKind,
@@ -714,12 +735,14 @@ impl<D> Wallet<D> {
let ((index, spk), index_changeset) = txout_index.next_unused_spk(&keychain);
(index, spk.into(), Some(index_changeset))
}
AddressIndex::Peek(index) => {
let (index, spk) = txout_index
.spks_of_keychain(&keychain)
.take(index as usize + 1)
.last()
.unwrap();
AddressIndex::Peek(mut peek_index) => {
let mut spk_iter = txout_index.unbounded_spk_iter(&keychain);
if !spk_iter.descriptor().has_wildcard() {
peek_index = 0;
}
let (index, spk) = spk_iter
.nth(peek_index as usize)
.expect("derivation index is out of bounds");
(index, spk, None)
}
};
@@ -749,7 +772,7 @@ impl<D> Wallet<D> {
///
/// Will only return `Some(_)` if the wallet has given out the spk.
pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
self.indexed_graph.index.index_of_spk(spk).copied()
self.indexed_graph.index.index_of_spk(spk)
}
/// Return the list of unspent outputs of this wallet
@@ -788,7 +811,7 @@ impl<D> Wallet<D> {
self.chain.tip()
}
/// Returns a iterators of all the script pubkeys for the `Internal` and External` variants in `KeychainKind`.
/// Get unbounded script pubkey iterators for both `Internal` and `External` keychains.
///
/// This is intended to be used when doing a full scan of your addresses (e.g. after restoring
/// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g.
@@ -796,36 +819,36 @@ impl<D> Wallet<D> {
///
/// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
/// script pubkeys the wallet is storing internally).
pub fn spks_of_all_keychains(
pub fn all_unbounded_spk_iters(
&self,
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + Clone> {
self.indexed_graph.index.spks_of_all_keychains()
self.indexed_graph.index.all_unbounded_spk_iters()
}
/// Gets an iterator over all the script pubkeys in a single keychain.
/// Get an unbounded script pubkey iterator for the given `keychain`.
///
/// See [`spks_of_all_keychains`] for more documentation
/// See [`all_unbounded_spk_iters`] for more documentation
///
/// [`spks_of_all_keychains`]: Self::spks_of_all_keychains
pub fn spks_of_keychain(
/// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
pub fn unbounded_spk_iter(
&self,
keychain: KeychainKind,
) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
self.indexed_graph.index.spks_of_keychain(&keychain)
self.indexed_graph.index.unbounded_spk_iter(&keychain)
}
/// 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<LocalOutput> {
let (&spk_i, _) = self.indexed_graph.index.txout(op)?;
let (keychain, index, _) = self.indexed_graph.index.txout(op)?;
self.indexed_graph
.graph()
.filter_chain_unspents(
&self.chain,
self.chain.tip().block_id(),
core::iter::once((spk_i, op)),
core::iter::once(((), op)),
)
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
.map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo))
.next()
}
@@ -1463,7 +1486,7 @@ impl<D> Wallet<D> {
let ((index, spk), index_changeset) =
self.indexed_graph.index.next_unused_spk(&change_keychain);
let spk = spk.into();
self.indexed_graph.index.mark_used(&change_keychain, index);
self.indexed_graph.index.mark_used(change_keychain, index);
self.persist
.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
index_changeset,
@@ -1644,7 +1667,7 @@ impl<D> Wallet<D> {
.into();
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
Some(&(keychain, derivation_index)) => {
Some((keychain, derivation_index)) => {
#[allow(deprecated)]
let satisfaction_weight = self
.get_descriptor_for_keychain(keychain)
@@ -1688,7 +1711,7 @@ impl<D> Wallet<D> {
for (index, txout) in tx.output.iter().enumerate() {
let change_type = self.map_keychain(KeychainKind::Internal);
match txout_index.index_of_spk(&txout.script_pubkey) {
Some(&(keychain, _)) if keychain == change_type => change_index = Some(index),
Some((keychain, _)) if keychain == change_type => change_index = Some(index),
_ => {}
}
}
@@ -1943,10 +1966,10 @@ impl<D> Wallet<D> {
pub fn cancel_tx(&mut self, tx: &Transaction) {
let txout_index = &mut self.indexed_graph.index;
for txout in &tx.output {
if let Some(&(keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
if let Some((keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
// NOTE: unmark_used will **not** make something unused if it has actually been used
// by a tx in the tracker. It only removes the superficial marking.
txout_index.unmark_used(&keychain, index);
txout_index.unmark_used(keychain, index);
}
}
}
@@ -1962,7 +1985,7 @@ impl<D> Wallet<D> {
}
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
let &(keychain, child) = self
let (keychain, child) = self
.indexed_graph
.index
.index_of_spk(&txout.script_pubkey)?;
@@ -2176,7 +2199,7 @@ impl<D> Wallet<D> {
{
// Try to find the prev_script in our db to figure out if this is internal or external,
// and the derivation index
let &(keychain, child) = self
let (keychain, child) = self
.indexed_graph
.index
.index_of_spk(&utxo.txout.script_pubkey)
@@ -2230,7 +2253,7 @@ impl<D> Wallet<D> {
// Try to figure out the keychain and derivation for every input and output
for (is_input, index, out) in utxos.into_iter() {
if let Some(&(keychain, child)) =
if let Some((keychain, child)) =
self.indexed_graph.index.index_of_spk(&out.script_pubkey)
{
let desc = self.get_descriptor_for_keychain(keychain);

View File

@@ -80,6 +80,7 @@
//! ```
use crate::collections::BTreeMap;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::cmp::Ordering;
@@ -162,16 +163,10 @@ pub enum SignerError {
SighashError(sighash::Error),
/// Miniscript PSBT error
MiniscriptPsbt(MiniscriptPsbtError),
/// Error while signing using hardware wallets
#[cfg(feature = "hardware-signer")]
HWIError(hwi::error::Error),
}
#[cfg(feature = "hardware-signer")]
impl From<hwi::error::Error> for SignerError {
fn from(e: hwi::error::Error) -> Self {
SignerError::HWIError(e)
}
/// To be used only by external libraries implementing [`InputSigner`] or
/// [`TransactionSigner`], so that they can return their own custom errors, without having to
/// modify [`SignerError`] in BDK.
External(String),
}
impl From<sighash::Error> for SignerError {
@@ -196,8 +191,7 @@ impl fmt::Display for SignerError {
Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"),
Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err),
Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err),
#[cfg(feature = "hardware-signer")]
Self::HWIError(err) => write!(f, "Error while signing using hardware wallets: {}", err),
Self::External(err) => write!(f, "{}", err),
}
}
}

View File

@@ -3591,41 +3591,6 @@ fn test_fee_rate_sign_grinding_low_r() {
assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate);
}
// #[cfg(feature = "test-hardware-signer")]
// #[test]
// fn test_hardware_signer() {
// use std::sync::Arc;
//
// use bdk::signer::SignerOrdering;
// use bdk::wallet::hardwaresigner::HWISigner;
// use hwi::types::HWIChain;
// use hwi::HWIClient;
//
// let mut devices = HWIClient::enumerate().unwrap();
// if devices.is_empty() {
// panic!("No devices found!");
// }
// let device = devices.remove(0).unwrap();
// let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
// let descriptors = client.get_descriptors::<String>(None).unwrap();
// let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
//
// let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
// wallet.add_signer(
// KeychainKind::External,
// SignerOrdering(200),
// Arc::new(custom_signer),
// );
//
// let addr = wallet.get_address(LastUnused);
// let mut builder = wallet.build_tx();
// builder.drain_to(addr.script_pubkey()).drain_wallet();
// let (mut psbt, _) = builder.finish().unwrap();
//
// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
// assert!(finalized);
// }
#[test]
fn test_taproot_load_descriptor_duplicated_keys() {
// Added after issue https://github.com/bitcoindevkit/bdk/issues/760

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_bitcoind_rpc"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
rust-version = "1.63"
homepage = "https://bitcoindevkit.org"
@@ -16,7 +16,7 @@ readme = "README.md"
# For no-std, remember to enable the bitcoin/no-std feature
bitcoin = { version = "0.30", default-features = false }
bitcoincore-rpc = { version = "0.17" }
bdk_chain = { path = "../chain", version = "0.7", default-features = false }
bdk_chain = { path = "../chain", version = "0.8", default-features = false }
[dev-dependencies]
bitcoind = { version = "0.33", features = ["25_0"] }

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_chain"
version = "0.7.0"
version = "0.8.0"
edition = "2021"
rust-version = "1.63"
homepage = "https://bitcoindevkit.org"

View File

@@ -58,8 +58,9 @@ impl<K: Ord> Append for ChangeSet<K> {
*index = other_index.max(*index);
}
});
self.0.append(&mut other.0);
// We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
// Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420
self.0.extend(other.0);
}
/// Returns whether the changeset are empty.

View File

@@ -5,24 +5,56 @@ use crate::{
spk_iter::BIP32_MAX_INDEX,
SpkIterator, SpkTxOutIndex,
};
use bitcoin::{OutPoint, Script, TxOut};
use core::{fmt::Debug, ops::Deref};
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
use core::{
fmt::Debug,
ops::{Bound, RangeBounds},
};
use crate::Append;
const DEFAULT_LOOKAHEAD: u32 = 1_000;
/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public
/// [`Descriptor`]s.
/// [`KeychainTxOutIndex`] controls how script pubkeys are revealed for multiple keychains, and
/// indexes [`TxOut`]s with them.
///
/// Descriptors are referenced by the provided keychain generic (`K`).
/// A single keychain is a chain of script pubkeys derived from a single [`Descriptor`]. Keychains
/// are identified using the `K` generic. Script pubkeys are identified by the keychain that they
/// are derived from `K`, as well as the derivation index `u32`.
///
/// Script pubkeys for a descriptor are revealed chronologically from index 0. I.e., If the last
/// revealed index of a descriptor is 5; scripts of indices 0 to 4 are guaranteed to be already
/// revealed. In addition to revealed scripts, we have a `lookahead` parameter for each keychain,
/// which defines the number of script pubkeys to store ahead of the last revealed index.
/// # Revealed script pubkeys
///
/// Methods that could update the last revealed index will return [`super::ChangeSet`] to report
/// Tracking how script pubkeys are revealed is useful for collecting chain data. For example, if
/// the user has requested 5 script pubkeys (to receive money with), we only need to use those
/// script pubkeys to scan for chain data.
///
/// Call [`reveal_to_target`] or [`reveal_next_spk`] to reveal more script pubkeys.
/// Call [`revealed_keychain_spks`] or [`revealed_spks`] to iterate through revealed script pubkeys.
///
/// # Lookahead script pubkeys
///
/// When an user first recovers a wallet (i.e. from a recovery phrase and/or descriptor), we will
/// NOT have knowledge of which script pubkeys are revealed. So when we index a transaction or
/// txout (using [`index_tx`]/[`index_txout`]) we scan the txouts against script pubkeys derived
/// above the last revealed index. These additionally-derived script pubkeys are called the
/// lookahead.
///
/// The [`KeychainTxOutIndex`] is constructed with the `lookahead` and cannot be altered. The
/// default `lookahead` count is 1000. Use [`new`] to set a custom `lookahead`.
///
/// # Unbounded script pubkey iterator
///
/// For script-pubkey-based chain sources (such as Electrum/Esplora), an initial scan is best done
/// by iterating though derived script pubkeys one by one and requesting transaction histories for
/// each script pubkey. We will stop after x-number of script pubkeys have empty histories. An
/// unbounded script pubkey iterator is useful to pass to such a chain source.
///
/// Call [`unbounded_spk_iter`] to get an unbounded script pubkey iterator for a given keychain.
/// Call [`all_unbounded_spk_iters`] to get unbounded script pubkey iterators for all keychains.
///
/// # Change sets
///
/// Methods that can update the last revealed index will return [`super::ChangeSet`] to report
/// these changes. This can be persisted for future recovery.
///
/// ## Synopsis
@@ -58,6 +90,15 @@ const DEFAULT_LOOKAHEAD: u32 = 1_000;
/// [`Ord`]: core::cmp::Ord
/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
/// [`Descriptor`]: crate::miniscript::Descriptor
/// [`reveal_to_target`]: KeychainTxOutIndex::reveal_to_target
/// [`reveal_next_spk`]: KeychainTxOutIndex::reveal_next_spk
/// [`revealed_keychain_spks`]: KeychainTxOutIndex::revealed_keychain_spks
/// [`revealed_spks`]: KeychainTxOutIndex::revealed_spks
/// [`index_tx`]: KeychainTxOutIndex::index_tx
/// [`index_txout`]: KeychainTxOutIndex::index_txout
/// [`new`]: KeychainTxOutIndex::new
/// [`unbounded_spk_iter`]: KeychainTxOutIndex::unbounded_spk_iter
/// [`all_unbounded_spk_iters`]: KeychainTxOutIndex::all_unbounded_spk_iters
#[derive(Clone, Debug)]
pub struct KeychainTxOutIndex<K> {
inner: SpkTxOutIndex<(K, u32)>,
@@ -75,14 +116,6 @@ impl<K> Default for KeychainTxOutIndex<K> {
}
}
impl<K> Deref for KeychainTxOutIndex<K> {
type Target = SpkTxOutIndex<(K, u32)>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
type ChangeSet = super::ChangeSet<K>;
@@ -110,7 +143,7 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
}
fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
self.is_relevant(tx)
self.inner.is_relevant(tx)
}
}
@@ -123,6 +156,8 @@ impl<K> KeychainTxOutIndex<K> {
/// beyond the last revealed index. In certain situations, such as when performing an initial
/// scan of the blockchain during wallet import, it may be uncertain or unknown what the index
/// of the last revealed script pubkey actually is.
///
/// Refer to [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
pub fn new(lookahead: u32) -> Self {
Self {
inner: SpkTxOutIndex::default(),
@@ -133,8 +168,12 @@ impl<K> KeychainTxOutIndex<K> {
}
}
/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`].
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
/// Return a reference to the internal [`SpkTxOutIndex`].
///
/// **WARNING:** The internal index will contain lookahead spks. Refer to
/// [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
&self.inner
}
@@ -144,7 +183,116 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
self.inner.outpoints()
}
/// Return a reference to the internal map of the keychain to descriptors.
/// Iterate over known txouts that spend to tracked script pubkeys.
pub fn txouts(
&self,
) -> impl DoubleEndedIterator<Item = (K, u32, OutPoint, &TxOut)> + ExactSizeIterator {
self.inner
.txouts()
.map(|((k, i), op, txo)| (k.clone(), *i, op, txo))
}
/// Finds all txouts on a transaction that has previously been scanned and indexed.
pub fn txouts_in_tx(
&self,
txid: Txid,
) -> impl DoubleEndedIterator<Item = (K, u32, OutPoint, &TxOut)> {
self.inner
.txouts_in_tx(txid)
.map(|((k, i), op, txo)| (k.clone(), *i, op, txo))
}
/// Return the [`TxOut`] of `outpoint` if it has been indexed.
///
/// The associated keychain and keychain index of the txout's spk is also returned.
///
/// This calls [`SpkTxOutIndex::txout`] internally.
pub fn txout(&self, outpoint: OutPoint) -> Option<(K, u32, &TxOut)> {
self.inner
.txout(outpoint)
.map(|((k, i), txo)| (k.clone(), *i, txo))
}
/// Return the script that exists under the given `keychain`'s `index`.
///
/// This calls [`SpkTxOutIndex::spk_at_index`] internally.
pub fn spk_at_index(&self, keychain: K, index: u32) -> Option<&Script> {
self.inner.spk_at_index(&(keychain, index))
}
/// Returns the keychain and keychain index associated with the spk.
///
/// This calls [`SpkTxOutIndex::index_of_spk`] internally.
pub fn index_of_spk(&self, script: &Script) -> Option<(K, u32)> {
self.inner.index_of_spk(script).cloned()
}
/// Returns whether the spk under the `keychain`'s `index` has been used.
///
/// Here, "unused" means that after the script pubkey was stored in the index, the index has
/// never scanned a transaction output with it.
///
/// This calls [`SpkTxOutIndex::is_used`] internally.
pub fn is_used(&self, keychain: K, index: u32) -> bool {
self.inner.is_used(&(keychain, index))
}
/// Marks the script pubkey at `index` as used even though the tracker hasn't seen an output
/// with it.
///
/// This only has an effect when the `index` had been added to `self` already and was unused.
///
/// Returns whether the `index` was initially present as `unused`.
///
/// This is useful when you want to reserve a script pubkey for something but don't want to add
/// the transaction output using it to the index yet. Other callers will consider `index` on
/// `keychain` used until you call [`unmark_used`].
///
/// This calls [`SpkTxOutIndex::mark_used`] internally.
///
/// [`unmark_used`]: Self::unmark_used
pub fn mark_used(&mut self, keychain: K, index: u32) -> bool {
self.inner.mark_used(&(keychain, index))
}
/// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into
/// `unused`.
///
/// Note that if `self` has scanned an output with this script pubkey, then this will have no
/// effect.
///
/// This calls [`SpkTxOutIndex::unmark_used`] internally.
///
/// [`mark_used`]: Self::mark_used
pub fn unmark_used(&mut self, keychain: K, index: u32) -> bool {
self.inner.unmark_used(&(keychain, index))
}
/// Computes total input value going from script pubkeys in the index (sent) and the total output
/// value going to script pubkeys in the index (received) in `tx`. For the `sent` to be computed
/// correctly, the output being spent must have already been scanned by the index. Calculating
/// received just uses the [`Transaction`] outputs directly, so it will be correct even if it has
/// not been scanned.
///
/// This calls [`SpkTxOutIndex::sent_and_received`] internally.
pub fn sent_and_received(&self, tx: &Transaction) -> (u64, u64) {
self.inner.sent_and_received(tx)
}
/// Computes the net value that this transaction gives to the script pubkeys in the index and
/// *takes* from the transaction outputs in the index. Shorthand for calling
/// [`sent_and_received`] and subtracting sent from received.
///
/// This calls [`SpkTxOutIndex::net_value`] internally.
///
/// [`sent_and_received`]: Self::sent_and_received
pub fn net_value(&self, tx: &Transaction) -> i64 {
self.inner.net_value(tx)
}
}
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
/// Return a reference to the internal map of keychain to descriptors.
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
&self.keychains
}
@@ -206,64 +354,74 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
fn next_store_index(&self, keychain: &K) -> u32 {
self.inner()
.all_spks()
// This range is filtering out the spks with a keychain different than
// `keychain`. We don't use filter here as range is more optimized.
.range((keychain.clone(), u32::MIN)..(keychain.clone(), u32::MAX))
.last()
.map_or(0, |((_, v), _)| *v + 1)
.map_or(0, |((_, index), _)| *index + 1)
}
/// Generates script pubkey iterators for every `keychain`. The iterators iterate over all
/// derivable script pubkeys.
pub fn spks_of_all_keychains(
/// Get an unbounded spk iterator over a given `keychain`.
///
/// # Panics
///
/// This will panic if the given `keychain`'s descriptor does not exist.
pub fn unbounded_spk_iter(&self, keychain: &K) -> SpkIterator<Descriptor<DescriptorPublicKey>> {
SpkIterator::new(
self.keychains
.get(keychain)
.expect("keychain does not exist")
.clone(),
)
}
/// Get unbounded spk iterators for all keychains.
pub fn all_unbounded_spk_iters(
&self,
) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
self.keychains
.iter()
.map(|(keychain, descriptor)| {
(
keychain.clone(),
SpkIterator::new_with_range(descriptor.clone(), 0..),
)
})
.map(|(k, descriptor)| (k.clone(), SpkIterator::new(descriptor.clone())))
.collect()
}
/// Generates a script pubkey iterator for the given `keychain`'s descriptor (if it exists). The
/// iterator iterates over all derivable scripts of the keychain's descriptor.
///
/// # Panics
///
/// This will panic if the `keychain` does not exist.
pub fn spks_of_keychain(&self, keychain: &K) -> SpkIterator<Descriptor<DescriptorPublicKey>> {
let descriptor = self
.keychains
.get(keychain)
.expect("keychain must exist")
.clone();
SpkIterator::new_with_range(descriptor, 0..)
/// Iterate over revealed spks of all keychains.
pub fn revealed_spks(&self) -> impl DoubleEndedIterator<Item = (K, u32, &Script)> + Clone {
self.keychains.keys().flat_map(|keychain| {
self.revealed_keychain_spks(keychain)
.map(|(i, spk)| (keychain.clone(), i, spk))
})
}
/// Convenience method to get [`revealed_spks_of_keychain`] of all keychains.
///
/// [`revealed_spks_of_keychain`]: Self::revealed_spks_of_keychain
pub fn revealed_spks_of_all_keychains(
&self,
) -> BTreeMap<K, impl Iterator<Item = (u32, &Script)> + Clone> {
self.keychains
.keys()
.map(|keychain| (keychain.clone(), self.revealed_spks_of_keychain(keychain)))
.collect()
}
/// Iterates over the script pubkeys revealed by this index under `keychain`.
pub fn revealed_spks_of_keychain(
/// Iterate over revealed spks of the given `keychain`.
pub fn revealed_keychain_spks(
&self,
keychain: &K,
) -> impl DoubleEndedIterator<Item = (u32, &Script)> + Clone {
let next_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
let next_i = self.last_revealed.get(keychain).map_or(0, |&i| i + 1);
self.inner
.all_spks()
.range((keychain.clone(), u32::MIN)..(keychain.clone(), next_index))
.map(|((_, derivation_index), spk)| (*derivation_index, spk.as_script()))
.range((keychain.clone(), u32::MIN)..(keychain.clone(), next_i))
.map(|((_, i), spk)| (*i, spk.as_script()))
}
/// Iterate over revealed, but unused, spks of all keychains.
pub fn unused_spks(&self) -> impl DoubleEndedIterator<Item = (K, u32, &Script)> + Clone {
self.keychains.keys().flat_map(|keychain| {
self.unused_keychain_spks(keychain)
.map(|(i, spk)| (keychain.clone(), i, spk))
})
}
/// Iterate over revealed, but unused, spks of the given `keychain`.
pub fn unused_keychain_spks(
&self,
keychain: &K,
) -> impl DoubleEndedIterator<Item = (u32, &Script)> + Clone {
let next_i = self.last_revealed.get(keychain).map_or(0, |&i| i + 1);
self.inner
.unused_spks((keychain.clone(), u32::MIN)..(keychain.clone(), next_i))
.map(|((_, i), spk)| (*i, spk))
}
/// Get the next derivation index for `keychain`. The next index is the index after the last revealed
@@ -362,51 +520,45 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
let has_wildcard = descriptor.has_wildcard();
let target_index = if has_wildcard { target_index } else { 0 };
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
let next_reveal_index = self
.last_revealed
.get(keychain)
.map_or(0, |index| *index + 1);
debug_assert!(next_reveal_index + self.lookahead >= self.next_store_index(keychain));
// if we need to reveal new indices, the latest revealed index goes here
let mut reveal_to_index = None;
// if the target is not yet revealed, but is already stored (due to lookahead), we need to
// set the `reveal_to_index` as target here (as the `for` loop below only updates
// `reveal_to_index` for indexes that are NOT stored)
if next_reveal_index <= target_index && target_index < next_reveal_index + self.lookahead {
reveal_to_index = Some(target_index);
}
// we range over indexes that are not stored
let range = next_reveal_index + self.lookahead..=target_index + self.lookahead;
for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) {
let _inserted = self
.inner
.insert_spk((keychain.clone(), new_index), new_spk);
debug_assert!(_inserted, "must not have existing spk",);
// everything after `target_index` is stored for lookahead only
if new_index <= target_index {
reveal_to_index = Some(new_index);
}
}
match reveal_to_index {
Some(index) => {
let _old_index = self.last_revealed.insert(keychain.clone(), index);
debug_assert!(_old_index < Some(index));
(
SpkIterator::new_with_range(descriptor.clone(), next_reveal_index..index + 1),
super::ChangeSet(core::iter::once((keychain.clone(), index)).collect()),
)
}
None => (
// If the target_index is already revealed, we are done
if next_reveal_index > target_index {
return (
SpkIterator::new_with_range(
descriptor.clone(),
next_reveal_index..next_reveal_index,
),
super::ChangeSet::default(),
),
);
}
// We range over the indexes that are not stored and insert their spks in the index.
// Indexes from next_reveal_index to next_reveal_index + lookahead are already stored (due
// to lookahead), so we only range from next_reveal_index + lookahead to target + lookahead
let range = next_reveal_index + self.lookahead..=target_index + self.lookahead;
for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) {
let _inserted = self
.inner
.insert_spk((keychain.clone(), new_index), new_spk);
debug_assert!(_inserted, "must not have existing spk");
debug_assert!(
has_wildcard || new_index == 0,
"non-wildcard descriptors must not iterate past index 0"
);
}
let _old_index = self.last_revealed.insert(keychain.clone(), target_index);
debug_assert!(_old_index < Some(target_index));
(
SpkIterator::new_with_range(descriptor.clone(), next_reveal_index..target_index + 1),
super::ChangeSet(core::iter::once((keychain.clone(), target_index)).collect()),
)
}
/// Attempts to reveal the next script pubkey for `keychain`.
@@ -446,13 +598,13 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
///
/// Panics if `keychain` has never been added to the index
pub fn next_unused_spk(&mut self, keychain: &K) -> ((u32, &Script), super::ChangeSet<K>) {
let need_new = self.unused_spks_of_keychain(keychain).next().is_none();
let need_new = self.unused_keychain_spks(keychain).next().is_none();
// this rather strange branch is needed because of some lifetime issues
if need_new {
self.reveal_next_spk(keychain)
} else {
(
self.unused_spks_of_keychain(keychain)
self.unused_keychain_spks(keychain)
.next()
.expect("we already know next exists"),
super::ChangeSet::default(),
@@ -460,58 +612,44 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
}
}
/// Marks the script pubkey at `index` as used even though the tracker hasn't seen an output with it.
/// This only has an effect when the `index` had been added to `self` already and was unused.
///
/// Returns whether the `index` was initially present as `unused`.
///
/// This is useful when you want to reserve a script pubkey for something but don't want to add
/// the transaction output using it to the index yet. Other callers will consider `index` on
/// `keychain` used until you call [`unmark_used`].
///
/// [`unmark_used`]: Self::unmark_used
pub fn mark_used(&mut self, keychain: &K, index: u32) -> bool {
self.inner.mark_used(&(keychain.clone(), index))
}
/// Undoes the effect of [`mark_used`]. Returns whether the `index` is inserted back into
/// `unused`.
///
/// Note that if `self` has scanned an output with this script pubkey, then this will have no
/// effect.
///
/// [`mark_used`]: Self::mark_used
pub fn unmark_used(&mut self, keychain: &K, index: u32) -> bool {
self.inner.unmark_used(&(keychain.clone(), index))
}
/// Iterates over all unused script pubkeys for a `keychain` stored in the index.
pub fn unused_spks_of_keychain(
&self,
keychain: &K,
) -> impl DoubleEndedIterator<Item = (u32, &Script)> {
let next_index = self.last_revealed.get(keychain).map_or(0, |&v| v + 1);
let range = (keychain.clone(), u32::MIN)..(keychain.clone(), next_index);
self.inner
.unused_spks(range)
.map(|((_, i), script)| (*i, script))
}
/// Iterates over all the [`OutPoint`] that have a `TxOut` with a script pubkey derived from
/// Iterate over all [`OutPoint`]s that point to `TxOut`s with script pubkeys derived from
/// `keychain`.
pub fn txouts_of_keychain(
///
/// Use [`keychain_outpoints_in_range`](KeychainTxOutIndex::keychain_outpoints_in_range) to
/// iterate over a specific derivation range.
pub fn keychain_outpoints(
&self,
keychain: &K,
) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
self.keychain_outpoints_in_range(keychain, ..)
}
/// Iterate over [`OutPoint`]s that point to `TxOut`s with script pubkeys derived from
/// `keychain` in a given derivation `range`.
pub fn keychain_outpoints_in_range(
&self,
keychain: &K,
range: impl RangeBounds<u32>,
) -> impl DoubleEndedIterator<Item = (u32, OutPoint)> + '_ {
let start = match range.start_bound() {
Bound::Included(i) => Bound::Included((keychain.clone(), *i)),
Bound::Excluded(i) => Bound::Excluded((keychain.clone(), *i)),
Bound::Unbounded => Bound::Unbounded,
};
let end = match range.end_bound() {
Bound::Included(i) => Bound::Included((keychain.clone(), *i)),
Bound::Excluded(i) => Bound::Excluded((keychain.clone(), *i)),
Bound::Unbounded => Bound::Unbounded,
};
self.inner
.outputs_in_range((keychain.clone(), u32::MIN)..(keychain.clone(), u32::MAX))
.outputs_in_range((start, end))
.map(|((_, i), op)| (*i, op))
}
/// Returns the highest derivation index of the `keychain` where [`KeychainTxOutIndex`] has
/// found a [`TxOut`] with it's script pubkey.
pub fn last_used_index(&self, keychain: &K) -> Option<u32> {
self.txouts_of_keychain(keychain).last().map(|(i, _)| i)
self.keychain_outpoints(keychain).last().map(|(i, _)| i)
}
/// Returns the highest derivation index of each keychain that [`KeychainTxOutIndex`] has found

View File

@@ -420,6 +420,28 @@ impl LocalChain {
Ok(changeset)
}
/// Removes blocks from (and inclusive of) the given `block_id`.
///
/// This will remove blocks with a height equal or greater than `block_id`, but only if
/// `block_id` exists in the chain.
///
/// # Errors
///
/// This will fail with [`MissingGenesisError`] if the caller attempts to disconnect from the
/// genesis block.
pub fn disconnect_from(&mut self, block_id: BlockId) -> Result<ChangeSet, MissingGenesisError> {
if self.index.get(&block_id.height) != Some(&block_id.hash) {
return Ok(ChangeSet::default());
}
let changeset = self
.index
.range(block_id.height..)
.map(|(&height, _)| (height, None))
.collect::<ChangeSet>();
self.apply_changeset(&changeset).map(|_| changeset)
}
/// Reindex the heights in the chain from (and including) `from` height
fn reindex(&mut self, from: u32) {
let _ = self.index.split_off(&from);

View File

@@ -87,6 +87,11 @@ where
secp: Secp256k1::verification_only(),
}
}
/// Get a reference to the internal descriptor.
pub fn descriptor(&self) -> &D {
&self.descriptor
}
}
impl<D> Iterator for SpkIterator<D>

View File

@@ -215,7 +215,7 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
/// let unused_change_spks =
/// txout_index.unused_spks((change_index, u32::MIN)..(change_index, u32::MAX));
/// ```
pub fn unused_spks<R>(&self, range: R) -> impl DoubleEndedIterator<Item = (&I, &Script)>
pub fn unused_spks<R>(&self, range: R) -> impl DoubleEndedIterator<Item = (&I, &Script)> + Clone
where
R: RangeBounds<I>,
{

View File

@@ -123,8 +123,10 @@ pub trait Append {
}
impl<K: Ord, V> Append for BTreeMap<K, V> {
fn append(&mut self, mut other: Self) {
BTreeMap::append(self, &mut other)
fn append(&mut self, other: Self) {
// We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
// Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420
BTreeMap::extend(self, other)
}
fn is_empty(&self) -> bool {
@@ -133,8 +135,10 @@ impl<K: Ord, V> Append for BTreeMap<K, V> {
}
impl<T: Ord> Append for BTreeSet<T> {
fn append(&mut self, mut other: Self) {
BTreeSet::append(self, &mut other)
fn append(&mut self, other: Self) {
// We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
// Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420
BTreeSet::extend(self, other)
}
fn is_empty(&self) -> bool {

View File

@@ -1271,10 +1271,12 @@ impl<A> ChangeSet<A> {
}
impl<A: Ord> Append for ChangeSet<A> {
fn append(&mut self, mut other: Self) {
self.txs.append(&mut other.txs);
self.txouts.append(&mut other.txouts);
self.anchors.append(&mut other.anchors);
fn append(&mut self, other: Self) {
// We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
// Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420
self.txs.extend(other.txs);
self.txouts.extend(other.txouts);
self.anchors.extend(other.anchors);
// last_seen timestamps should only increase
self.last_seen.extend(

View File

@@ -95,25 +95,25 @@ fn test_lookahead() {
);
assert_eq!(
txout_index
.revealed_spks_of_keychain(&TestKeychain::External)
.revealed_keychain_spks(&TestKeychain::External)
.count(),
index as usize + 1,
);
assert_eq!(
txout_index
.revealed_spks_of_keychain(&TestKeychain::Internal)
.revealed_keychain_spks(&TestKeychain::Internal)
.count(),
0,
);
assert_eq!(
txout_index
.unused_spks_of_keychain(&TestKeychain::External)
.unused_keychain_spks(&TestKeychain::External)
.count(),
index as usize + 1,
);
assert_eq!(
txout_index
.unused_spks_of_keychain(&TestKeychain::Internal)
.unused_keychain_spks(&TestKeychain::Internal)
.count(),
0,
);
@@ -147,7 +147,7 @@ fn test_lookahead() {
);
assert_eq!(
txout_index
.revealed_spks_of_keychain(&TestKeychain::Internal)
.revealed_keychain_spks(&TestKeychain::Internal)
.count(),
25,
);
@@ -199,13 +199,13 @@ fn test_lookahead() {
);
assert_eq!(
txout_index
.revealed_spks_of_keychain(&TestKeychain::External)
.revealed_keychain_spks(&TestKeychain::External)
.count(),
last_external_index as usize + 1,
);
assert_eq!(
txout_index
.revealed_spks_of_keychain(&TestKeychain::Internal)
.revealed_keychain_spks(&TestKeychain::Internal)
.count(),
last_internal_index as usize + 1,
);
@@ -305,7 +305,7 @@ fn test_wildcard_derivations() {
(0..=15)
.chain([17, 20, 23])
.for_each(|index| assert!(txout_index.mark_used(&TestKeychain::External, index)));
.for_each(|index| assert!(txout_index.mark_used(TestKeychain::External, index)));
assert_eq!(txout_index.next_index(&TestKeychain::External), (26, true));
@@ -321,7 +321,7 @@ fn test_wildcard_derivations() {
// - Use all the derived till 26.
// - next_unused() = ((27, <spk>), keychain::ChangeSet)
(0..=26).for_each(|index| {
txout_index.mark_used(&TestKeychain::External, index);
txout_index.mark_used(TestKeychain::External, index);
});
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External);
@@ -364,7 +364,7 @@ fn test_non_wildcard_derivations() {
// - derive new and next unused should return the old script
// - store_up_to should not panic and return empty changeset
assert_eq!(txout_index.next_index(&TestKeychain::External), (0, false));
txout_index.mark_used(&TestKeychain::External, 0);
txout_index.mark_used(TestKeychain::External, 0);
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External);
assert_eq!(spk, (0, external_spk.as_script()));
@@ -381,7 +381,7 @@ fn test_non_wildcard_derivations() {
// we check that spks_of_keychain returns a SpkIterator with just one element
assert_eq!(
txout_index
.spks_of_keychain(&TestKeychain::External)
.revealed_keychain_spks(&TestKeychain::External)
.count(),
1,
);

View File

@@ -1,5 +1,5 @@
use bdk_chain::local_chain::{
AlterCheckPointError, CannotConnectError, ChangeSet, LocalChain, Update,
AlterCheckPointError, CannotConnectError, ChangeSet, LocalChain, MissingGenesisError, Update,
};
use bitcoin::BlockHash;
@@ -350,3 +350,76 @@ fn local_chain_insert_block() {
assert_eq!(chain, t.expected_final, "[{}] unexpected final chain", i,);
}
}
#[test]
fn local_chain_disconnect_from() {
struct TestCase {
name: &'static str,
original: LocalChain,
disconnect_from: (u32, BlockHash),
exp_result: Result<ChangeSet, MissingGenesisError>,
exp_final: LocalChain,
}
let test_cases = [
TestCase {
name: "try_replace_genesis_should_fail",
original: local_chain![(0, h!("_"))],
disconnect_from: (0, h!("_")),
exp_result: Err(MissingGenesisError),
exp_final: local_chain![(0, h!("_"))],
},
TestCase {
name: "try_replace_genesis_should_fail_2",
original: local_chain![(0, h!("_")), (2, h!("B")), (3, h!("C"))],
disconnect_from: (0, h!("_")),
exp_result: Err(MissingGenesisError),
exp_final: local_chain![(0, h!("_")), (2, h!("B")), (3, h!("C"))],
},
TestCase {
name: "from_does_not_exist",
original: local_chain![(0, h!("_")), (3, h!("C"))],
disconnect_from: (2, h!("B")),
exp_result: Ok(ChangeSet::default()),
exp_final: local_chain![(0, h!("_")), (3, h!("C"))],
},
TestCase {
name: "from_has_different_blockhash",
original: local_chain![(0, h!("_")), (2, h!("B"))],
disconnect_from: (2, h!("not_B")),
exp_result: Ok(ChangeSet::default()),
exp_final: local_chain![(0, h!("_")), (2, h!("B"))],
},
TestCase {
name: "disconnect_one",
original: local_chain![(0, h!("_")), (2, h!("B"))],
disconnect_from: (2, h!("B")),
exp_result: Ok(ChangeSet::from_iter([(2, None)])),
exp_final: local_chain![(0, h!("_"))],
},
TestCase {
name: "disconnect_three",
original: local_chain![(0, h!("_")), (2, h!("B")), (3, h!("C")), (4, h!("D"))],
disconnect_from: (2, h!("B")),
exp_result: Ok(ChangeSet::from_iter([(2, None), (3, None), (4, None)])),
exp_final: local_chain![(0, h!("_"))],
},
];
for (i, t) in test_cases.into_iter().enumerate() {
println!("Case {}: {}", i, t.name);
let mut chain = t.original;
let result = chain.disconnect_from(t.disconnect_from.into());
assert_eq!(
result, t.exp_result,
"[{}:{}] unexpected changeset result",
i, t.name
);
assert_eq!(
chain, t.exp_final,
"[{}:{}] unexpected final chain",
i, t.name
);
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_electrum"
version = "0.5.0"
version = "0.6.0"
edition = "2021"
homepage = "https://bitcoindevkit.org"
repository = "https://github.com/bitcoindevkit/bdk"
@@ -12,6 +12,6 @@ readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bdk_chain = { path = "../chain", version = "0.7.0", default-features = false }
bdk_chain = { path = "../chain", version = "0.8.0", default-features = false }
electrum-client = { version = "0.18" }
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }

View File

@@ -187,7 +187,7 @@ impl ElectrumExt for Client {
) -> Result<(ElectrumUpdate, BTreeMap<K, u32>), Error> {
let mut request_spks = keychain_spks
.into_iter()
.map(|(k, s)| (k, s.into_iter()))
.map(|(k, s)| (k.clone(), s.into_iter()))
.collect::<BTreeMap<K, _>>();
let mut scanned_spks = BTreeMap::<(K, u32), (ScriptBuf, bool)>::new();

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_esplora"
version = "0.4.0"
version = "0.6.0"
edition = "2021"
homepage = "https://bitcoindevkit.org"
repository = "https://github.com/bitcoindevkit/bdk"
@@ -12,7 +12,7 @@ readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bdk_chain = { path = "../chain", version = "0.7.0", default-features = false }
bdk_chain = { path = "../chain", version = "0.8.0", default-features = false }
esplora-client = { version = "0.6.0", default-features = false }
async-trait = { version = "0.1.66", optional = true }
futures = { version = "0.3.26", optional = true }

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_file_store"
version = "0.2.0"
version = "0.4.0"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/bitcoindevkit/bdk"
@@ -11,7 +11,7 @@ authors = ["Bitcoin Dev Kit Developers"]
readme = "README.md"
[dependencies]
bdk_chain = { path = "../chain", version = "0.7.0", features = [ "serde", "miniscript" ] }
bdk_chain = { path = "../chain", version = "0.8.0", features = [ "serde", "miniscript" ] }
bincode = { version = "1" }
serde = { version = "1", features = ["derive"] }

13
crates/hwi/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "bdk_hwi"
version = "0.1.0"
edition = "2021"
homepage = "https://bitcoindevkit.org"
repository = "https://github.com/bitcoindevkit/bdk"
description = "Utilities to use bdk with hardware wallets"
license = "MIT OR Apache-2.0"
readme = "README.md"
[dependencies]
bdk = { path = "../bdk" }
hwi = { version = "0.7.0", features = [ "miniscript"] }

42
crates/hwi/src/lib.rs Normal file
View File

@@ -0,0 +1,42 @@
//! HWI Signer
//!
//! This crate contains HWISigner, an implementation of a [`TransactionSigner`] to be
//! used with hardware wallets.
//! ```no_run
//! # use bdk::bitcoin::Network;
//! # use bdk::signer::SignerOrdering;
//! # use bdk_hwi::HWISigner;
//! # use bdk::wallet::AddressIndex::New;
//! # use bdk::{FeeRate, KeychainKind, SignOptions, Wallet};
//! # use hwi::HWIClient;
//! # use std::sync::Arc;
//! #
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut devices = HWIClient::enumerate()?;
//! if devices.is_empty() {
//! panic!("No devices found!");
//! }
//! let first_device = devices.remove(0)?;
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
//!
//! # let mut wallet = Wallet::new_no_persist(
//! # "",
//! # None,
//! # Network::Testnet,
//! # )?;
//! #
//! // Adding the hardware signer to the BDK wallet
//! wallet.add_signer(
//! KeychainKind::External,
//! SignerOrdering(200),
//! Arc::new(custom_signer),
//! );
//!
//! # Ok(())
//! # }
//! ```
//!
//! [`TransactionSigner`]: bdk::wallet::signer::TransactionSigner
mod signer;
pub use signer::*;

94
crates/hwi/src/signer.rs Normal file
View File

@@ -0,0 +1,94 @@
use bdk::bitcoin::bip32::Fingerprint;
use bdk::bitcoin::psbt::PartiallySignedTransaction;
use bdk::bitcoin::secp256k1::{All, Secp256k1};
use hwi::error::Error;
use hwi::types::{HWIChain, HWIDevice};
use hwi::HWIClient;
use bdk::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
#[derive(Debug)]
/// Custom signer for Hardware Wallets
///
/// This ignores `sign_options` and leaves the decisions up to the hardware wallet.
pub struct HWISigner {
fingerprint: Fingerprint,
client: HWIClient,
}
impl HWISigner {
/// Create a instance from the specified device and chain
pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result<HWISigner, Error> {
let client = HWIClient::get_client(device, false, chain)?;
Ok(HWISigner {
fingerprint: device.fingerprint,
client,
})
}
}
impl SignerCommon for HWISigner {
fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
SignerId::Fingerprint(self.fingerprint)
}
}
impl TransactionSigner for HWISigner {
fn sign_transaction(
&self,
psbt: &mut PartiallySignedTransaction,
_sign_options: &bdk::SignOptions,
_secp: &Secp256k1<All>,
) -> Result<(), SignerError> {
psbt.combine(
self.client
.sign_tx(psbt)
.map_err(|e| {
SignerError::External(format!("While signing with hardware wallet: {}", e))
})?
.psbt,
)
.expect("Failed to combine HW signed psbt with passed PSBT");
Ok(())
}
}
// TODO: re-enable this once we have the `get_funded_wallet` test util
// #[cfg(test)]
// mod tests {
// #[test]
// fn test_hardware_signer() {
// use std::sync::Arc;
//
// use bdk::tests::get_funded_wallet;
// use bdk::signer::SignerOrdering;
// use bdk::bitcoin::Network;
// use crate::HWISigner;
// use hwi::HWIClient;
//
// let mut devices = HWIClient::enumerate().unwrap();
// if devices.is_empty() {
// panic!("No devices found!");
// }
// let device = devices.remove(0).unwrap();
// let client = HWIClient::get_client(&device, true, Network::Regtest.into()).unwrap();
// let descriptors = client.get_descriptors::<String>(None).unwrap();
// let custom_signer = HWISigner::from_device(&device, Network::Regtest.into()).unwrap();
//
// let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
// wallet.add_signer(
// bdk::KeychainKind::External,
// SignerOrdering(200),
// Arc::new(custom_signer),
// );
//
// let addr = wallet.get_address(bdk::wallet::AddressIndex::LastUnused);
// let mut builder = wallet.build_tx();
// builder.drain_to(addr.script_pubkey()).drain_wallet();
// let (mut psbt, _) = builder.finish().unwrap();
//
// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
// assert!(finalized);
// }
// }

View File

@@ -478,14 +478,14 @@ where
true => Keychain::Internal,
false => Keychain::External,
};
for (spk_i, spk) in index.revealed_spks_of_keychain(&target_keychain) {
for (spk_i, spk) in index.revealed_keychain_spks(&target_keychain) {
let address = Address::from_script(spk, network)
.expect("should always be able to derive address");
println!(
"{:?} {} used:{}",
spk_i,
address,
index.is_used(&(target_keychain, spk_i))
index.is_used(target_keychain, spk_i)
);
}
Ok(())
@@ -611,7 +611,7 @@ where
// We don't want other callers/threads to use this address while we're using it
// but we also don't want to scan the tx we just created because it's not
// technically in the blockchain yet.
graph.index.mark_used(&change_keychain, index);
graph.index.mark_used(change_keychain, index);
(tx, Some((change_keychain, index)))
} else {
(tx, None)
@@ -636,7 +636,7 @@ where
Err(e) => {
if let Some((keychain, index)) = change_index {
// We failed to broadcast, so allow our change address to be used in the future
graph.lock().unwrap().index.unmark_used(&keychain, index);
graph.lock().unwrap().index.unmark_used(keychain, index);
}
Err(e)
}

View File

@@ -5,7 +5,7 @@ use std::{
};
use bdk_chain::{
bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid},
bitcoin::{constants::genesis_block, Address, Network, OutPoint, Txid},
indexed_tx_graph::{self, IndexedTxGraph},
keychain,
local_chain::{self, LocalChain},
@@ -112,7 +112,12 @@ fn main() -> anyhow::Result<()> {
graph
});
let chain = Mutex::new(LocalChain::from_changeset(disk_local_chain)?);
let chain = Mutex::new({
let genesis_hash = genesis_block(args.network).block_hash();
let (mut chain, _) = LocalChain::from_genesis_hash(genesis_hash);
chain.apply_changeset(&disk_local_chain)?;
chain
});
let electrum_cmd = match &args.command {
example_cli::Commands::ChainSpecific(electrum_cmd) => electrum_cmd,
@@ -150,7 +155,7 @@ fn main() -> anyhow::Result<()> {
let keychain_spks = graph
.index
.spks_of_all_keychains()
.all_unbounded_spk_iters()
.into_iter()
.map(|(keychain, iter)| {
let mut first = true;
@@ -201,29 +206,28 @@ fn main() -> anyhow::Result<()> {
if all_spks {
let all_spks = graph
.index
.all_spks()
.iter()
.map(|(k, v)| (*k, v.clone()))
.revealed_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.collect::<Vec<_>>();
spks = Box::new(spks.chain(all_spks.into_iter().map(|(index, script)| {
eprintln!("scanning {:?}", index);
script
spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
eprintln!("scanning {}:{}", k, i);
spk
})));
}
if unused_spks {
let unused_spks = graph
.index
.unused_spks(..)
.map(|(k, v)| (*k, ScriptBuf::from(v)))
.unused_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.collect::<Vec<_>>();
spks = Box::new(spks.chain(unused_spks.into_iter().map(|(index, script)| {
spks = Box::new(spks.chain(unused_spks.into_iter().map(|(k, i, spk)| {
eprintln!(
"Checking if address {} {:?} has been used",
Address::from_script(&script, args.network).unwrap(),
index
"Checking if address {} {}:{} has been used",
Address::from_script(&spk, args.network).unwrap(),
k,
i,
);
script
spk
})));
}

View File

@@ -165,7 +165,7 @@ fn main() -> anyhow::Result<()> {
.lock()
.expect("mutex must not be poisoned")
.index
.spks_of_all_keychains()
.all_unbounded_spk_iters()
.into_iter()
// This `map` is purely for logging.
.map(|(keychain, iter)| {
@@ -235,32 +235,32 @@ fn main() -> anyhow::Result<()> {
if *all_spks {
let all_spks = graph
.index
.all_spks()
.iter()
.map(|(k, v)| (*k, v.clone()))
.revealed_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.collect::<Vec<_>>();
spks = Box::new(spks.chain(all_spks.into_iter().map(|(index, script)| {
eprintln!("scanning {:?}", index);
spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
eprintln!("scanning {}:{}", k, i);
// Flush early to ensure we print at every iteration.
let _ = io::stderr().flush();
script
spk
})));
}
if unused_spks {
let unused_spks = graph
.index
.unused_spks(..)
.map(|(k, v)| (*k, v.to_owned()))
.unused_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.collect::<Vec<_>>();
spks = Box::new(spks.chain(unused_spks.into_iter().map(|(index, script)| {
spks = Box::new(spks.chain(unused_spks.into_iter().map(|(k, i, spk)| {
eprintln!(
"Checking if address {} {:?} has been used",
Address::from_script(&script, args.network).unwrap(),
index
"Checking if address {} {}:{} has been used",
Address::from_script(&spk, args.network).unwrap(),
k,
i,
);
// Flush early to ensure we print at every iteration.
let _ = io::stderr().flush();
script
spk
})));
}
if utxos {

View File

@@ -40,7 +40,7 @@ fn main() -> Result<(), anyhow::Error> {
let prev_tip = wallet.latest_checkpoint();
let keychain_spks = wallet
.spks_of_all_keychains()
.all_unbounded_spk_iters()
.into_iter()
.map(|(k, k_spks)| {
let mut once = Some(());

View File

@@ -39,7 +39,7 @@ async fn main() -> Result<(), anyhow::Error> {
let prev_tip = wallet.latest_checkpoint();
let keychain_spks = wallet
.spks_of_all_keychains()
.all_unbounded_spk_iters()
.into_iter()
.map(|(k, k_spks)| {
let mut once = Some(());

View File

@@ -38,7 +38,7 @@ fn main() -> Result<(), anyhow::Error> {
let prev_tip = wallet.latest_checkpoint();
let keychain_spks = wallet
.spks_of_all_keychains()
.all_unbounded_spk_iters()
.into_iter()
.map(|(k, k_spks)| {
let mut once = Some(());