Compare commits

..

3 Commits

Author SHA1 Message Date
Steve Myers
d288cbbbbc Bump version to 0.25.0 2022-12-05 14:28:30 -06:00
Steve Myers
005447c81e Downgrade ubuntu to 20.04 for test_hardware_wallet CI job 2022-12-05 13:54:05 -06:00
Steve Myers
5168d41a93 Bump version to 0.25.0-rc.1 2022-11-30 09:08:37 -08:00
38 changed files with 370 additions and 792 deletions

View File

@@ -34,7 +34,6 @@ Change the `master` branch to the next MINOR+1 version:
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR+1`, eg. `bump_dev_0_22`.
- [ ] Bump the `bump_dev_MAJOR_MINOR+1` branch to the next development MINOR+1 version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0`.
- Update the `CHANGELOG.md` file.
- The commit message should be "Bump version to MAJOR.MINOR+1.0".
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR+1` branch to `master`.
- Title PR "Bump version to MAJOR.MINOR+1.0".

View File

@@ -34,7 +34,6 @@ Change the `master` branch to the new PATCH+1 version:
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR_PATCH+1`, eg. `bump_dev_0_22_1`.
- [ ] Bump the `bump_dev_MAJOR_MINOR` branch to the next development PATCH+1 version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR.PATCH+1`.
- Update the `CHANGELOG.md` file.
- The commit message should be "Bump version to MAJOR.MINOR.PATCH+1".
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR_PATCH+1` branch to `master`.
- Title PR "Bump version to MAJOR.MINOR.PATCH+1".

View File

@@ -10,9 +10,9 @@ jobs:
strategy:
matrix:
rust:
- version: 1.65.0 # STABLE
- version: 1.60.0 # STABLE
clippy: true
- version: 1.57.0 # MSRV
- version: 1.56.1 # MSRV
features:
- default
- minimal
@@ -146,7 +146,7 @@ jobs:
- run: sudo apt-get update || exit 1
- run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
- name: Set default toolchain
run: rustup default 1.65.0 # STABLE
run: rustup default 1.56.1 # STABLE
- name: Set profile
run: rustup set profile minimal
- name: Add target wasm32
@@ -154,7 +154,7 @@ jobs:
- name: Update toolchain
run: rustup update
- name: Check
run: cargo check --target wasm32-unknown-unknown --features async-interface,use-esplora-async,dev-getrandom-wasm --no-default-features
run: cargo check --target wasm32-unknown-unknown --features use-esplora-async,dev-getrandom-wasm --no-default-features
fmt:
name: Rust fmt
@@ -178,8 +178,8 @@ jobs:
strategy:
matrix:
rust:
- version: 1.65.0 # STABLE
- version: 1.57.0 # MSRV
- version: 1.60.0 # STABLE
- version: 1.56.1 # MSRV
steps:
- name: Checkout
uses: actions/checkout@v3

View File

@@ -18,7 +18,7 @@ jobs:
target
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Set default toolchain
run: rustup default nightly-2022-12-14
run: rustup default nightly-2022-01-25
- name: Set profile
run: rustup set profile minimal
- name: Update toolchain

View File

@@ -1,166 +1,13 @@
# Changelog
All notable changes to this project can be found here and in each release's git tag and can be viewed with `git tag -ln100 "v*"`. See also [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
Contributors do not need to change this file but do need to add changelog details in their PR descriptions. The person making the next release will collect changelog details from included PRs and edit this file prior to each release.
All notable changes to this project prior to release **0.22.0** are documented in this file. Future
changelog information can be found in each release's git tag and can be viewed with `git tag -ln100 "v*"`.
Changelog info is also documented on the [GitHub releases](https://github.com/bitcoindevkit/bdk/releases)
page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [v0.27.2]
### Summary
Disable default-features for rust-bitcoin and rust-miniscript dependencies, and for rust-esplora-client optional dependency.
### Changed
- Set default-features = false for rust-bitcoin and rust-miniscript #882
- Update esplora client dependency to version 0.4 #884
## [v0.27.1]
### Summary
Fixes [RUSTSEC-2022-0090], this issue is only applicable if you are using the optional sqlite database feature.
[RUSTSEC-2022-0090]: https://rustsec.org/advisories/RUSTSEC-2022-0090
### Changed
- Update optional sqlite dependency from 0.27.0 to 0.28.0. #867
## [v0.27.0]
### Summary
A maintenance release with a bump in project MSRV to 1.57.0, updated dependence and a few developer oriented improvements. Improvements include better error formatting, don't default to async/await for wasm32 and adding derived PartialEq and Eq on SyncTime.
### Changed
- Improve display error formatting #814
- Don't default to use async/await on wasm32 #831
- Project MSRV changed from 1.56.1 to 1.57.0 #842
- Update rust-miniscript dependency to latest bug fix release 9.0 #844
### Added
- Derive PartialEq, Eq on SyncTime #837
## [v0.26.0]
### Summary
This release improves Fulcrum electrum server compatibility and fixes public descriptor template key origin paths. We also snuck in small enhancements to configure the electrum client to validate the domain using SSL and sort TransactionDetails by block height and timestamp.
### Fixed
- Make electrum blockchain client `save_tx` function order independent to work with Fulcrum servers. #808
- Fix wrong testnet key origin path in public descriptor templates. #818
- Make README.md code examples compile without errors. #820
### Changed
- Bump `hwi` dependency to `0.4.0`. #825
- Bump `esplora-client` dependency to `0.3` #830
### Added
- For electrum blockchain client, allow user to configure whether to validate the domain using SSL. #805
- Implement ordering for `TransactionDetails`. #812
## [v0.25.0]
### Summary
This release fixes slow sync time and big script_pubkeys table with SQLite, the wallet rescan height for the FullyNodedExport and setting the network for keys in the KeyMap when using descriptor templates. Also added are new blockchain and mnemonic examples.
### Fixed
- Slow sync time and big script_pubkeys table with SQLite.
- Wallet rescan height for the FullyNodedExport.
- Setting the network for keys in the KeyMap when using descriptor templates.
### Added
- Examples for connecting to Esplora, Electrum Server, Neutrino and Bitcoin Core.
- Example for using a mnemonic in a descriptors.
## [v0.24.0]
### Summary
This release contains important dependency updates for `rust-bitcoin` to `0.29` and `rust-miniscript` to `8.0`, plus related crates that also depend on the latest version of `rust-bitcoin`. The release also includes a breaking change to the BDK signer which now produces low-R signatures by default, saving one byte. A bug was found in the `get_checksum` and `get_checksum_bytes` functions, which are now deprecated in favor of fixed versions called `calc_checksum` and `calc_checksum_bytes`. And finally a new `hardware-signer` features was added that re-exports the `hwi` crate, along with a new `hardware_signers.rs` example file.
### Changed
- Updated dependency versions for `rust-bitcoin` to `0.29` and `rust-miniscript` to `8.0`, plus all related crates. @afilini #770
- BDK Signer now produces low-R signatures by default, saving one byte. If you want to preserve the original behavior, set allow_grinding in the SignOptions to false. @vladimirfomene #779
- Deprecated `get_checksum`and `get_checksum_bytes` due to bug where they calculates the checksum of a descriptor that already has a checksum. Use `calc_checksum` and `calc_checksum_bytes` instead. @evanlinjin #765
- Remove deprecated "address validators". @afilini #770
### Added
- New `calc_checksum` and `calc_checksum_bytes`, replace deprecated `get_checksum` and `get_checksum_bytes`. @evanlinjin #765
- Re-export the hwi crate when the feature hardware-signer is on. @danielabrozzoni #758
- New examples/hardware_signer.rs. @danielabrozzoni #758
- Make psbt module public to expose PsbtUtils trait to downstream projects. @notmandatory #782
## [v0.23.0]
### Summary
This release brings new utilities functions on PSBTs like `fee_amount()` and `fee_rate()` and migrates BDK to use our new external esplora client library.
As always many bug fixes, docs and tests improvement are also included.
### Changed
- Update electrum-client to 0.11.0 by @afilini in https://github.com/bitcoindevkit/bdk/pull/737
- Change configs for source-base code coverage by @wszdexdrf in https://github.com/bitcoindevkit/bdk/pull/708
- Improve docs regarding PSBT finalization by @tnull in https://github.com/bitcoindevkit/bdk/pull/753
- Update compiler example to a Policy example by @rajarshimaitra in https://github.com/bitcoindevkit/bdk/pull/730
- Fix the release process by @afilini in https://github.com/bitcoindevkit/bdk/pull/754
- Remove redundant duplicated keys check by @afilini in https://github.com/bitcoindevkit/bdk/pull/761
- Remove genesis_block lazy initialization by @shobitb in https://github.com/bitcoindevkit/bdk/pull/756
- Fix `Wallet::descriptor_checksum` to actually return the checksum by @evanlinjin in https://github.com/bitcoindevkit/bdk/pull/763
- Use the esplora client crate by @afilini in https://github.com/bitcoindevkit/bdk/pull/764
### Added
- Run code coverage on every PR by @danielabrozzoni in https://github.com/bitcoindevkit/bdk/pull/747
- Add psbt_signer.rs example by @notmandatory in https://github.com/bitcoindevkit/bdk/pull/744
- Add fee_amount() and fee_rate() functions to PsbtUtils trait by @notmandatory in https://github.com/bitcoindevkit/bdk/pull/728
- Add tests to improve coverage by @vladimirfomene in https://github.com/bitcoindevkit/bdk/pull/745
- Enable signing taproot transactions with only `non_witness_utxos` by @afilini in https://github.com/bitcoindevkit/bdk/pull/757
- Add datatype for is_spent sqlite column by @vladimirfomene in https://github.com/bitcoindevkit/bdk/pull/713
- Add vscode filter to gitignore by @evanlinjin in https://github.com/bitcoindevkit/bdk/pull/762
## [v0.22.0]
### Summary
This release brings support for hardware signers on desktop through the HWI library.
It also includes fixes and improvements which are part of our ongoing effort of integrating
BDK and LDK together.
### Changed
- FeeRate function name as_sat_vb to as_sat_per_vb. #678
- Verify signatures after signing. #718
- Dependency electrum-client to 0.11.0. #737
### Added
- Functions to create FeeRate from sats/kvbytes and sats/kwu. #678
- Custom hardware wallet signer HwiSigner in wallet::hardwaresigner module. #682
- Function allow_dust on TxBuilder. #689
- Implementation of Deref<Target=UrlClient> for EsploraBlockchain. #722
- Implementation of Deref<Target=Client> for ElectrumBlockchain #705
- Implementation of Deref<Target=Client> for RpcBlockchain. #731
## [v0.21.0]
## [v0.21.0] - [v0.20.0]
- Add `descriptor::checksum::get_checksum_bytes` method.
- Add `Excess` enum to handle remaining amount after coin selection.
@@ -173,7 +20,7 @@ BDK and LDK together.
- New `RpcBlockchain` implementation with various fixes.
- Return balance in separate categories, namely `confirmed`, `trusted_pending`, `untrusted_pending` & `immature`.
## [v0.20.0]
## [v0.20.0] - [v0.19.0]
- New MSRV set to `1.56.1`
- Fee sniping discouraging through nLockTime - if the user specifies a `current_height`, we use that as a nlocktime, otherwise we use the last sync height (or 0 if we never synced)
@@ -184,7 +31,7 @@ BDK and LDK together.
- Deprecate `AddressValidator`
- Fix Electrum wallet sync potentially causing address index decrement - compare proposed index and current index before applying batch operations during sync.
## [v0.19.0]
## [v0.19.0] - [v0.18.0]
- added `OldestFirstCoinSelection` impl to `CoinSelectionAlgorithm`
- New MSRV set to `1.56`
@@ -200,7 +47,7 @@ BDK and LDK together.
- Support for `tr()` descriptors in the `descriptor!()` macro
- Add support for Bitcoin Core 23.0 when using the `rpc` blockchain
## [v0.18.0]
## [v0.18.0] - [v0.17.0]
- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, i.e. for mobile platforms.
- Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
@@ -210,7 +57,7 @@ BDK and LDK together.
- Rename `WalletExport` to `FullyNodedExport`, deprecate the former.
- Bump `miniscript` dependency version to `^6.1`.
## [v0.17.0]
## [v0.17.0] - [v0.16.1]
- Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag.
- `verify` flag removed from `TransactionDetails`.
@@ -231,45 +78,45 @@ To decouple the `Wallet` from the `Blockchain` we've made major changes:
- Removed `max_addresses` sync parameter which determined how many addresses to cache before syncing since this can just be done with `ensure_addresses_cached`.
- remove `flush` method from the `Database` trait.
## [v0.16.1]
## [v0.16.1] - [v0.16.0]
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
## [v0.16.0]
## [v0.16.0] - [v0.15.0]
- Disable `reqwest` default features.
- Added `reqwest-default-tls` feature: Use this to restore the TLS defaults of reqwest if you don't want to add a dependency to it in your own manifest.
- Use dust_value from rust-bitcoin
- Fixed generating WIF in the correct network format.
## [v0.15.0]
## [v0.15.0] - [v0.14.0]
- Overhauled sync logic for electrum and esplora.
- Unify ureq and reqwest esplora backends to have the same configuration parameters. This means reqwest now has a timeout parameter and ureq has a concurrency parameter.
- Fixed esplora fee estimation.
## [v0.14.0]
## [v0.14.0] - [v0.13.0]
- BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39.
- Add new method on the `TxBuilder` to embed data in the transaction via `OP_RETURN`. To allow that a fix to check the dust only on spendable output has been introduced.
- Update the `Database` trait to store the last sync timestamp and block height
- Rename `ConfirmationTime` to `BlockTime`
## [v0.13.0]
## [v0.13.0] - [v0.12.0]
- Exposed `get_tx()` method from `Database` to `Wallet`.
## [v0.12.0]
## [v0.12.0] - [v0.11.0]
- Activate `miniscript/use-serde` feature to allow consumers of the library to access it via the re-exported `miniscript` crate.
- Add support for proxies in `EsploraBlockchain`
- Added `SqliteDatabase` that implements `Database` backed by a sqlite database using `rusqlite` crate.
## [v0.11.0]
## [v0.11.0] - [v0.10.0]
- Added `flush` method to the `Database` trait to explicitly flush to disk latest changes on the db.
## [v0.10.0]
## [v0.10.0] - [v0.9.0]
- Added `RpcBlockchain` in the `AnyBlockchain` struct to allow using Rpc backend where `AnyBlockchain` is used (eg `bdk-cli`)
- Removed hard dependency on `tokio`.
@@ -283,21 +130,21 @@ To decouple the `Wallet` from the `Blockchain` we've made major changes:
- Removed `stop_gap` from `Blockchain` trait and added it to only `ElectrumBlockchain` and `EsploraBlockchain` structs.
- Added a `ureq` backend for use when not using feature `async-interface` or target WASM. `ureq` is a blocking HTTP client.
## [v0.9.0]
## [v0.9.0] - [v0.8.0]
### Wallet
- Added Bitcoin core RPC added as blockchain backend
- Added a `verify` feature that can be enable to verify the unconfirmed txs we download against the consensus rules
## [v0.8.0]
## [v0.8.0] - [v0.7.0]
### Wallet
- Added an option that must be explicitly enabled to allow signing using non-`SIGHASH_ALL` sighashes (#350)
#### Changed
`get_address` now returns an `AddressInfo` struct that includes the index and derefs to `Address`.
## [v0.7.0]
## [v0.7.0] - [v0.6.0]
### Policy
#### Changed
@@ -312,7 +159,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
- Require and validate `non_witness_utxo` for SegWit signatures by default, can be adjusted with `SignOptions`
- Replace the opt-in builder option `force_non_witness_utxo` with the opposite `only_witness_utxo`. From now on we will provide the `non_witness_utxo`, unless explicitly asked not to.
## [v0.6.0]
## [v0.6.0] - [v0.5.1]
### Misc
#### Changed
@@ -336,13 +183,13 @@ Timelocks are considered (optionally) in building the `satisfaction` field
#### Fixed
- Fixed `coin_select` calculation for UTXOs where `value < fee` that caused over-/underflow errors.
## [v0.5.1]
## [v0.5.1] - [v0.5.0]
### Misc
#### Changed
- Pin `hyper` to `=0.14.4` to make it compile on Rust 1.45
## [v0.5.0]
## [v0.5.0] - [v0.4.0]
### Misc
#### Changed
@@ -352,7 +199,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
#### Changed
- `FeeRate` constructors `from_sat_per_vb` and `default_min_relay_fee` are now `const` functions
## [v0.4.0]
## [v0.4.0] - [v0.3.0]
### Keys
#### Changed
@@ -381,7 +228,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
- Removed unneeded `Result<(), PolicyError>` return type for `Satisfaction::finalize()`
- Removed the `TooManyItemsSelected` policy error (see commit message for more details)
## [v0.3.0]
## [v0.3.0] - [v0.2.0]
### Descriptor
#### Changed
@@ -418,7 +265,7 @@ final transaction is created by calling `finish` on the builder.
#### Changed
- Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository
## [v0.2.0]
## [v0.2.0] - [0.1.0-beta.1]
### Project
#### Added
@@ -646,12 +493,3 @@ final transaction is created by calling `finish` on the builder.
[v0.19.0]: https://github.com/bitcoindevkit/bdk/compare/v0.18.0...v0.19.0
[v0.20.0]: https://github.com/bitcoindevkit/bdk/compare/v0.19.0...v0.20.0
[v0.21.0]: https://github.com/bitcoindevkit/bdk/compare/v0.20.0...v0.21.0
[v0.22.0]: https://github.com/bitcoindevkit/bdk/compare/v0.21.0...v0.22.0
[v0.23.0]: https://github.com/bitcoindevkit/bdk/compare/v0.22.0...v0.23.0
[v0.24.0]: https://github.com/bitcoindevkit/bdk/compare/v0.23.0...v0.24.0
[v0.25.0]: https://github.com/bitcoindevkit/bdk/compare/v0.24.0...v0.25.0
[v0.26.0]: https://github.com/bitcoindevkit/bdk/compare/v0.25.0...v0.26.0
[v0.27.0]: https://github.com/bitcoindevkit/bdk/compare/v0.26.0...v0.27.0
[v0.27.1]: https://github.com/bitcoindevkit/bdk/compare/v0.27.0...v0.27.1
[v0.27.2]: https://github.com/bitcoindevkit/bdk/compare/v0.27.1...v0.27.2
[Unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.27.2...HEAD

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk"
version = "0.27.2"
version = "0.25.0"
edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org"
@@ -14,8 +14,8 @@ license = "MIT OR Apache-2.0"
[dependencies]
bdk-macros = "^0.6"
log = "^0.4"
miniscript = { version = "9.0", default-features = false, features = ["serde"] }
bitcoin = { version = "0.29.2", default-features = false, features = ["serde", "base64", "rand"] }
miniscript = { version = "8.0", features = ["serde"] }
bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
rand = "^0.8"
@@ -23,15 +23,15 @@ rand = "^0.8"
# Optional dependencies
sled = { version = "0.34", optional = true }
electrum-client = { version = "0.12", optional = true }
esplora-client = { version = "0.4", default-features = false, optional = true }
rusqlite = { version = "0.28.0", optional = true }
esplora-client = { version = "0.2", default-features = false, optional = true }
rusqlite = { version = "0.27.0", optional = true }
ahash = { version = "0.7.6", optional = true }
futures = { version = "0.3", optional = true }
async-trait = { version = "0.1", optional = true }
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
cc = { version = ">=1.0.64", optional = true }
socks = { version = "0.3", optional = true }
hwi = { version = "0.5", optional = true, features = ["use-miniscript"] }
hwi = { version = "0.3.0", optional = true }
bip39 = { version = "1.0.1", optional = true }
bitcoinconsensus = { version = "0.19.0-3", optional = true }
@@ -104,18 +104,11 @@ test-hardware-signer = ["hardware-signer"]
dev-getrandom-wasm = ["getrandom/js"]
[dev-dependencies]
miniscript = { version = "9.0", features = ["std"] }
bitcoin = { version = "0.29.2", features = ["std"] }
lazy_static = "1.4"
env_logger = "0.7"
electrsd = "0.22"
electrsd = "0.21"
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
base64 = "^0.13"
assert_matches = "1.5.0"
# zip versions after 0.6.3 don't work with our MSRV 1.57.0
zip = "=0.6.3"
# base64ct versions at 1.6.0 and higher have MSRV 1.60.0
base64ct = "<1.6.0"
[[example]]
name = "compact_filters_balance"

View File

@@ -13,7 +13,7 @@
<a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
<a href="https://coveralls.io/github/bitcoindevkit/bdk?branch=master"><img src="https://coveralls.io/repos/github/bitcoindevkit/bdk/badge.svg?branch=master"/></a>
<a href="https://docs.rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a>
<a href="https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html"><img alt="Rustc Version 1.57.0+" src="https://img.shields.io/badge/rustc-1.57.0%2B-lightgrey.svg"/></a>
<a href="https://blog.rust-lang.org/2021/11/01/Rust-1.56.1.html"><img alt="Rustc Version 1.56.1+" src="https://img.shields.io/badge/rustc-1.56.1%2B-lightgrey.svg"/></a>
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
</p>
@@ -68,13 +68,12 @@ fn main() -> Result<(), bdk::Error> {
```rust
use bdk::{Wallet, database::MemoryDatabase};
use bdk::wallet::AddressIndex::New;
use bdk::bitcoin::Network;
fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
Network::Testnet,
bitcoin::Network::Testnet,
MemoryDatabase::default(),
)?;
@@ -97,15 +96,14 @@ use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
use base64;
use bdk::bitcoin::consensus::serialize;
use bdk::bitcoin::Network;
use bitcoin::consensus::serialize;
fn main() -> Result<(), bdk::Error> {
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
Network::Testnet,
bitcoin::Network::Testnet,
MemoryDatabase::default(),
)?;
@@ -135,21 +133,20 @@ fn main() -> Result<(), bdk::Error> {
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use base64;
use bdk::bitcoin::consensus::deserialize;
use bdk::bitcoin::Network;
use bitcoin::consensus::deserialize;
fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
Network::Testnet,
bitcoin::Network::Testnet,
MemoryDatabase::default(),
)?;
let psbt = "...";
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
let _finalized = wallet.sign(&mut psbt, SignOptions::default())?;
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
Ok(())
}

View File

@@ -6,4 +6,4 @@ RUN apt-get install wget -y
RUN wget "https://github.com/LedgerHQ/speculos/blob/master/apps/nanos%23btc%232.1%231c8db8da.elf?raw=true" -O /speculos/btc.elf
ADD automation.json /speculos/automation.json
ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--model", "nanos", "--display", "headless", "--vnc-port", "41000", "btc.elf"]
ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--display", "headless", "--vnc-port", "41000", "btc.elf"]

View File

@@ -29,7 +29,7 @@ use bdk::wallet::AddressIndex::New;
use bdk::{KeychainKind, Wallet};
/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
/// rust-miniscript library here https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
/// rust-miscript library here https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
/// rust-miniscript provides a `compile()` function that can be used to compile any miniscript policy
/// into a descriptor. This descriptor then in turn can be used in bdk a fully functioning wallet
/// can be derived from the policy.

View File

@@ -2,7 +2,6 @@ use bdk::bitcoin::{Address, Network};
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase;
use bdk::hwi::{types::HWIChain, HWIClient};
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
use bdk::signer::SignerOrdering;
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
@@ -24,27 +23,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Hold tight, I'm connecting to your hardware wallet...");
// Listing all the available hardware wallet devices...
let mut devices = HWIClient::enumerate()?;
if devices.is_empty() {
panic!("No devices found. Either plug in a hardware wallet, or start a simulator.");
}
let first_device = devices.remove(0)?;
let devices = HWIClient::enumerate()?;
let first_device = devices
.first()
.expect("No devices found. Either plug in a hardware wallet, or start a simulator.");
// ...and creating a client out of the first one
let client = HWIClient::get_client(&first_device, true, HWIChain::Test)?;
let client = HWIClient::get_client(first_device, true, HWIChain::Test)?;
println!("Look what I found, a {}!", first_device.model);
// Getting the HW's public descriptors
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
let descriptors = client.get_descriptors(None)?;
println!(
"The hardware wallet's descriptor is: {}",
descriptors.receive[0]
);
// Creating a custom signer from the device
let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
let mut wallet = Wallet::new(
descriptors.receive[0].clone(),
Some(descriptors.internal[0].clone()),
&descriptors.receive[0],
Some(&descriptors.internal[0]),
Network::Testnet,
MemoryDatabase::default(),
)?;

View File

@@ -19,7 +19,7 @@ use syn::{parse, ImplItemMethod, ItemImpl, ItemTrait, Token};
fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
let output = quote! {
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
#parsed
};
@@ -32,7 +32,7 @@ fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
let output = quote! {
#output
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#[async_trait(?Send)]
#parsed
};
@@ -42,7 +42,7 @@ fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
let output = quote! {
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
#parsed
};
@@ -51,7 +51,7 @@ fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
let output = quote! {
#output
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#parsed
};
@@ -60,7 +60,7 @@ fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
let output = quote! {
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
#parsed
};
@@ -73,7 +73,7 @@ fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
let output = quote! {
#output
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#[async_trait(?Send)]
#parsed
};
@@ -81,7 +81,7 @@ fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
output.into()
}
/// Makes a method or every method of a trait `async`, if the `async-interface` feature is enabled.
/// Makes a method or every method of a trait "async" only if the target_arch is "wasm32"
///
/// Requires the `async-trait` crate as a dependency whenever this attribute is used on a trait
/// definition or trait implementation.
@@ -101,18 +101,18 @@ pub fn maybe_async(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
}
/// Awaits, if the `async-interface` feature is enabled.
/// Awaits if target_arch is "wasm32", does nothing otherwise
#[proc_macro]
pub fn maybe_await(expr: TokenStream) -> TokenStream {
let expr: proc_macro2::TokenStream = expr.into();
let quoted = quote! {
{
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
{
#expr
}
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
{
#expr.await
}
@@ -122,20 +122,20 @@ pub fn maybe_await(expr: TokenStream) -> TokenStream {
quoted.into()
}
/// Awaits, if the `async-interface` feature is enabled, uses `tokio::Runtime::block_on()` otherwise
/// Awaits if target_arch is "wasm32", uses `tokio::Runtime::block_on()` otherwise
///
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build.
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build on non-wasm32 platforms.
#[proc_macro]
pub fn await_or_block(expr: TokenStream) -> TokenStream {
let expr: proc_macro2::TokenStream = expr.into();
let quoted = quote! {
{
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
{
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(#expr)
}
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
{
#expr.await
}

View File

@@ -131,7 +131,7 @@ impl GetBlockHash for AnyBlockchain {
impl WalletSync for AnyBlockchain {
fn wallet_sync<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
@@ -144,7 +144,7 @@ impl WalletSync for AnyBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
@@ -178,8 +178,7 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
/// "type" : "electrum",
/// "url" : "ssl://electrum.blockstream.info:50002",
/// "retry": 2,
/// "stop_gap": 20,
/// "validate_domain": true
/// "stop_gap": 20
/// }"#,
/// )
/// .unwrap();
@@ -191,7 +190,6 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
/// socks5: None,
/// timeout: None,
/// stop_gap: 20,
/// validate_domain: true,
/// })
/// );
/// # }

View File

@@ -51,7 +51,6 @@
use std::collections::HashSet;
use std::fmt;
use std::ops::DerefMut;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
@@ -275,7 +274,7 @@ impl WalletSync for CompactFiltersBlockchain {
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let first_peer = &self.peers[0];
@@ -323,9 +322,6 @@ impl WalletSync for CompactFiltersBlockchain {
cf_sync.prepare_sync(Arc::clone(first_peer))?;
let mut database = database.borrow_mut();
let database = database.deref_mut();
let all_scripts = Arc::new(
database
.iter_script_pubkeys(None)?
@@ -580,27 +576,7 @@ pub enum CompactFiltersError {
impl fmt::Display for CompactFiltersError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidResponse => write!(f, "A peer sent an invalid or unexpected response"),
Self::InvalidHeaders => write!(f, "Invalid headers"),
Self::InvalidFilterHeader => write!(f, "Invalid filter header"),
Self::InvalidFilter => write!(f, "Invalid filters"),
Self::MissingBlock => write!(f, "The peer is missing a block in the valid chain"),
Self::BlockHashNotFound => write!(f, "Block hash not found"),
Self::DataCorruption => write!(
f,
"The data stored in the block filters storage are corrupted"
),
Self::NotConnected => write!(f, "A peer is not connected"),
Self::Timeout => write!(f, "A peer took too long to reply to one of our messages"),
Self::PeerBloomDisabled => write!(f, "Peer doesn't advertise the BLOOM service flag"),
Self::NoPeers => write!(f, "No peers have been specified"),
Self::Db(err) => write!(f, "Internal database error: {}", err),
Self::Io(err) => write!(f, "Internal I/O error: {}", err),
Self::Bip158(err) => write!(f, "Invalid BIP158 filter: {}", err),
Self::Time(err) => write!(f, "Invalid system time: {}", err),
Self::Global(err) => write!(f, "Generic error: {}", err),
}
write!(f, "{:?}", self)
}
}

View File

@@ -233,7 +233,7 @@ impl ChainStore<Full> {
batch.put_cf(
cf_handle,
StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
0usize.to_be_bytes(),
&0usize.to_be_bytes(),
);
store.write(batch)?;
}
@@ -302,7 +302,7 @@ impl ChainStore<Full> {
batch.put_cf(
new_cf_handle,
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
from.to_be_bytes(),
&from.to_be_bytes(),
);
batch.put_cf(
new_cf_handle,
@@ -584,7 +584,7 @@ impl<T: StoreType> ChainStore<T> {
batch.put_cf(
cf_handle,
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
(height).to_be_bytes(),
&(height).to_be_bytes(),
);
batch.put_cf(
cf_handle,

View File

@@ -136,7 +136,7 @@ impl CfSync {
let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
assert_eq!(resp.previous_filter_header, checkpoint);
assert!(resp.previous_filter_header == checkpoint);
status =
self.cf_store
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;

View File

@@ -25,7 +25,7 @@
//! ```
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
#[allow(unused_imports)]
use log::{debug, error, info, trace};
@@ -117,11 +117,9 @@ impl GetBlockHash for ElectrumBlockchain {
impl WalletSync for ElectrumBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let mut database = database.borrow_mut();
let database = database.deref_mut();
let mut request = script_sync::start(database, self.stop_gap)?;
let mut block_times = HashMap::<u32, u32>::new();
let mut txid_to_height = HashMap::<Txid, u32>::new();
@@ -283,11 +281,9 @@ impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
.client
.batch_transaction_get(need_fetch.clone())
.map_err(Error::Electrum)?;
let mut txs: HashMap<_, _> = txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
for txid in need_fetch {
if let Some(tx) = txs.remove(txid) {
self.cache.insert(*txid, tx);
}
for (tx, _txid) in txs.into_iter().zip(need_fetch) {
debug_assert_eq!(*_txid, tx.txid());
self.cache.insert(tx.txid(), tx);
}
}
@@ -314,8 +310,6 @@ pub struct ElectrumBlockchainConfig {
pub timeout: Option<u8>,
/// Stop searching addresses for transactions after finding an unused gap of this length
pub stop_gap: usize,
/// Validate the domain when using SSL
pub validate_domain: bool,
}
impl ConfigurableBlockchain for ElectrumBlockchain {
@@ -327,7 +321,6 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
.retry(config.retry)
.timeout(config.timeout)?
.socks5(socks5)?
.validate_domain(config.validate_domain)
.build();
Ok(ElectrumBlockchain {
@@ -422,7 +415,6 @@ mod test {
retry: 0,
timeout: None,
stop_gap: stop_gap,
validate_domain: true,
})
}
}

View File

@@ -12,7 +12,7 @@
//! Esplora by way of `reqwest` HTTP client.
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
use bitcoin::{Transaction, Txid};
@@ -135,12 +135,10 @@ impl GetBlockHash for EsploraBlockchain {
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
use crate::blockchain::script_sync::Request;
let mut database = database.borrow_mut();
let database = database.deref_mut();
let mut request = script_sync::start(database, self.stop_gap)?;
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();

View File

@@ -12,7 +12,6 @@
//! Esplora by way of `ureq` HTTP client.
use std::collections::{HashMap, HashSet};
use std::ops::DerefMut;
#[allow(unused_imports)]
use log::{debug, error, info, trace};
@@ -118,12 +117,10 @@ impl GetBlockHash for EsploraBlockchain {
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
use crate::blockchain::script_sync::Request;
let mut database = database.borrow_mut();
let database = database.deref_mut();
let mut request = script_sync::start(database, self.stop_gap)?;
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
let batch_update = loop {

View File

@@ -16,7 +16,6 @@
//! [Compact Filters/Neutrino](crate::blockchain::compact_filters), along with a generalized trait
//! [`Blockchain`] that can be implemented to build customized backends.
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::sync::mpsc::{channel, Receiver, Sender};
@@ -134,7 +133,7 @@ pub trait WalletSync {
/// Populate the internal database with transactions and UTXOs
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error>;
@@ -157,7 +156,7 @@ pub trait WalletSync {
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
fn wallet_sync<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.wallet_setup(database, progress_update))
@@ -249,8 +248,11 @@ pub trait BlockchainFactory {
/// operations to build a blockchain for a given wallet, so if a wallet needs to be synced
/// often it's recommended to use [`BlockchainFactory::build_for_wallet`] to reuse the same
/// blockchain multiple times.
#[cfg(not(feature = "async-interface"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "async-interface"))))]
#[cfg(not(any(target_arch = "wasm32", feature = "async-interface")))]
#[cfg_attr(
docsrs,
doc(cfg(not(any(target_arch = "wasm32", feature = "async-interface"))))
)]
fn sync_wallet<D: BatchDatabase>(
&self,
wallet: &Wallet<D>,
@@ -375,7 +377,7 @@ impl<T: GetBlockHash> GetBlockHash for Arc<T> {
impl<T: WalletSync> WalletSync for Arc<T> {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_setup(database, progress_update))
@@ -383,7 +385,7 @@ impl<T: WalletSync> WalletSync for Arc<T> {
fn wallet_sync<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_sync(database, progress_update))

View File

@@ -49,9 +49,8 @@ use bitcoincore_rpc::Auth as RpcAuth;
use bitcoincore_rpc::{Client, RpcApi};
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
@@ -193,12 +192,10 @@ impl GetBlockHash for RpcBlockchain {
}
impl WalletSync for RpcBlockchain {
fn wallet_setup<D>(&self, db: &RefCell<D>, prog: Box<dyn Progress>) -> Result<(), Error>
fn wallet_setup<D>(&self, db: &mut D, prog: Box<dyn Progress>) -> Result<(), Error>
where
D: BatchDatabase,
{
let mut db = db.borrow_mut();
let db = db.deref_mut();
let batch = DbState::new(db, &self.sync_params, &*prog)?
.sync_with_core(&self.client, self.is_descriptors)?
.as_db_batch()?;

View File

@@ -49,7 +49,7 @@ pub use memory::MemoryDatabase;
/// Blockchain state at the time of syncing
///
/// Contains only the block time and height at the moment
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SyncTime {
/// Block timestamp and height at the time of sync
pub block_time: BlockTime,

View File

@@ -1137,10 +1137,12 @@ pub mod test {
let child: u32 = row.get(1).unwrap();
let count: usize = row.get(2).unwrap();
assert_eq!(
count, 1,
assert!(
count == 1,
"keychain={}, child={}, count={}",
keychain, child, count
keychain,
child,
count
);
}
}

View File

@@ -133,7 +133,6 @@ pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
mod test {
use super::*;
use crate::descriptor::calc_checksum;
use assert_matches::assert_matches;
// test calc_checksum() function; it should return the same value as Bitcoin Core
#[test]
@@ -156,16 +155,16 @@ mod test {
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
assert_matches!(
calc_checksum(desc),
Err(DescriptorError::InvalidDescriptorChecksum)
);
assert!(matches!(
calc_checksum(desc).err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
assert_matches!(
calc_checksum(desc),
Err(DescriptorError::InvalidDescriptorChecksum)
);
assert!(matches!(
calc_checksum(desc).err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
}
#[test]
@@ -173,9 +172,9 @@ mod test {
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
assert_matches!(
calc_checksum(&invalid_desc),
Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
);
assert!(matches!(
calc_checksum(&invalid_desc).err(),
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
));
}
}

View File

@@ -53,26 +53,7 @@ impl From<crate::keys::KeyError> for Error {
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidHdKeyPath => write!(f, "Invalid HD key path"),
Self::InvalidDescriptorChecksum => {
write!(f, "The provided descriptor doesn't match its checksum")
}
Self::HardenedDerivationXpub => write!(
f,
"The descriptor contains hardened derivation steps on public extended keys"
),
Self::Key(err) => write!(f, "Key error: {}", err),
Self::Policy(err) => write!(f, "Policy error: {}", err),
Self::InvalidDescriptorCharacter(char) => {
write!(f, "Invalid descriptor character: {}", char)
}
Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
Self::Base58(err) => write!(f, "Base58 error: {}", err),
Self::Pk(err) => write!(f, "Key-related error: {}", err),
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
}
write!(f, "{:?}", self)
}
}

View File

@@ -581,7 +581,6 @@ impl DescriptorMeta for ExtendedDescriptor {
mod test {
use std::str::FromStr;
use assert_matches::assert_matches;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::secp256k1::Secp256k1;
@@ -764,11 +763,17 @@ mod test {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.into_wallet_descriptor(&secp, Network::Testnet);
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
assert!(matches!(
desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.into_wallet_descriptor(&secp, Network::Testnet);
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
assert!(matches!(
desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
}
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
@@ -802,11 +807,17 @@ mod test {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
.into_wallet_descriptor(&secp, Network::Bitcoin);
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
assert!(matches!(
desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork))
));
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
.into_wallet_descriptor(&secp, Network::Bitcoin);
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
assert!(matches!(
desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork))
));
}
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
@@ -840,7 +851,11 @@ mod test {
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DescriptorError::HardenedDerivationXpub
));
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);

View File

@@ -512,14 +512,7 @@ pub enum PolicyError {
impl fmt::Display for PolicyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotEnoughItemsSelected(err) => write!(f, "Not enought items selected: {}", err),
Self::IndexOutOfRange(index) => write!(f, "Index out of range: {}", index),
Self::AddOnLeaf => write!(f, "Add on leaf"),
Self::AddOnPartialComplete => write!(f, "Add on partial complete"),
Self::MixedTimelockUnits => write!(f, "Mixed timelock units"),
Self::IncompatibleConditions => write!(f, "Incompatible conditions"),
}
write!(f, "{:?}", self)
}
}
@@ -1146,7 +1139,6 @@ mod test {
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer;
use assert_matches::assert_matches;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32;
use bitcoin::Network;
@@ -1190,8 +1182,8 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
assert_matches!(&policy.contribution, Satisfaction::None);
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc
@@ -1203,8 +1195,10 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
assert!(
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
);
}
// 2 pub keys descriptor, required 2 prv keys
@@ -1223,16 +1217,19 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
// TODO should this be "Satisfaction::None" since we have no prv keys?
// TODO should items and conditions not be empty?
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
assert!(
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
&& m == &2usize
&& items.is_empty()
&& conditions.is_empty()
)
);
}
@@ -1251,15 +1248,18 @@ mod test {
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
assert!(
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
&& m == &2usize
&& items.len() == 1
&& conditions.contains_key(&0)
)
);
}
@@ -1281,15 +1281,18 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &1
&& items.len() == 2
&& conditions.contains_key(&vec![0])
&& conditions.contains_key(&vec![1])
)
);
}
@@ -1310,15 +1313,18 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &2
&& items.len() == 2
&& conditions.contains_key(&vec![0,1])
)
);
}
@@ -1339,8 +1345,8 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
assert_matches!(&policy.contribution, Satisfaction::None);
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc
@@ -1352,8 +1358,10 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
assert!(matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint));
assert!(
matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
);
}
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
@@ -1374,15 +1382,18 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
assert!(
matches!(policy.item, Multisig { keys, threshold } if threshold == 1
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
assert!(
matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
&& m == 1
&& items.len() == 2
&& conditions.contains_key(&vec![0])
&& conditions.contains_key(&vec![1])
)
);
}
@@ -1414,14 +1425,18 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
assert!(
matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2)
);
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
&& m == &2
&& items.len() == 3
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
&& conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
)
);
}
@@ -1584,9 +1599,11 @@ mod test {
.unwrap();
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
assert!(
matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0]
)
);
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
@@ -1596,9 +1613,11 @@ mod test {
.unwrap();
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
assert!(
matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![1]
)
);
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
@@ -1606,9 +1625,11 @@ mod test {
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
.unwrap()
.unwrap();
assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
assert!(
matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0, 1]
)
);
}
@@ -1652,9 +1673,11 @@ mod test {
.extract_policy(&signers_container, build_sat, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
assert!(
matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
&& m == &2
&& items.is_empty()
)
);
//println!("{}", serde_json::to_string(&policy).unwrap());
@@ -1668,9 +1691,11 @@ mod test {
.extract_policy(&signers_container, build_sat_expired, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
assert!(
matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
&& m == &2
&& items == &vec![0]
)
);
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
@@ -1686,9 +1711,11 @@ mod test {
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
assert!(
matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
&& m == &2
&& items == &vec![0, 1]
)
);
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
}
@@ -1763,8 +1790,12 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1]);
assert!(
matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
);
assert!(
matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1])
);
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
@@ -1856,11 +1887,19 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
assert_matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty());
assert!(
matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
);
assert!(
matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty())
);
assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
assert_matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1]);
assert!(
matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
);
assert!(
matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1])
);
let satisfied_items = match policy_signed.item {
SatisfiableItem::Thresh { items, .. } => items,

View File

@@ -235,17 +235,14 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2Pkh(legacy::make_bipxx_public(
44, self.0, self.1, self.2, network,
)?)
.build(network)
P2Pkh(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build(network)
}
}
@@ -287,7 +284,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))`
///
/// This assumes that the key used has already been derived with `m/49'/0'/0'` for Mainnet or `m/49'/1'/0'` for Testnet.
/// This assumes that the key used has already been derived with `m/49'/0'/0'`.
///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
///
@@ -314,17 +311,14 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/0'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(
49, self.0, self.1, self.2, network,
)?)
.build(network)
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build(network)
}
}
@@ -366,7 +360,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)`
///
/// This assumes that the key used has already been derived with `m/84'/0'/0'` for Mainnet or `m/84'/1'/0'` for Testnet.
/// This assumes that the key used has already been derived with `m/84'/0'/0'`.
///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
///
@@ -393,17 +387,14 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2Wpkh(segwit_v0::make_bipxx_public(
84, self.0, self.1, self.2, network,
)?)
.build(network)
P2Wpkh(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build(network)
}
}
@@ -449,7 +440,6 @@ macro_rules! expand_make_bipxx {
key: K,
parent_fingerprint: bip32::Fingerprint,
keychain: KeychainKind,
network: Network,
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
let derivation_path: bip32::DerivationPath = match keychain {
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
@@ -458,10 +448,7 @@ macro_rules! expand_make_bipxx {
let source_path = bip32::DerivationPath::from(vec![
bip32::ChildNumber::from_hardened_idx(bip)?,
match network {
Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?,
_ => bip32::ChildNumber::from_hardened_idx(1)?,
},
bip32::ChildNumber::from_hardened_idx(0)?,
bip32::ChildNumber::from_hardened_idx(0)?,
]);
@@ -483,7 +470,6 @@ mod test {
use super::*;
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks;
use assert_matches::assert_matches;
use bitcoin::network::constants::Network::Regtest;
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use miniscript::Descriptor;
@@ -502,9 +488,9 @@ mod test {
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 });
assert!(matches!(purpose, Hardened { index: 44 }));
let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 0 });
assert!(matches!(coin_type, Hardened { index: 0 }));
}
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
@@ -516,9 +502,9 @@ mod test {
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 });
assert!(matches!(purpose, Hardened { index: 44 }));
let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 1 });
assert!(matches!(coin_type, Hardened { index: 1 }));
}
}

View File

@@ -107,7 +107,7 @@ pub enum Error {
MiniscriptPsbt(MiniscriptPsbtError),
/// BIP32 error
Bip32(bitcoin::util::bip32::Error),
/// A secp256k1 error
/// An ECDSA error
Secp256k1(bitcoin::secp256k1::Error),
/// Error serializing or deserializing JSON data
Json(serde_json::Error),
@@ -157,18 +157,6 @@ pub enum MiniscriptPsbtError {
OutputUpdate(miniscript::psbt::OutputUpdateError),
}
impl fmt::Display for MiniscriptPsbtError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Conversion(err) => write!(f, "Conversion error: {}", err),
Self::UtxoUpdate(err) => write!(f, "UTXO update error: {}", err),
Self::OutputUpdate(err) => write!(f, "Output update error: {}", err),
}
}
}
impl std::error::Error for MiniscriptPsbtError {}
/// Represents the last failed [`crate::blockchain::WalletSync`] sync attempt in which we were short
/// on cached `scriptPubKey`s.
#[derive(Debug)]
@@ -181,93 +169,7 @@ pub struct MissingCachedScripts {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidU32Bytes(_) => write!(
f,
"Wrong number of bytes found when trying to convert to u32"
),
Self::Generic(err) => write!(f, "Generic error: {}", err),
Self::ScriptDoesntHaveAddressForm => write!(f, "Script doesn't have address form"),
Self::NoRecipients => write!(f, "Cannot build tx without recipients"),
Self::NoUtxosSelected => write!(f, "No UTXO selected"),
Self::OutputBelowDustLimit(limit) => {
write!(f, "Output below the dust limit: {}", limit)
}
Self::InsufficientFunds { needed, available } => write!(
f,
"Insufficient funds: {} sat available of {} sat needed",
available, needed
),
Self::BnBTotalTriesExceeded => {
write!(f, "Branch and bound coin selection: total tries exceeded")
}
Self::BnBNoExactMatch => write!(f, "Branch and bound coin selection: not exact match"),
Self::UnknownUtxo => write!(f, "UTXO not found in the internal database"),
Self::TransactionNotFound => {
write!(f, "Transaction not found in the internal database")
}
Self::TransactionConfirmed => write!(f, "Transaction already confirmed"),
Self::IrreplaceableTransaction => write!(f, "Transaction can't be replaced"),
Self::FeeRateTooLow { required } => write!(
f,
"Fee rate too low: required {} sat/vbyte",
required.as_sat_per_vb()
),
Self::FeeTooLow { required } => write!(f, "Fee to low: required {} sat", required),
Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
Self::MissingKeyOrigin(err) => write!(f, "Missing key origin: {}", err),
Self::Key(err) => write!(f, "Key error: {}", err),
Self::ChecksumMismatch => write!(f, "Descriptor checksum mismatch"),
Self::SpendingPolicyRequired(keychain_kind) => {
write!(f, "Spending policy required: {:?}", keychain_kind)
}
Self::InvalidPolicyPathError(err) => write!(f, "Invalid policy path: {}", err),
Self::Signer(err) => write!(f, "Signer error: {}", err),
Self::InvalidNetwork { requested, found } => write!(
f,
"Invalid network: requested {} but found {}",
requested, found
),
#[cfg(feature = "verify")]
Self::Verification(err) => write!(f, "Transaction verification error: {}", err),
Self::InvalidProgressValue(progress) => {
write!(f, "Invalid progress value: {}", progress)
}
Self::ProgressUpdateError => write!(
f,
"Progress update error (maybe the channel has been closed)"
),
Self::InvalidOutpoint(outpoint) => write!(
f,
"Requested outpoint doesn't exist in the tx: {}",
outpoint
),
Self::Descriptor(err) => write!(f, "Descriptor error: {}", err),
Self::Encode(err) => write!(f, "Encoding error: {}", err),
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err),
Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
Self::Secp256k1(err) => write!(f, "Secp256k1 error: {}", err),
Self::Json(err) => write!(f, "Serialize/Deserialize JSON error: {}", err),
Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
Self::Psbt(err) => write!(f, "PSBT error: {}", err),
Self::PsbtParse(err) => write!(f, "Impossible to parse PSBT: {}", err),
Self::MissingCachedScripts(missing_cached_scripts) => {
write!(f, "Missing cached scripts: {:?}", missing_cached_scripts)
}
#[cfg(feature = "electrum")]
Self::Electrum(err) => write!(f, "Electrum client error: {}", err),
#[cfg(feature = "esplora")]
Self::Esplora(err) => write!(f, "Esplora client error: {}", err),
#[cfg(feature = "compact_filters")]
Self::CompactFilters(err) => write!(f, "Compact filters client error: {}", err),
#[cfg(feature = "key-value-db")]
Self::Sled(err) => write!(f, "Sled database error: {}", err),
#[cfg(feature = "rpc")]
Self::Rpc(err) => write!(f, "RPC client error: {}", err),
#[cfg(feature = "sqlite")]
Self::Rusqlite(err) => write!(f, "SQLite error: {}", err),
}
write!(f, "{:?}", self)
}
}

View File

@@ -40,7 +40,7 @@ pub mod bip39;
/// Set of valid networks for a key
pub type ValidNetworks = HashSet<Network>;
/// Create a set containing mainnet, testnet, signet, and regtest
/// Create a set containing mainnet, testnet and regtest
pub fn any_network() -> ValidNetworks {
vec![
Network::Bitcoin,
@@ -95,7 +95,7 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
}
// This method is used internally by `bdk::fragment!` and `bdk::descriptor!`. It has to be
// public because it is effectively called by external crates once the macros are expanded,
// public because it is effectively called by external crates, once the macros are expanded,
// but since it is not meant to be part of the public api we hide it from the docs.
#[doc(hidden)]
pub fn extract(
@@ -375,7 +375,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
/// `(DerivableKey, KeySource, DerivationPath)` tuples.
///
/// For key types that don't encode any indication about the path to use (like bip39), it's
/// generally recommended to implement this trait instead of [`IntoDescriptorKey`]. The same
/// generally recommended to implemented this trait instead of [`IntoDescriptorKey`]. The same
/// rules regarding script context and valid networks apply.
///
/// ## Examples
@@ -935,14 +935,7 @@ impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError);
impl std::fmt::Display for KeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidScriptContext => write!(f, "Invalid script context"),
Self::InvalidNetwork => write!(f, "Invalid network"),
Self::InvalidChecksum => write!(f, "Invalid checksum"),
Self::Message(err) => write!(f, "{}", err),
Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
}
write!(f, "{:?}", self)
}
}

View File

@@ -227,7 +227,7 @@ compile_error!(
#[cfg(feature = "keys-bip39")]
extern crate bip39;
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#[macro_use]
extern crate async_trait;
#[macro_use]
@@ -256,9 +256,6 @@ pub extern crate rusqlite;
#[macro_use]
pub mod testutils;
#[cfg(test)]
extern crate assert_matches;
#[allow(unused_imports)]
#[macro_use]
pub(crate) mod error;

View File

@@ -22,7 +22,7 @@ pub trait PsbtUtils {
/// Get the `TxOut` for the specified input index, if it doesn't exist in the PSBT `None` is returned.
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in sats.
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
/// If the PSBT is missing a TxOut for an input returns None.
fn fee_amount(&self) -> Option<u64>;

View File

@@ -23,7 +23,7 @@ pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
None
}
/// Runs all available tests.
/// Runs all avaliable tests.
fn run(&self) {
let test_client = &mut TestClient::default();

View File

@@ -230,6 +230,7 @@ pub struct TransactionDetails {
pub transaction: Option<Transaction>,
/// Transaction id
pub txid: Txid,
/// Received value (sats)
/// Sum of owned outputs of this transaction.
pub received: u64,
@@ -241,25 +242,11 @@ pub struct TransactionDetails {
/// Server backend, but it could be `None` with a Bitcoin RPC node without txindex that receive
/// funds while offline.
pub fee: Option<u64>,
/// If the transaction is confirmed, contains height and Unix timestamp of the block containing the
/// If the transaction is confirmed, contains height and timestamp of the block containing the
/// transaction, unconfirmed transaction contains `None`.
pub confirmation_time: Option<BlockTime>,
}
impl PartialOrd for TransactionDetails {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TransactionDetails {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.confirmation_time
.cmp(&other.confirmation_time)
.then_with(|| self.txid.cmp(&other.txid))
}
}
/// Block height and timestamp of a block
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct BlockTime {
@@ -269,20 +256,6 @@ pub struct BlockTime {
pub timestamp: u64,
}
impl PartialOrd for BlockTime {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BlockTime {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.height
.cmp(&other.height)
.then_with(|| self.timestamp.cmp(&other.timestamp))
}
}
/// **DEPRECATED**: Confirmation time of a transaction
///
/// The structure has been renamed to `BlockTime`
@@ -361,95 +334,6 @@ impl std::iter::Sum for Balance {
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::hashes::Hash;
#[test]
fn sort_block_time() {
let block_time_a = BlockTime {
height: 100,
timestamp: 100,
};
let block_time_b = BlockTime {
height: 100,
timestamp: 110,
};
let block_time_c = BlockTime {
height: 0,
timestamp: 0,
};
let mut vec = vec![
block_time_a.clone(),
block_time_b.clone(),
block_time_c.clone(),
];
vec.sort();
let expected = vec![block_time_c, block_time_a, block_time_b];
assert_eq!(vec, expected)
}
#[test]
fn sort_tx_details() {
let block_time_a = BlockTime {
height: 100,
timestamp: 100,
};
let block_time_b = BlockTime {
height: 0,
timestamp: 0,
};
let tx_details_a = TransactionDetails {
transaction: None,
txid: Txid::from_inner([0; 32]),
received: 0,
sent: 0,
fee: None,
confirmation_time: None,
};
let tx_details_b = TransactionDetails {
transaction: None,
txid: Txid::from_inner([0; 32]),
received: 0,
sent: 0,
fee: None,
confirmation_time: Some(block_time_a),
};
let tx_details_c = TransactionDetails {
transaction: None,
txid: Txid::from_inner([0; 32]),
received: 0,
sent: 0,
fee: None,
confirmation_time: Some(block_time_b.clone()),
};
let tx_details_d = TransactionDetails {
transaction: None,
txid: Txid::from_inner([1; 32]),
received: 0,
sent: 0,
fee: None,
confirmation_time: Some(block_time_b),
};
let mut vec = vec![
tx_details_a.clone(),
tx_details_b.clone(),
tx_details_c.clone(),
tx_details_d.clone(),
];
vec.sort();
let expected = vec![tx_details_a, tx_details_c, tx_details_d, tx_details_b];
assert_eq!(vec, expected)
}
#[test]
fn can_store_feerate_in_const() {

View File

@@ -102,11 +102,11 @@ use crate::{error::Error, Utxo};
use bitcoin::consensus::encode::serialize;
use bitcoin::Script;
#[cfg(test)]
use assert_matches::assert_matches;
use rand::seq::SliceRandom;
#[cfg(not(test))]
use rand::thread_rng;
#[cfg(test)]
use rand::{rngs::StdRng, SeedableRng};
use std::collections::HashMap;
use std::convert::TryInto;
@@ -671,7 +671,6 @@ impl BranchAndBoundCoinSelection {
optional_utxos.shuffle(&mut thread_rng());
#[cfg(test)]
{
use rand::{rngs::StdRng, SeedableRng};
let seed = [0; 32];
let mut rng: StdRng = SeedableRng::from_seed(seed);
optional_utxos.shuffle(&mut rng);
@@ -1523,22 +1522,24 @@ mod test {
let database = MemoryDatabase::default();
let drain_script = Script::default();
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,
vec![],
utxos,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
);
let err = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
vec![],
utxos,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
)
.unwrap_err();
assert_matches!(
selection,
Err(Error::InsufficientFunds {
assert!(matches!(
err,
Error::InsufficientFunds {
available: 300_000,
..
})
);
}
));
}
#[test]
@@ -1551,22 +1552,24 @@ mod test {
.into_iter()
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,
required,
optional,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
);
let err = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
required,
optional,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
)
.unwrap_err();
assert_matches!(
selection,
Err(Error::InsufficientFunds {
assert!(matches!(
err,
Error::InsufficientFunds {
available: 300_010,
..
})
);
}
));
}
#[test]
@@ -1575,21 +1578,23 @@ mod test {
let database = MemoryDatabase::default();
let drain_script = Script::default();
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,
utxos,
vec![],
FeeRate::from_sat_per_vb(10_000.0),
500_000,
&drain_script,
);
let err = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
utxos,
vec![],
FeeRate::from_sat_per_vb(10_000.0),
500_000,
&drain_script,
)
.unwrap_err();
assert_matches!(
selection,
Err(Error::InsufficientFunds {
assert!(matches!(
err,
Error::InsufficientFunds {
available: 300_010,
..
})
);
}
));
}
}

View File

@@ -24,12 +24,9 @@
//! # 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, HWIChain::Test)?;
//! let devices = HWIClient::enumerate()?;
//! let first_device = devices.first().expect("No devices found!");
//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
//!
//! # let mut wallet = Wallet::new(
//! # "",

View File

@@ -17,7 +17,7 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::{BTreeMap, HashSet};
use std::fmt;
use std::ops::Deref;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use std::sync::Arc;
@@ -132,7 +132,7 @@ pub enum AddressIndex {
Reset(u32),
}
/// A derived address and the index it was found at.
/// A derived address and the index it was found at
/// For convenience this automatically derefs to `Address`
#[derive(Debug, PartialEq, Eq)]
pub struct AddressInfo {
@@ -1084,7 +1084,7 @@ where
}
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's
/// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that has the value true if the PSBT was finalized, or false otherwise.
/// [`SignerOrdering`]
///
/// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
/// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
@@ -1719,11 +1719,14 @@ where
let max_rounds = if has_wildcard { 100 } else { 1 };
for _ in 0..max_rounds {
let sync_res = if run_setup {
maybe_await!(blockchain.wallet_setup(&self.database, new_progress()))
} else {
maybe_await!(blockchain.wallet_sync(&self.database, new_progress()))
};
let sync_res =
if run_setup {
maybe_await!(blockchain
.wallet_setup(self.database.borrow_mut().deref_mut(), new_progress()))
} else {
maybe_await!(blockchain
.wallet_sync(self.database.borrow_mut().deref_mut(), new_progress()))
};
// If the error is the special `MissingCachedScripts` error, we return the number of
// scripts we should ensure cached.
@@ -1848,7 +1851,6 @@ pub fn get_funded_wallet(
#[cfg(test)]
pub(crate) mod test {
use assert_matches::assert_matches;
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
use crate::database::Database;
@@ -4423,9 +4425,11 @@ pub(crate) mod test {
result.is_err(),
"Signing should have failed because the TX uses non-standard sighashes"
);
assert_matches!(
result,
Err(Error::Signer(SignerError::NonStandardSighash)),
assert!(
matches!(
result.unwrap_err(),
Error::Signer(SignerError::NonStandardSighash)
),
"Signing failed with the wrong error type"
);
@@ -4908,10 +4912,16 @@ pub(crate) mod test {
..Default::default()
},
);
assert_matches!(
result,
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
"Signing should have failed with the correct error because the witness_utxo is missing"
assert!(
result.is_err(),
"Signing should have failed because the witness_utxo is missing"
);
assert!(
matches!(
result.unwrap_err(),
Error::Signer(SignerError::MissingWitnessUtxo)
),
"Signing failed with the wrong error type"
);
// restore the witness_utxo
@@ -4925,9 +4935,9 @@ pub(crate) mod test {
},
);
assert_matches!(
result,
Ok(true),
assert!(result.is_ok(), "Signing should have worked");
assert!(
result.unwrap(),
"Should finalize the input since we can produce signatures"
);
}
@@ -5252,9 +5262,11 @@ pub(crate) mod test {
result.is_err(),
"Signing should have failed because the TX uses non-standard sighashes"
);
assert_matches!(
result,
Err(Error::Signer(SignerError::NonStandardSighash)),
assert!(
matches!(
result.unwrap_err(),
Error::Signer(SignerError::NonStandardSighash)
),
"Signing failed with the wrong error type"
);
@@ -5270,9 +5282,11 @@ pub(crate) mod test {
result.is_err(),
"Signing should have failed because the witness_utxo is missing"
);
assert_matches!(
result,
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
assert!(
matches!(
result.unwrap_err(),
Error::Signer(SignerError::MissingWitnessUtxo)
),
"Signing failed with the wrong error type"
);
@@ -5353,26 +5367,26 @@ pub(crate) mod test {
builder
.add_recipient(addr.script_pubkey(), balance.immature / 2)
.current_height(confirmation_time);
assert_matches!(
builder.finish(),
Err(Error::InsufficientFunds {
assert!(matches!(
builder.finish().unwrap_err(),
Error::InsufficientFunds {
needed: _,
available: 0
})
);
}
));
// Still unspendable...
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), balance.immature / 2)
.current_height(not_yet_mature_time);
assert_matches!(
builder.finish(),
Err(Error::InsufficientFunds {
assert!(matches!(
builder.finish().unwrap_err(),
Error::InsufficientFunds {
needed: _,
available: 0
})
);
}
));
// ...Now the coinbase is mature :)
let sync_time = SyncTime {
@@ -5414,7 +5428,10 @@ pub(crate) mod test {
builder.add_recipient(addr.script_pubkey(), 0);
assert_matches!(builder.finish(), Err(Error::OutputBelowDustLimit(0)));
assert!(matches!(
builder.finish().unwrap_err(),
Error::OutputBelowDustLimit(0)
));
let mut builder = wallet.build_tx();
@@ -5529,14 +5546,11 @@ pub(crate) mod test {
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 devices = HWIClient::enumerate().unwrap();
let device = devices.first().expect("No devices found");
let client = HWIClient::get_client(device, true, HWIChain::Regtest).unwrap();
let descriptors = client.get_descriptors(None).unwrap();
let custom_signer = HWISigner::from_device(device, HWIChain::Regtest).unwrap();
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
wallet.add_signer(

View File

@@ -180,22 +180,7 @@ impl From<sighash::Error> for SignerError {
impl fmt::Display for SignerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingKey => write!(f, "Missing private key"),
Self::InvalidKey => write!(f, "The private key in use has the right fingerprint but derives differently than expected"),
Self::UserCanceled => write!(f, "The user canceled the operation"),
Self::InputIndexOutOfRange => write!(f, "Input index out of range"),
Self::MissingNonWitnessUtxo => write!(f, "Missing non-witness UTXO"),
Self::InvalidNonWitnessUtxo => write!(f, "Invalid non-witness UTXO"),
Self::MissingWitnessUtxo => write!(f, "Missing witness UTXO"),
Self::MissingWitnessScript => write!(f, "Missing witness script"),
Self::MissingHdKeypath => write!(f, "Missing fingerprint and derivation path"),
Self::NonStandardSighash => write!(f, "The psbt contains a non standard sighash"),
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),
#[cfg(feature = "hardware-signer")]
Self::HWIError(err) => write!(f, "Error while signing using hardware wallets: {}", err),
}
write!(f, "{:?}", self)
}
}
@@ -1013,7 +998,6 @@ mod signers_container_tests {
use crate::descriptor;
use crate::descriptor::IntoWalletDescriptor;
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use assert_matches::assert_matches;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32;
use bitcoin::Network;
@@ -1083,17 +1067,17 @@ mod signers_container_tests {
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
assert_matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1));
assert_matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2));
assert_matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3));
assert!(matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1)));
assert!(matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2)));
assert!(matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3)));
// The `signer4` has the same ID as `signer3` but lower ordering.
// It should be found by `id3` instead of `signer3`.
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
assert_matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4));
assert!(matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4)));
// Can't find anything with ID that doesn't exist
assert_matches!(signers.find(id_nonexistent), None);
assert!(matches!(signers.find(id_nonexistent), None));
}
#[derive(Debug, Clone, Copy)]

View File

@@ -91,12 +91,7 @@ pub enum VerifyError {
impl fmt::Display for VerifyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingInputTx(txid) => write!(f, "The transaction being spent is not available in the database or the blockchain client: {}", txid),
Self::InvalidInput(outpoint) => write!(f, "The transaction being spent doesn't have the requested output: {}", outpoint),
Self::Consensus(err) => write!(f, "Consensus error: {:?}", err),
Self::Global(err) => write!(f, "Generic error: {}", err),
}
write!(f, "{:?}", self)
}
}
@@ -113,7 +108,6 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
mod test {
use super::*;
use crate::database::{BatchOperations, MemoryDatabase};
use assert_matches::assert_matches;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::{Transaction, Txid};
@@ -143,7 +137,9 @@ mod test {
}
let result = verify_tx(&signed_tx, &database, &blockchain);
assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(),
assert!(result.is_err(), "Should fail with missing input tx");
assert!(
matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid()),
"Error should be a `MissingInputTx` error"
);
@@ -151,9 +147,9 @@ mod test {
database.set_raw_tx(&prev_tx).unwrap();
let result = verify_tx(&unsigned_tx, &database, &blockchain);
assert_matches!(
result,
Err(VerifyError::Consensus(_)),
assert!(result.is_err(), "Should fail since the TX is unsigned");
assert!(
matches!(result, Err(VerifyError::Consensus(_))),
"Error should be a `Consensus` error"
);