Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7231039e81 | ||
|
|
1d840e09f8 | ||
|
|
b6fecc8bc0 | ||
|
|
d0f7543f69 | ||
|
|
7587f1603d | ||
|
|
177c96db5a | ||
|
|
07c1ce9c85 | ||
|
|
6097957650 | ||
|
|
9cffaad71f | ||
|
|
3ccdb84523 | ||
|
|
f7d0852e92 | ||
|
|
15079ee4db | ||
|
|
0f25c6ab29 | ||
|
|
0bf9a0ee2c | ||
|
|
973cd9a286 | ||
|
|
78529b6a42 | ||
|
|
0ad65c7776 | ||
|
|
cbcbdd120d | ||
|
|
f507185729 | ||
|
|
573bf52578 | ||
|
|
10608afb76 | ||
|
|
de46a51208 | ||
|
|
e8acafce8e | ||
|
|
bb2b2d6dd8 | ||
|
|
87c558c9cf | ||
|
|
a4647cfa98 | ||
|
|
b111f97c58 | ||
|
|
7a8e6609b1 | ||
|
|
4ec6f3272e | ||
|
|
553df318ff | ||
|
|
9e2e6411f2 | ||
|
|
5d48e37926 | ||
|
|
c2a42493fd | ||
|
|
0c2570ae07 | ||
|
|
e83bb7c4dc | ||
|
|
46273fe72f | ||
|
|
0b26fa75dc | ||
|
|
35bbe2beef | ||
|
|
9e7bad8afa | ||
|
|
4ada11f358 | ||
|
|
cf8cd2f2b4 | ||
|
|
147a4ed141 | ||
|
|
97f8fe3fd1 | ||
|
|
f0cec015b5 | ||
|
|
ff72078095 | ||
|
|
e678aad3c7 | ||
|
|
41dc7f7d0d | ||
|
|
6b92a169ab | ||
|
|
45d41416ed | ||
|
|
9019793bd4 | ||
|
|
32912eaa05 | ||
|
|
2e7a220e39 | ||
|
|
b02bfb347d | ||
|
|
0cce1ce982 | ||
|
|
fb76c9ed9a | ||
|
|
3a782b3b0d | ||
|
|
eac739d395 | ||
|
|
6e5873ebba | ||
|
|
3205f0c16d | ||
|
|
5f0870a741 | ||
|
|
5a483472c1 | ||
|
|
8d4cc3920a | ||
|
|
14bc9c0e35 | ||
|
|
2451c00268 | ||
|
|
4cad18bbca | ||
|
|
634a0575cb | ||
|
|
d3d07564f2 | ||
|
|
0b768d6f0b | ||
|
|
ec9aefac6b | ||
|
|
d72aa7ebc0 | ||
|
|
99930af12e | ||
|
|
d6e730f18a | ||
|
|
d1e5b87bfc | ||
|
|
c101dea460 | ||
|
|
9ddd502538 | ||
|
|
a5d345fff2 | ||
|
|
11dcc14374 | ||
|
|
4c5ceaff14 | ||
|
|
b5fcddcf1a | ||
|
|
d570ff2c65 | ||
|
|
21c96c9c81 | ||
|
|
c51d544932 | ||
|
|
5e56c3b3c1 | ||
|
|
235961a934 | ||
|
|
df905a8d5e | ||
|
|
8b68cf9546 | ||
|
|
150f4d6f41 | ||
|
|
1c95ca33a8 | ||
|
|
108edc3a6b | ||
|
|
f99a6b9f43 | ||
|
|
aedbc8c97d | ||
|
|
e9bbb8724f |
2
.cargo/audit.toml
Normal file
2
.cargo/audit.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[advisories]
|
||||||
|
ignore = ["RUSTSEC-2022-0046"]
|
||||||
1
.github/ISSUE_TEMPLATE/minor_release.md
vendored
1
.github/ISSUE_TEMPLATE/minor_release.md
vendored
@@ -34,6 +34,7 @@ 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`.
|
- [ ] 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.
|
- [ ] 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`.
|
- 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".
|
- 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`.
|
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR+1` branch to `master`.
|
||||||
- Title PR "Bump version to MAJOR.MINOR+1.0".
|
- Title PR "Bump version to MAJOR.MINOR+1.0".
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/patch_release.md
vendored
1
.github/ISSUE_TEMPLATE/patch_release.md
vendored
@@ -34,6 +34,7 @@ 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`.
|
- [ ] 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.
|
- [ ] 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`.
|
- 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".
|
- 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`.
|
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR_PATCH+1` branch to `master`.
|
||||||
- Title PR "Bump version to MAJOR.MINOR.PATCH+1".
|
- Title PR "Bump version to MAJOR.MINOR.PATCH+1".
|
||||||
|
|||||||
3
.github/workflows/audit.yml
vendored
3
.github/workflows/audit.yml
vendored
@@ -2,6 +2,9 @@ name: Audit
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'release/*'
|
||||||
paths:
|
paths:
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
|
|||||||
10
.github/workflows/code_coverage.yml
vendored
10
.github/workflows/code_coverage.yml
vendored
@@ -1,4 +1,12 @@
|
|||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'release/*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'release/*'
|
||||||
|
|
||||||
name: Code Coverage
|
name: Code Coverage
|
||||||
|
|
||||||
|
|||||||
46
.github/workflows/cont_integration.yml
vendored
46
.github/workflows/cont_integration.yml
vendored
@@ -1,4 +1,12 @@
|
|||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'release/*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'release/*'
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
@@ -10,9 +18,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- version: 1.60.0 # STABLE
|
- version: 1.65.0 # STABLE
|
||||||
clippy: true
|
clippy: true
|
||||||
- version: 1.56.1 # MSRV
|
- version: 1.57.0 # MSRV
|
||||||
features:
|
features:
|
||||||
- default
|
- default
|
||||||
- minimal
|
- minimal
|
||||||
@@ -51,6 +59,16 @@ jobs:
|
|||||||
run: rustup component add clippy
|
run: rustup component add clippy
|
||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
run: rustup update
|
run: rustup update
|
||||||
|
- name: Pin dependencies for MSRV
|
||||||
|
if: matrix.rust.version == '1.57.0'
|
||||||
|
run: |
|
||||||
|
cargo update -p log --precise "0.4.18"
|
||||||
|
cargo update -p tempfile --precise "3.6.0"
|
||||||
|
cargo update -p hashlink --precise "0.8.1"
|
||||||
|
cargo update -p regex --precise "1.7.3"
|
||||||
|
cargo update -p zip --precise "0.6.3"
|
||||||
|
cargo update -p base64ct --precise "1.5.3"
|
||||||
|
cargo update -p rustix --precise "0.37.23"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --features ${{ matrix.features }} --no-default-features
|
run: cargo build --features ${{ matrix.features }} --no-default-features
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
@@ -146,7 +164,7 @@ jobs:
|
|||||||
- run: sudo apt-get update || exit 1
|
- run: sudo apt-get update || exit 1
|
||||||
- run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
|
- run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
|
||||||
- name: Set default toolchain
|
- name: Set default toolchain
|
||||||
run: rustup default 1.56.1 # STABLE
|
run: rustup default 1.65.0 # STABLE
|
||||||
- name: Set profile
|
- name: Set profile
|
||||||
run: rustup set profile minimal
|
run: rustup set profile minimal
|
||||||
- name: Add target wasm32
|
- name: Add target wasm32
|
||||||
@@ -154,7 +172,7 @@ jobs:
|
|||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
run: rustup update
|
run: rustup update
|
||||||
- name: Check
|
- name: Check
|
||||||
run: cargo check --target wasm32-unknown-unknown --features use-esplora-async,dev-getrandom-wasm --no-default-features
|
run: cargo check --target wasm32-unknown-unknown --features async-interface,use-esplora-async,dev-getrandom-wasm --no-default-features
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Rust fmt
|
name: Rust fmt
|
||||||
@@ -173,13 +191,13 @@ jobs:
|
|||||||
- name: Check fmt
|
- name: Check fmt
|
||||||
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
|
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
|
||||||
|
|
||||||
test_harware_wallet:
|
test_hardware_wallet:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- version: 1.60.0 # STABLE
|
- version: 1.65.0 # STABLE
|
||||||
- version: 1.56.1 # MSRV
|
- version: 1.57.0 # MSRV
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -199,5 +217,15 @@ jobs:
|
|||||||
run: rustup set profile minimal
|
run: rustup set profile minimal
|
||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
run: rustup update
|
run: rustup update
|
||||||
|
- name: Pin dependencies for MSRV
|
||||||
|
if: matrix.rust.version == '1.57.0'
|
||||||
|
run: |
|
||||||
|
cargo update -p log --precise "0.4.18"
|
||||||
|
cargo update -p tempfile --precise "3.6.0"
|
||||||
|
cargo update -p hashlink --precise "0.8.1"
|
||||||
|
cargo update -p regex --precise "1.7.3"
|
||||||
|
cargo update -p zip --precise "0.6.3"
|
||||||
|
cargo update -p base64ct --precise "1.5.3"
|
||||||
|
cargo update -p rustix --precise "0.37.23"
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test --features test-hardware-signer
|
run: cargo test --features test-hardware-signer
|
||||||
|
|||||||
12
.github/workflows/nightly_docs.yml
vendored
12
.github/workflows/nightly_docs.yml
vendored
@@ -1,6 +1,14 @@
|
|||||||
name: Publish Nightly Docs
|
name: Publish Nightly Docs
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'release/*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'release/*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_docs:
|
build_docs:
|
||||||
@@ -18,7 +26,7 @@ jobs:
|
|||||||
target
|
target
|
||||||
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
||||||
- name: Set default toolchain
|
- name: Set default toolchain
|
||||||
run: rustup default nightly-2022-01-25
|
run: rustup default nightly-2022-12-14
|
||||||
- name: Set profile
|
- name: Set profile
|
||||||
run: rustup set profile minimal
|
run: rustup set profile minimal
|
||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
|
|||||||
231
CHANGELOG.md
231
CHANGELOG.md
@@ -1,13 +1,182 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
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*"`.
|
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.
|
||||||
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.
|
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.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [v0.21.0] - [v0.20.0]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v0.28.1]
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
This patch release backports (from the BDK 1.0 dev branch) a fix for a bug in the policy condition calculation and adds a new taproot single key descriptor template (BIP-86). The policy condition calculation bug can cause issues when a policy subtree fails due to missing info even if it's not selected when creating a new transaction, errors on unused policy paths are now ignored.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Backported #932 fix for policy condition calculation #1008
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Backported #840 taproot descriptor template (BIP-86) #1033
|
||||||
|
|
||||||
|
## [v0.28.0]
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
Disable default-features for rust-bitcoin and rust-miniscript dependencies, and for rust-esplora-client optional dependency. New default `std` feature must be enabled unless building for wasm.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Bump bip39 crate to v2.0.0 #875
|
||||||
|
- Set default-features = false for rust-bitcoin and rust-miniscript #882
|
||||||
|
- Update esplora client dependency to version 0.4 #884
|
||||||
|
- Added new `std` feature as part of default features #930
|
||||||
|
|
||||||
|
## [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]
|
||||||
|
|
||||||
- Add `descriptor::checksum::get_checksum_bytes` method.
|
- Add `descriptor::checksum::get_checksum_bytes` method.
|
||||||
- Add `Excess` enum to handle remaining amount after coin selection.
|
- Add `Excess` enum to handle remaining amount after coin selection.
|
||||||
@@ -20,7 +189,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- New `RpcBlockchain` implementation with various fixes.
|
- New `RpcBlockchain` implementation with various fixes.
|
||||||
- Return balance in separate categories, namely `confirmed`, `trusted_pending`, `untrusted_pending` & `immature`.
|
- Return balance in separate categories, namely `confirmed`, `trusted_pending`, `untrusted_pending` & `immature`.
|
||||||
|
|
||||||
## [v0.20.0] - [v0.19.0]
|
## [v0.20.0]
|
||||||
|
|
||||||
- New MSRV set to `1.56.1`
|
- 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)
|
- 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)
|
||||||
@@ -31,7 +200,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Deprecate `AddressValidator`
|
- Deprecate `AddressValidator`
|
||||||
- Fix Electrum wallet sync potentially causing address index decrement - compare proposed index and current index before applying batch operations during sync.
|
- 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.18.0]
|
## [v0.19.0]
|
||||||
|
|
||||||
- added `OldestFirstCoinSelection` impl to `CoinSelectionAlgorithm`
|
- added `OldestFirstCoinSelection` impl to `CoinSelectionAlgorithm`
|
||||||
- New MSRV set to `1.56`
|
- New MSRV set to `1.56`
|
||||||
@@ -47,7 +216,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Support for `tr()` descriptors in the `descriptor!()` macro
|
- Support for `tr()` descriptors in the `descriptor!()` macro
|
||||||
- Add support for Bitcoin Core 23.0 when using the `rpc` blockchain
|
- Add support for Bitcoin Core 23.0 when using the `rpc` blockchain
|
||||||
|
|
||||||
## [v0.18.0] - [v0.17.0]
|
## [v0.18.0]
|
||||||
|
|
||||||
- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, i.e. for mobile platforms.
|
- 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.
|
- Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
|
||||||
@@ -57,7 +226,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Rename `WalletExport` to `FullyNodedExport`, deprecate the former.
|
- Rename `WalletExport` to `FullyNodedExport`, deprecate the former.
|
||||||
- Bump `miniscript` dependency version to `^6.1`.
|
- Bump `miniscript` dependency version to `^6.1`.
|
||||||
|
|
||||||
## [v0.17.0] - [v0.16.1]
|
## [v0.17.0]
|
||||||
|
|
||||||
- Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag.
|
- 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`.
|
- `verify` flag removed from `TransactionDetails`.
|
||||||
@@ -78,45 +247,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`.
|
- 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.
|
- remove `flush` method from the `Database` trait.
|
||||||
|
|
||||||
## [v0.16.1] - [v0.16.0]
|
## [v0.16.1]
|
||||||
|
|
||||||
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
|
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
|
||||||
|
|
||||||
## [v0.16.0] - [v0.15.0]
|
## [v0.16.0]
|
||||||
|
|
||||||
- Disable `reqwest` default features.
|
- 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.
|
- 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
|
- Use dust_value from rust-bitcoin
|
||||||
- Fixed generating WIF in the correct network format.
|
- Fixed generating WIF in the correct network format.
|
||||||
|
|
||||||
## [v0.15.0] - [v0.14.0]
|
## [v0.15.0]
|
||||||
|
|
||||||
- Overhauled sync logic for electrum and esplora.
|
- 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.
|
- 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.
|
- Fixed esplora fee estimation.
|
||||||
|
|
||||||
## [v0.14.0] - [v0.13.0]
|
## [v0.14.0]
|
||||||
|
|
||||||
- BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39.
|
- 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.
|
- 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
|
- Update the `Database` trait to store the last sync timestamp and block height
|
||||||
- Rename `ConfirmationTime` to `BlockTime`
|
- Rename `ConfirmationTime` to `BlockTime`
|
||||||
|
|
||||||
## [v0.13.0] - [v0.12.0]
|
## [v0.13.0]
|
||||||
|
|
||||||
- Exposed `get_tx()` method from `Database` to `Wallet`.
|
- Exposed `get_tx()` method from `Database` to `Wallet`.
|
||||||
|
|
||||||
## [v0.12.0] - [v0.11.0]
|
## [v0.12.0]
|
||||||
|
|
||||||
- Activate `miniscript/use-serde` feature to allow consumers of the library to access it via the re-exported `miniscript` crate.
|
- 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`
|
- Add support for proxies in `EsploraBlockchain`
|
||||||
- Added `SqliteDatabase` that implements `Database` backed by a sqlite database using `rusqlite` crate.
|
- Added `SqliteDatabase` that implements `Database` backed by a sqlite database using `rusqlite` crate.
|
||||||
|
|
||||||
## [v0.11.0] - [v0.10.0]
|
## [v0.11.0]
|
||||||
|
|
||||||
- Added `flush` method to the `Database` trait to explicitly flush to disk latest changes on the db.
|
- Added `flush` method to the `Database` trait to explicitly flush to disk latest changes on the db.
|
||||||
|
|
||||||
## [v0.10.0] - [v0.9.0]
|
## [v0.10.0]
|
||||||
|
|
||||||
- Added `RpcBlockchain` in the `AnyBlockchain` struct to allow using Rpc backend where `AnyBlockchain` is used (eg `bdk-cli`)
|
- Added `RpcBlockchain` in the `AnyBlockchain` struct to allow using Rpc backend where `AnyBlockchain` is used (eg `bdk-cli`)
|
||||||
- Removed hard dependency on `tokio`.
|
- Removed hard dependency on `tokio`.
|
||||||
@@ -130,21 +299,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.
|
- 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.
|
- 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.8.0]
|
## [v0.9.0]
|
||||||
|
|
||||||
### Wallet
|
### Wallet
|
||||||
|
|
||||||
- Added Bitcoin core RPC added as blockchain backend
|
- 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
|
- Added a `verify` feature that can be enable to verify the unconfirmed txs we download against the consensus rules
|
||||||
|
|
||||||
## [v0.8.0] - [v0.7.0]
|
## [v0.8.0]
|
||||||
|
|
||||||
### Wallet
|
### Wallet
|
||||||
- Added an option that must be explicitly enabled to allow signing using non-`SIGHASH_ALL` sighashes (#350)
|
- Added an option that must be explicitly enabled to allow signing using non-`SIGHASH_ALL` sighashes (#350)
|
||||||
#### Changed
|
#### Changed
|
||||||
`get_address` now returns an `AddressInfo` struct that includes the index and derefs to `Address`.
|
`get_address` now returns an `AddressInfo` struct that includes the index and derefs to `Address`.
|
||||||
|
|
||||||
## [v0.7.0] - [v0.6.0]
|
## [v0.7.0]
|
||||||
|
|
||||||
### Policy
|
### Policy
|
||||||
#### Changed
|
#### Changed
|
||||||
@@ -159,7 +328,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`
|
- 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.
|
- 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.5.1]
|
## [v0.6.0]
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
#### Changed
|
#### Changed
|
||||||
@@ -183,13 +352,13 @@ Timelocks are considered (optionally) in building the `satisfaction` field
|
|||||||
#### Fixed
|
#### Fixed
|
||||||
- Fixed `coin_select` calculation for UTXOs where `value < fee` that caused over-/underflow errors.
|
- Fixed `coin_select` calculation for UTXOs where `value < fee` that caused over-/underflow errors.
|
||||||
|
|
||||||
## [v0.5.1] - [v0.5.0]
|
## [v0.5.1]
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
#### Changed
|
#### Changed
|
||||||
- Pin `hyper` to `=0.14.4` to make it compile on Rust 1.45
|
- Pin `hyper` to `=0.14.4` to make it compile on Rust 1.45
|
||||||
|
|
||||||
## [v0.5.0] - [v0.4.0]
|
## [v0.5.0]
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
#### Changed
|
#### Changed
|
||||||
@@ -199,7 +368,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
|
|||||||
#### Changed
|
#### Changed
|
||||||
- `FeeRate` constructors `from_sat_per_vb` and `default_min_relay_fee` are now `const` functions
|
- `FeeRate` constructors `from_sat_per_vb` and `default_min_relay_fee` are now `const` functions
|
||||||
|
|
||||||
## [v0.4.0] - [v0.3.0]
|
## [v0.4.0]
|
||||||
|
|
||||||
### Keys
|
### Keys
|
||||||
#### Changed
|
#### Changed
|
||||||
@@ -228,7 +397,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
|
|||||||
- Removed unneeded `Result<(), PolicyError>` return type for `Satisfaction::finalize()`
|
- Removed unneeded `Result<(), PolicyError>` return type for `Satisfaction::finalize()`
|
||||||
- Removed the `TooManyItemsSelected` policy error (see commit message for more details)
|
- Removed the `TooManyItemsSelected` policy error (see commit message for more details)
|
||||||
|
|
||||||
## [v0.3.0] - [v0.2.0]
|
## [v0.3.0]
|
||||||
|
|
||||||
### Descriptor
|
### Descriptor
|
||||||
#### Changed
|
#### Changed
|
||||||
@@ -265,7 +434,7 @@ final transaction is created by calling `finish` on the builder.
|
|||||||
#### Changed
|
#### Changed
|
||||||
- Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository
|
- 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] - [0.1.0-beta.1]
|
## [v0.2.0]
|
||||||
|
|
||||||
### Project
|
### Project
|
||||||
#### Added
|
#### Added
|
||||||
@@ -493,3 +662,13 @@ 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.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.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.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.28.0]: https://github.com/bitcoindevkit/bdk/compare/v0.27.1...v0.28.0
|
||||||
|
[v0.28.1]: https://github.com/bitcoindevkit/bdk/compare/v0.28.0...v0.28.1
|
||||||
|
[Unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.28.1...HEAD
|
||||||
|
|||||||
52
Cargo.toml
52
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "0.24.0"
|
version = "0.28.1"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
@@ -13,9 +13,9 @@ license = "MIT OR Apache-2.0"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk-macros = "^0.6"
|
bdk-macros = "^0.6"
|
||||||
log = "^0.4"
|
log = "0.4"
|
||||||
miniscript = { version = "8.0", features = ["serde"] }
|
miniscript = { version = "9.0", default-features = false, features = ["serde"] }
|
||||||
bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] }
|
bitcoin = { version = "0.29.2", default-features = false, features = ["serde", "base64", "rand"] }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_json = { version = "^1.0" }
|
serde_json = { version = "^1.0" }
|
||||||
rand = "^0.8"
|
rand = "^0.8"
|
||||||
@@ -23,17 +23,17 @@ rand = "^0.8"
|
|||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
sled = { version = "0.34", optional = true }
|
sled = { version = "0.34", optional = true }
|
||||||
electrum-client = { version = "0.12", optional = true }
|
electrum-client = { version = "0.12", optional = true }
|
||||||
esplora-client = { version = "0.2", default-features = false, optional = true }
|
esplora-client = { version = "0.5", default-features = false, optional = true }
|
||||||
rusqlite = { version = "0.27.0", optional = true }
|
rusqlite = { version = "0.28.0", optional = true }
|
||||||
ahash = { version = "0.7.6", optional = true }
|
ahash = { version = "0.7.6", optional = true }
|
||||||
futures = { version = "0.3", optional = true }
|
futures = { version = "0.3", optional = true }
|
||||||
async-trait = { version = "0.1", optional = true }
|
async-trait = { version = "0.1", optional = true }
|
||||||
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
|
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
|
||||||
cc = { version = ">=1.0.64", optional = true }
|
cc = { version = ">=1.0.64", optional = true }
|
||||||
socks = { version = "0.3", optional = true }
|
socks = { version = "0.3", optional = true }
|
||||||
hwi = { version = "0.3.0", optional = true }
|
hwi = { version = "0.5", optional = true, features = ["use-miniscript"] }
|
||||||
|
|
||||||
bip39 = { version = "1.0.1", optional = true }
|
bip39 = { version = "2.0.0", optional = true }
|
||||||
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
||||||
|
|
||||||
# Needed by bdk_blockchain_tests macro and the `rpc` feature
|
# Needed by bdk_blockchain_tests macro and the `rpc` feature
|
||||||
@@ -41,7 +41,7 @@ bitcoincore-rpc = { version = "0.16", optional = true }
|
|||||||
|
|
||||||
# Platform-specific dependencies
|
# Platform-specific dependencies
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
tokio = { version = "1", features = ["rt"] }
|
tokio = { version = "1", features = ["rt", "macros"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
getrandom = "0.2"
|
getrandom = "0.2"
|
||||||
@@ -52,7 +52,10 @@ js-sys = "0.3"
|
|||||||
minimal = []
|
minimal = []
|
||||||
compiler = ["miniscript/compiler"]
|
compiler = ["miniscript/compiler"]
|
||||||
verify = ["bitcoinconsensus"]
|
verify = ["bitcoinconsensus"]
|
||||||
default = ["key-value-db", "electrum"]
|
default = ["std", "key-value-db", "electrum"]
|
||||||
|
# std feature is always required unless building for wasm32-unknown-unknown target
|
||||||
|
# if building for wasm user must add dependencies bitcoin/no-std,miniscript/no-std
|
||||||
|
std = ["bitcoin/std", "miniscript/std"]
|
||||||
sqlite = ["rusqlite", "ahash"]
|
sqlite = ["rusqlite", "ahash"]
|
||||||
sqlite-bundled = ["sqlite", "rusqlite/bundled"]
|
sqlite-bundled = ["sqlite", "rusqlite/bundled"]
|
||||||
compact_filters = ["rocksdb", "socks", "cc"]
|
compact_filters = ["rocksdb", "socks", "cc"]
|
||||||
@@ -104,11 +107,14 @@ test-hardware-signer = ["hardware-signer"]
|
|||||||
dev-getrandom-wasm = ["getrandom/js"]
|
dev-getrandom-wasm = ["getrandom/js"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
miniscript = { version = "9.0", features = ["std"] }
|
||||||
|
bitcoin = { version = "0.29.2", features = ["std"] }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
env_logger = "0.7"
|
env_logger = { version = "0.7", default-features = false }
|
||||||
electrsd = "0.21"
|
electrsd = "0.22"
|
||||||
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
|
# Remove after upgrade to rust-bitcoin ^0.30 where base64 is re-exported
|
||||||
base64 = "^0.13"
|
base64 = "^0.13"
|
||||||
|
assert_matches = "1.5.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "compact_filters_balance"
|
name = "compact_filters_balance"
|
||||||
@@ -138,6 +144,26 @@ name = "hardware_signer"
|
|||||||
path = "examples/hardware_signer.rs"
|
path = "examples/hardware_signer.rs"
|
||||||
required-features = ["electrum", "hardware-signer"]
|
required-features = ["electrum", "hardware-signer"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "electrum_backend"
|
||||||
|
path = "examples/electrum_backend.rs"
|
||||||
|
required-features = ["electrum"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "esplora_backend_synchronous"
|
||||||
|
path = "examples/esplora_backend_synchronous.rs"
|
||||||
|
required-features = ["use-esplora-ureq"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "esplora_backend_asynchronous"
|
||||||
|
path = "examples/esplora_backend_asynchronous.rs"
|
||||||
|
required-features = ["use-esplora-reqwest", "reqwest-default-tls", "async-interface"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "mnemonic_to_descriptors"
|
||||||
|
path = "examples/mnemonic_to_descriptors.rs"
|
||||||
|
required-features = ["all-keys"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["macros"]
|
members = ["macros"]
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -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://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://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://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/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://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://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
|
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -68,12 +68,13 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
```rust
|
```rust
|
||||||
use bdk::{Wallet, database::MemoryDatabase};
|
use bdk::{Wallet, database::MemoryDatabase};
|
||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -96,14 +97,15 @@ use bdk::electrum_client::Client;
|
|||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
|
|
||||||
use base64;
|
use base64;
|
||||||
use bitcoin::consensus::serialize;
|
use bdk::bitcoin::consensus::serialize;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -133,20 +135,21 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
||||||
|
|
||||||
use base64;
|
use base64;
|
||||||
use bitcoin::consensus::deserialize;
|
use bdk::bitcoin::consensus::deserialize;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let psbt = "...";
|
let psbt = "...";
|
||||||
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -198,3 +201,26 @@ at your option.
|
|||||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||||
dual licensed as above, without any additional terms or conditions.
|
dual licensed as above, without any additional terms or conditions.
|
||||||
|
|
||||||
|
## Minimum Supported Rust Version (MSRV)
|
||||||
|
|
||||||
|
This library should compile with any combination of features with Rust 1.57.0.
|
||||||
|
|
||||||
|
To build with the MSRV you will need to pin dependencies as follows:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# log 0.4.19 has MSRV 1.60.0
|
||||||
|
cargo update -p log --precise "0.4.18"
|
||||||
|
# tempfile 3.7.0 has MSRV 1.63.0
|
||||||
|
cargo update -p tempfile --precise "3.6.0"
|
||||||
|
# required for sqlite feature, hashlink 0.8.2 has MSRV 1.61.0
|
||||||
|
cargo update -p hashlink --precise "0.8.1"
|
||||||
|
# required for compact_filters feature, regex after 1.7.3 has MSRV 1.60.0
|
||||||
|
cargo update -p regex --precise "1.7.3"
|
||||||
|
# zip 0.6.3 has MSRV 1.59.0 but still works
|
||||||
|
cargo update -p zip --precise "0.6.3"
|
||||||
|
# base64ct 1.6.0 has MSRV 1.60.0
|
||||||
|
cargo update -p base64ct --precise "1.5.3"
|
||||||
|
# rustix 0.38.0 has MSRV 1.65.0
|
||||||
|
cargo update -p rustix --precise "0.37.23"
|
||||||
|
```
|
||||||
|
|||||||
@@ -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
|
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
|
ADD automation.json /speculos/automation.json
|
||||||
|
|
||||||
ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--display", "headless", "--vnc-port", "41000", "btc.elf"]
|
ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--model", "nanos", "--display", "headless", "--vnc-port", "41000", "btc.elf"]
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use bdk::wallet::AddressIndex::New;
|
|||||||
use bdk::{KeychainKind, Wallet};
|
use bdk::{KeychainKind, Wallet};
|
||||||
|
|
||||||
/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
|
/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
|
||||||
/// rust-miscript library here https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
|
/// rust-miniscript 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
|
/// 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
|
/// into a descriptor. This descriptor then in turn can be used in bdk a fully functioning wallet
|
||||||
/// can be derived from the policy.
|
/// can be derived from the policy.
|
||||||
|
|||||||
87
examples/electrum_backend.rs
Normal file
87
examples/electrum_backend.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||||
|
use bdk::database::MemoryDatabase;
|
||||||
|
use bdk::template::Bip84;
|
||||||
|
use bdk::wallet::export::FullyNodedExport;
|
||||||
|
use bdk::{KeychainKind, SyncOptions, Wallet};
|
||||||
|
|
||||||
|
use bdk::electrum_client::Client;
|
||||||
|
use bdk::wallet::AddressIndex;
|
||||||
|
use bitcoin::util::bip32;
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::utils::tx::build_signed_tx;
|
||||||
|
|
||||||
|
/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server.
|
||||||
|
/// If enough amount is available, this will send a transaction to an address.
|
||||||
|
/// Otherwise, this will display a wallet address to receive funds.
|
||||||
|
///
|
||||||
|
/// This can be run with `cargo run --example electrum_backend` in the root folder.
|
||||||
|
fn main() {
|
||||||
|
let network = Network::Testnet;
|
||||||
|
|
||||||
|
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||||
|
|
||||||
|
let electrum_url = "ssl://electrum.blockstream.info:60002";
|
||||||
|
|
||||||
|
run(&network, electrum_url, xpriv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||||
|
Wallet::new(
|
||||||
|
Bip84(*xpriv, KeychainKind::External),
|
||||||
|
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||||
|
*network,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(network: &Network, electrum_url: &str, xpriv: &str) {
|
||||||
|
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||||
|
|
||||||
|
// Apparently it works only with Electrs (not EletrumX)
|
||||||
|
let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap());
|
||||||
|
|
||||||
|
let wallet = create_wallet(network, &xpriv);
|
||||||
|
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
|
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
|
println!("address: {}", address);
|
||||||
|
|
||||||
|
let balance = wallet.get_balance().unwrap();
|
||||||
|
|
||||||
|
println!("Available coins in BDK wallet : {} sats", balance);
|
||||||
|
|
||||||
|
if balance.confirmed > 6500 {
|
||||||
|
// the wallet sends the amount to itself.
|
||||||
|
let recipient_address = wallet
|
||||||
|
.get_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let amount = 5359;
|
||||||
|
|
||||||
|
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||||
|
|
||||||
|
blockchain.broadcast(&tx).unwrap();
|
||||||
|
|
||||||
|
println!("tx id: {}", tx.txid());
|
||||||
|
} else {
|
||||||
|
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||||
|
}
|
||||||
|
|
||||||
|
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||||
|
.map_err(ToString::to_string)
|
||||||
|
.map_err(bdk::Error::Generic)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("------\nWallet Backup: {}", export.to_string());
|
||||||
|
}
|
||||||
93
examples/esplora_backend_asynchronous.rs
Normal file
93
examples/esplora_backend_asynchronous.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::blockchain::Blockchain;
|
||||||
|
use bdk::{
|
||||||
|
blockchain::esplora::EsploraBlockchain,
|
||||||
|
database::MemoryDatabase,
|
||||||
|
template::Bip84,
|
||||||
|
wallet::{export::FullyNodedExport, AddressIndex},
|
||||||
|
KeychainKind, SyncOptions, Wallet,
|
||||||
|
};
|
||||||
|
use bitcoin::{
|
||||||
|
util::bip32::{self, ExtendedPrivKey},
|
||||||
|
Network,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::utils::tx::build_signed_tx;
|
||||||
|
|
||||||
|
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
|
||||||
|
/// using non blocking asynchronous calls with `reqwest`.
|
||||||
|
/// If enough amount is available, this will send a transaction to an address.
|
||||||
|
/// Otherwise, this will display a wallet address to receive funds.
|
||||||
|
///
|
||||||
|
/// This can be run with `cargo run --no-default-features --features="use-esplora-reqwest, reqwest-default-tls, async-interface" --example esplora_backend_asynchronous`
|
||||||
|
/// in the root folder.
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() {
|
||||||
|
let network = Network::Signet;
|
||||||
|
|
||||||
|
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||||
|
|
||||||
|
let esplora_url = "https://explorer.bc-2.jp/api";
|
||||||
|
|
||||||
|
run(&network, esplora_url, xpriv).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||||
|
Wallet::new(
|
||||||
|
Bip84(*xpriv, KeychainKind::External),
|
||||||
|
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||||
|
*network,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(network: &Network, esplora_url: &str, xpriv: &str) {
|
||||||
|
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||||
|
|
||||||
|
let blockchain = EsploraBlockchain::new(esplora_url, 20);
|
||||||
|
|
||||||
|
let wallet = create_wallet(network, &xpriv);
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.sync(&blockchain, SyncOptions::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
|
println!("address: {}", address);
|
||||||
|
|
||||||
|
let balance = wallet.get_balance().unwrap();
|
||||||
|
|
||||||
|
println!("Available coins in BDK wallet : {} sats", balance);
|
||||||
|
|
||||||
|
if balance.confirmed > 10500 {
|
||||||
|
// the wallet sends the amount to itself.
|
||||||
|
let recipient_address = wallet
|
||||||
|
.get_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let amount = 9359;
|
||||||
|
|
||||||
|
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||||
|
|
||||||
|
let _ = blockchain.broadcast(&tx);
|
||||||
|
|
||||||
|
println!("tx id: {}", tx.txid());
|
||||||
|
} else {
|
||||||
|
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||||
|
}
|
||||||
|
|
||||||
|
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||||
|
.map_err(ToString::to_string)
|
||||||
|
.map_err(bdk::Error::Generic)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("------\nWallet Backup: {}", export.to_string());
|
||||||
|
}
|
||||||
89
examples/esplora_backend_synchronous.rs
Normal file
89
examples/esplora_backend_synchronous.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::blockchain::Blockchain;
|
||||||
|
use bdk::{
|
||||||
|
blockchain::esplora::EsploraBlockchain,
|
||||||
|
database::MemoryDatabase,
|
||||||
|
template::Bip84,
|
||||||
|
wallet::{export::FullyNodedExport, AddressIndex},
|
||||||
|
KeychainKind, SyncOptions, Wallet,
|
||||||
|
};
|
||||||
|
use bitcoin::{
|
||||||
|
util::bip32::{self, ExtendedPrivKey},
|
||||||
|
Network,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::utils::tx::build_signed_tx;
|
||||||
|
|
||||||
|
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
|
||||||
|
/// using blocking calls with `ureq`.
|
||||||
|
/// If enough amount is available, this will send a transaction to an address.
|
||||||
|
/// Otherwise, this will display a wallet address to receive funds.
|
||||||
|
///
|
||||||
|
/// This can be run with `cargo run --features=use-esplora-ureq --example esplora_backend_synchronous`
|
||||||
|
/// in the root folder.
|
||||||
|
fn main() {
|
||||||
|
let network = Network::Signet;
|
||||||
|
|
||||||
|
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||||
|
|
||||||
|
let esplora_url = "https://explorer.bc-2.jp/api";
|
||||||
|
|
||||||
|
run(&network, esplora_url, xpriv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||||
|
Wallet::new(
|
||||||
|
Bip84(*xpriv, KeychainKind::External),
|
||||||
|
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||||
|
*network,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(network: &Network, esplora_url: &str, xpriv: &str) {
|
||||||
|
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||||
|
|
||||||
|
let blockchain = EsploraBlockchain::new(esplora_url, 20);
|
||||||
|
|
||||||
|
let wallet = create_wallet(network, &xpriv);
|
||||||
|
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
|
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
|
println!("address: {}", address);
|
||||||
|
|
||||||
|
let balance = wallet.get_balance().unwrap();
|
||||||
|
|
||||||
|
println!("Available coins in BDK wallet : {} sats", balance);
|
||||||
|
|
||||||
|
if balance.confirmed > 10500 {
|
||||||
|
// the wallet sends the amount to itself.
|
||||||
|
let recipient_address = wallet
|
||||||
|
.get_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let amount = 9359;
|
||||||
|
|
||||||
|
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||||
|
|
||||||
|
blockchain.broadcast(&tx).unwrap();
|
||||||
|
|
||||||
|
println!("tx id: {}", tx.txid());
|
||||||
|
} else {
|
||||||
|
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||||
|
}
|
||||||
|
|
||||||
|
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||||
|
.map_err(ToString::to_string)
|
||||||
|
.map_err(bdk::Error::Generic)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("------\nWallet Backup: {}", export.to_string());
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ use bdk::bitcoin::{Address, Network};
|
|||||||
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::hwi::{types::HWIChain, HWIClient};
|
use bdk::hwi::{types::HWIChain, HWIClient};
|
||||||
|
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
|
||||||
use bdk::signer::SignerOrdering;
|
use bdk::signer::SignerOrdering;
|
||||||
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
||||||
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
||||||
@@ -23,26 +24,27 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
println!("Hold tight, I'm connecting to your hardware wallet...");
|
println!("Hold tight, I'm connecting to your hardware wallet...");
|
||||||
|
|
||||||
// Listing all the available hardware wallet devices...
|
// Listing all the available hardware wallet devices...
|
||||||
let devices = HWIClient::enumerate()?;
|
let mut devices = HWIClient::enumerate()?;
|
||||||
let first_device = devices
|
if devices.is_empty() {
|
||||||
.first()
|
panic!("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
||||||
.expect("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
}
|
||||||
|
let first_device = devices.remove(0)?;
|
||||||
// ...and creating a client out of the first one
|
// ...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);
|
println!("Look what I found, a {}!", first_device.model);
|
||||||
|
|
||||||
// Getting the HW's public descriptors
|
// Getting the HW's public descriptors
|
||||||
let descriptors = client.get_descriptors(None)?;
|
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
|
||||||
println!(
|
println!(
|
||||||
"The hardware wallet's descriptor is: {}",
|
"The hardware wallet's descriptor is: {}",
|
||||||
descriptors.receive[0]
|
descriptors.receive[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Creating a custom signer from the device
|
// 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(
|
let mut wallet = Wallet::new(
|
||||||
&descriptors.receive[0],
|
descriptors.receive[0].clone(),
|
||||||
Some(&descriptors.internal[0]),
|
Some(descriptors.internal[0].clone()),
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
60
examples/mnemonic_to_descriptors.rs
Normal file
60
examples/mnemonic_to_descriptors.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
|
||||||
|
//
|
||||||
|
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||||
|
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||||
|
// You may not use this file except in accordance with one or both of these
|
||||||
|
// licenses.
|
||||||
|
|
||||||
|
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||||
|
use bdk::bitcoin::util::bip32::DerivationPath;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
use bdk::descriptor;
|
||||||
|
use bdk::descriptor::IntoWalletDescriptor;
|
||||||
|
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
|
||||||
|
use bdk::keys::{GeneratableKey, GeneratedKey};
|
||||||
|
use bdk::miniscript::Tap;
|
||||||
|
use bdk::Error as BDK_Error;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// This example demonstrates how to generate a mnemonic phrase
|
||||||
|
/// using BDK and use that to generate a descriptor string.
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
|
// In this example we are generating a 12 words mnemonic phrase
|
||||||
|
// but it is also possible generate 15, 18, 21 and 24 words
|
||||||
|
// using their respective `WordCount` variant.
|
||||||
|
let mnemonic: GeneratedKey<_, Tap> =
|
||||||
|
Mnemonic::generate((WordCount::Words12, Language::English))
|
||||||
|
.map_err(|_| BDK_Error::Generic("Mnemonic generation error".to_string()))?;
|
||||||
|
|
||||||
|
println!("Mnemonic phrase: {}", *mnemonic);
|
||||||
|
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();
|
||||||
|
|
||||||
|
// generate external and internal descriptor from mnemonic
|
||||||
|
let (external_descriptor, ext_keymap) =
|
||||||
|
descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))?
|
||||||
|
.into_wallet_descriptor(&secp, Network::Testnet)?;
|
||||||
|
let (internal_descriptor, int_keymap) =
|
||||||
|
descriptor!(tr((mnemonic_with_passphrase, internal_path)))?
|
||||||
|
.into_wallet_descriptor(&secp, Network::Testnet)?;
|
||||||
|
|
||||||
|
println!("tpub external descriptor: {}", external_descriptor);
|
||||||
|
println!("tpub internal descriptor: {}", internal_descriptor);
|
||||||
|
println!(
|
||||||
|
"tprv external descriptor: {}",
|
||||||
|
external_descriptor.to_string_with_secret(&ext_keymap)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"tprv internal descriptor: {}",
|
||||||
|
internal_descriptor.to_string_with_secret(&int_keymap)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
30
examples/utils/mod.rs
Normal file
30
examples/utils/mod.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
pub(crate) mod tx {
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::{database::BatchDatabase, SignOptions, Wallet};
|
||||||
|
use bitcoin::{Address, Transaction};
|
||||||
|
|
||||||
|
pub fn build_signed_tx<D: BatchDatabase>(
|
||||||
|
wallet: &Wallet<D>,
|
||||||
|
recipient_address: &str,
|
||||||
|
amount: u64,
|
||||||
|
) -> Transaction {
|
||||||
|
// Create a transaction builder
|
||||||
|
let mut tx_builder = wallet.build_tx();
|
||||||
|
|
||||||
|
let to_address = Address::from_str(recipient_address).unwrap();
|
||||||
|
|
||||||
|
// Set recipient of the transaction
|
||||||
|
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);
|
||||||
|
|
||||||
|
// Finalise the transaction and extract PSBT
|
||||||
|
let (mut psbt, _) = tx_builder.finish().unwrap();
|
||||||
|
|
||||||
|
// Sign the above psbt with signing option
|
||||||
|
wallet.sign(&mut psbt, SignOptions::default()).unwrap();
|
||||||
|
|
||||||
|
// Extract the final transaction
|
||||||
|
psbt.extract_tx()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ use syn::{parse, ImplItemMethod, ItemImpl, ItemTrait, Token};
|
|||||||
|
|
||||||
fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
|
fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
|
#[cfg(not(feature = "async-interface"))]
|
||||||
#parsed
|
#parsed
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
|
|||||||
let output = quote! {
|
let output = quote! {
|
||||||
#output
|
#output
|
||||||
|
|
||||||
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
|
#[cfg(feature = "async-interface")]
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
#parsed
|
#parsed
|
||||||
};
|
};
|
||||||
@@ -42,7 +42,7 @@ fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
|
|||||||
|
|
||||||
fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
|
fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
|
#[cfg(not(feature = "async-interface"))]
|
||||||
#parsed
|
#parsed
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
|
|||||||
let output = quote! {
|
let output = quote! {
|
||||||
#output
|
#output
|
||||||
|
|
||||||
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
|
#[cfg(feature = "async-interface")]
|
||||||
#parsed
|
#parsed
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
|
|||||||
|
|
||||||
fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
|
fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
|
#[cfg(not(feature = "async-interface"))]
|
||||||
#parsed
|
#parsed
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
|
|||||||
let output = quote! {
|
let output = quote! {
|
||||||
#output
|
#output
|
||||||
|
|
||||||
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
|
#[cfg(feature = "async-interface")]
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
#parsed
|
#parsed
|
||||||
};
|
};
|
||||||
@@ -81,7 +81,7 @@ fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
|
|||||||
output.into()
|
output.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a method or every method of a trait "async" only if the target_arch is "wasm32"
|
/// Makes a method or every method of a trait `async`, if the `async-interface` feature is enabled.
|
||||||
///
|
///
|
||||||
/// Requires the `async-trait` crate as a dependency whenever this attribute is used on a trait
|
/// Requires the `async-trait` crate as a dependency whenever this attribute is used on a trait
|
||||||
/// definition or trait implementation.
|
/// definition or trait implementation.
|
||||||
@@ -101,18 +101,18 @@ pub fn maybe_async(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Awaits if target_arch is "wasm32", does nothing otherwise
|
/// Awaits, if the `async-interface` feature is enabled.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn maybe_await(expr: TokenStream) -> TokenStream {
|
pub fn maybe_await(expr: TokenStream) -> TokenStream {
|
||||||
let expr: proc_macro2::TokenStream = expr.into();
|
let expr: proc_macro2::TokenStream = expr.into();
|
||||||
let quoted = quote! {
|
let quoted = quote! {
|
||||||
{
|
{
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
|
#[cfg(not(feature = "async-interface"))]
|
||||||
{
|
{
|
||||||
#expr
|
#expr
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
|
#[cfg(feature = "async-interface")]
|
||||||
{
|
{
|
||||||
#expr.await
|
#expr.await
|
||||||
}
|
}
|
||||||
@@ -122,20 +122,20 @@ pub fn maybe_await(expr: TokenStream) -> TokenStream {
|
|||||||
quoted.into()
|
quoted.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Awaits if target_arch is "wasm32", uses `tokio::Runtime::block_on()` otherwise
|
/// Awaits, if the `async-interface` feature is enabled, uses `tokio::Runtime::block_on()` otherwise
|
||||||
///
|
///
|
||||||
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build on non-wasm32 platforms.
|
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn await_or_block(expr: TokenStream) -> TokenStream {
|
pub fn await_or_block(expr: TokenStream) -> TokenStream {
|
||||||
let expr: proc_macro2::TokenStream = expr.into();
|
let expr: proc_macro2::TokenStream = expr.into();
|
||||||
let quoted = quote! {
|
let quoted = quote! {
|
||||||
{
|
{
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
|
#[cfg(not(feature = "async-interface"))]
|
||||||
{
|
{
|
||||||
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(#expr)
|
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(#expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
|
#[cfg(feature = "async-interface")]
|
||||||
{
|
{
|
||||||
#expr.await
|
#expr.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ impl GetBlockHash for AnyBlockchain {
|
|||||||
impl WalletSync for AnyBlockchain {
|
impl WalletSync for AnyBlockchain {
|
||||||
fn wallet_sync<D: BatchDatabase>(
|
fn wallet_sync<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
progress_update: Box<dyn Progress>,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
maybe_await!(impl_inner_method!(
|
maybe_await!(impl_inner_method!(
|
||||||
@@ -144,7 +144,7 @@ impl WalletSync for AnyBlockchain {
|
|||||||
|
|
||||||
fn wallet_setup<D: BatchDatabase>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
progress_update: Box<dyn Progress>,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
maybe_await!(impl_inner_method!(
|
maybe_await!(impl_inner_method!(
|
||||||
@@ -178,7 +178,8 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
|||||||
/// "type" : "electrum",
|
/// "type" : "electrum",
|
||||||
/// "url" : "ssl://electrum.blockstream.info:50002",
|
/// "url" : "ssl://electrum.blockstream.info:50002",
|
||||||
/// "retry": 2,
|
/// "retry": 2,
|
||||||
/// "stop_gap": 20
|
/// "stop_gap": 20,
|
||||||
|
/// "validate_domain": true
|
||||||
/// }"#,
|
/// }"#,
|
||||||
/// )
|
/// )
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
@@ -190,6 +191,7 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
|||||||
/// socks5: None,
|
/// socks5: None,
|
||||||
/// timeout: None,
|
/// timeout: None,
|
||||||
/// stop_gap: 20,
|
/// stop_gap: 20,
|
||||||
|
/// validate_domain: true,
|
||||||
/// })
|
/// })
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
/// # }
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::DerefMut;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -274,7 +275,7 @@ impl WalletSync for CompactFiltersBlockchain {
|
|||||||
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
|
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
|
||||||
fn wallet_setup<D: BatchDatabase>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
progress_update: Box<dyn Progress>,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let first_peer = &self.peers[0];
|
let first_peer = &self.peers[0];
|
||||||
@@ -322,6 +323,9 @@ impl WalletSync for CompactFiltersBlockchain {
|
|||||||
|
|
||||||
cf_sync.prepare_sync(Arc::clone(first_peer))?;
|
cf_sync.prepare_sync(Arc::clone(first_peer))?;
|
||||||
|
|
||||||
|
let mut database = database.borrow_mut();
|
||||||
|
let database = database.deref_mut();
|
||||||
|
|
||||||
let all_scripts = Arc::new(
|
let all_scripts = Arc::new(
|
||||||
database
|
database
|
||||||
.iter_script_pubkeys(None)?
|
.iter_script_pubkeys(None)?
|
||||||
@@ -576,7 +580,27 @@ pub enum CompactFiltersError {
|
|||||||
|
|
||||||
impl fmt::Display for CompactFiltersError {
|
impl fmt::Display for CompactFiltersError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ impl ChainStore<Full> {
|
|||||||
batch.put_cf(
|
batch.put_cf(
|
||||||
cf_handle,
|
cf_handle,
|
||||||
StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
|
StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
|
||||||
&0usize.to_be_bytes(),
|
0usize.to_be_bytes(),
|
||||||
);
|
);
|
||||||
store.write(batch)?;
|
store.write(batch)?;
|
||||||
}
|
}
|
||||||
@@ -302,7 +302,7 @@ impl ChainStore<Full> {
|
|||||||
batch.put_cf(
|
batch.put_cf(
|
||||||
new_cf_handle,
|
new_cf_handle,
|
||||||
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
|
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
|
||||||
&from.to_be_bytes(),
|
from.to_be_bytes(),
|
||||||
);
|
);
|
||||||
batch.put_cf(
|
batch.put_cf(
|
||||||
new_cf_handle,
|
new_cf_handle,
|
||||||
@@ -584,7 +584,7 @@ impl<T: StoreType> ChainStore<T> {
|
|||||||
batch.put_cf(
|
batch.put_cf(
|
||||||
cf_handle,
|
cf_handle,
|
||||||
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
|
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
|
||||||
&(height).to_be_bytes(),
|
(height).to_be_bytes(),
|
||||||
);
|
);
|
||||||
batch.put_cf(
|
batch.put_cf(
|
||||||
cf_handle,
|
cf_handle,
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ impl CfSync {
|
|||||||
|
|
||||||
let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
|
let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
|
||||||
|
|
||||||
assert!(resp.previous_filter_header == checkpoint);
|
assert_eq!(resp.previous_filter_header, checkpoint);
|
||||||
status =
|
status =
|
||||||
self.cf_store
|
self.cf_store
|
||||||
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
|
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ops::Deref;
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
@@ -117,9 +117,11 @@ impl GetBlockHash for ElectrumBlockchain {
|
|||||||
impl WalletSync for ElectrumBlockchain {
|
impl WalletSync for ElectrumBlockchain {
|
||||||
fn wallet_setup<D: BatchDatabase>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
_progress_update: Box<dyn Progress>,
|
_progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> 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 request = script_sync::start(database, self.stop_gap)?;
|
||||||
let mut block_times = HashMap::<u32, u32>::new();
|
let mut block_times = HashMap::<u32, u32>::new();
|
||||||
let mut txid_to_height = HashMap::<Txid, u32>::new();
|
let mut txid_to_height = HashMap::<Txid, u32>::new();
|
||||||
@@ -281,9 +283,11 @@ impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
|
|||||||
.client
|
.client
|
||||||
.batch_transaction_get(need_fetch.clone())
|
.batch_transaction_get(need_fetch.clone())
|
||||||
.map_err(Error::Electrum)?;
|
.map_err(Error::Electrum)?;
|
||||||
for (tx, _txid) in txs.into_iter().zip(need_fetch) {
|
let mut txs: HashMap<_, _> = txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
|
||||||
debug_assert_eq!(*_txid, tx.txid());
|
for txid in need_fetch {
|
||||||
self.cache.insert(tx.txid(), tx);
|
if let Some(tx) = txs.remove(txid) {
|
||||||
|
self.cache.insert(*txid, tx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +314,8 @@ pub struct ElectrumBlockchainConfig {
|
|||||||
pub timeout: Option<u8>,
|
pub timeout: Option<u8>,
|
||||||
/// Stop searching addresses for transactions after finding an unused gap of this length
|
/// Stop searching addresses for transactions after finding an unused gap of this length
|
||||||
pub stop_gap: usize,
|
pub stop_gap: usize,
|
||||||
|
/// Validate the domain when using SSL
|
||||||
|
pub validate_domain: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableBlockchain for ElectrumBlockchain {
|
impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||||
@@ -321,6 +327,7 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
|
|||||||
.retry(config.retry)
|
.retry(config.retry)
|
||||||
.timeout(config.timeout)?
|
.timeout(config.timeout)?
|
||||||
.socks5(socks5)?
|
.socks5(socks5)?
|
||||||
|
.validate_domain(config.validate_domain)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Ok(ElectrumBlockchain {
|
Ok(ElectrumBlockchain {
|
||||||
@@ -415,6 +422,7 @@ mod test {
|
|||||||
retry: 0,
|
retry: 0,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
stop_gap: stop_gap,
|
stop_gap: stop_gap,
|
||||||
|
validate_domain: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
//! Esplora by way of `reqwest` HTTP client.
|
//! Esplora by way of `reqwest` HTTP client.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ops::Deref;
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use bitcoin::{Transaction, Txid};
|
use bitcoin::{Transaction, Txid};
|
||||||
|
|
||||||
@@ -135,10 +135,12 @@ impl GetBlockHash for EsploraBlockchain {
|
|||||||
impl WalletSync for EsploraBlockchain {
|
impl WalletSync for EsploraBlockchain {
|
||||||
fn wallet_setup<D: BatchDatabase>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
_progress_update: Box<dyn Progress>,
|
_progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use crate::blockchain::script_sync::Request;
|
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 request = script_sync::start(database, self.stop_gap)?;
|
||||||
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
|
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
//! Esplora by way of `ureq` HTTP client.
|
//! Esplora by way of `ureq` HTTP client.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
@@ -117,10 +118,12 @@ impl GetBlockHash for EsploraBlockchain {
|
|||||||
impl WalletSync for EsploraBlockchain {
|
impl WalletSync for EsploraBlockchain {
|
||||||
fn wallet_setup<D: BatchDatabase>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
_progress_update: Box<dyn Progress>,
|
_progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use crate::blockchain::script_sync::Request;
|
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 request = script_sync::start(database, self.stop_gap)?;
|
||||||
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
|
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
|
||||||
let batch_update = loop {
|
let batch_update = loop {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
//! [Compact Filters/Neutrino](crate::blockchain::compact_filters), along with a generalized trait
|
//! [Compact Filters/Neutrino](crate::blockchain::compact_filters), along with a generalized trait
|
||||||
//! [`Blockchain`] that can be implemented to build customized backends.
|
//! [`Blockchain`] that can be implemented to build customized backends.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
@@ -133,7 +134,7 @@ pub trait WalletSync {
|
|||||||
/// Populate the internal database with transactions and UTXOs
|
/// Populate the internal database with transactions and UTXOs
|
||||||
fn wallet_setup<D: BatchDatabase>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
progress_update: Box<dyn Progress>,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ pub trait WalletSync {
|
|||||||
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
|
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
|
||||||
fn wallet_sync<D: BatchDatabase>(
|
fn wallet_sync<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
progress_update: Box<dyn Progress>,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
maybe_await!(self.wallet_setup(database, progress_update))
|
maybe_await!(self.wallet_setup(database, progress_update))
|
||||||
@@ -248,11 +249,8 @@ pub trait BlockchainFactory {
|
|||||||
/// operations to build a blockchain for a given wallet, so if a wallet needs to be synced
|
/// 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
|
/// often it's recommended to use [`BlockchainFactory::build_for_wallet`] to reuse the same
|
||||||
/// blockchain multiple times.
|
/// blockchain multiple times.
|
||||||
#[cfg(not(any(target_arch = "wasm32", feature = "async-interface")))]
|
#[cfg(not(feature = "async-interface"))]
|
||||||
#[cfg_attr(
|
#[cfg_attr(docsrs, doc(cfg(not(feature = "async-interface"))))]
|
||||||
docsrs,
|
|
||||||
doc(cfg(not(any(target_arch = "wasm32", feature = "async-interface"))))
|
|
||||||
)]
|
|
||||||
fn sync_wallet<D: BatchDatabase>(
|
fn sync_wallet<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
wallet: &Wallet<D>,
|
wallet: &Wallet<D>,
|
||||||
@@ -377,7 +375,7 @@ impl<T: GetBlockHash> GetBlockHash for Arc<T> {
|
|||||||
impl<T: WalletSync> WalletSync for Arc<T> {
|
impl<T: WalletSync> WalletSync for Arc<T> {
|
||||||
fn wallet_setup<D: BatchDatabase>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
progress_update: Box<dyn Progress>,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
maybe_await!(self.deref().wallet_setup(database, progress_update))
|
maybe_await!(self.deref().wallet_setup(database, progress_update))
|
||||||
@@ -385,7 +383,7 @@ impl<T: WalletSync> WalletSync for Arc<T> {
|
|||||||
|
|
||||||
fn wallet_sync<D: BatchDatabase>(
|
fn wallet_sync<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &RefCell<D>,
|
||||||
progress_update: Box<dyn Progress>,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
maybe_await!(self.deref().wallet_sync(database, progress_update))
|
maybe_await!(self.deref().wallet_sync(database, progress_update))
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ use bitcoincore_rpc::Auth as RpcAuth;
|
|||||||
use bitcoincore_rpc::{Client, RpcApi};
|
use bitcoincore_rpc::{Client, RpcApi};
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ops::Deref;
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -192,10 +193,12 @@ impl GetBlockHash for RpcBlockchain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WalletSync for RpcBlockchain {
|
impl WalletSync for RpcBlockchain {
|
||||||
fn wallet_setup<D>(&self, db: &mut D, prog: Box<dyn Progress>) -> Result<(), Error>
|
fn wallet_setup<D>(&self, db: &RefCell<D>, prog: Box<dyn Progress>) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
D: BatchDatabase,
|
D: BatchDatabase,
|
||||||
{
|
{
|
||||||
|
let mut db = db.borrow_mut();
|
||||||
|
let db = db.deref_mut();
|
||||||
let batch = DbState::new(db, &self.sync_params, &*prog)?
|
let batch = DbState::new(db, &self.sync_params, &*prog)?
|
||||||
.sync_with_core(&self.client, self.is_descriptors)?
|
.sync_with_core(&self.client, self.is_descriptors)?
|
||||||
.as_db_batch()?;
|
.as_db_batch()?;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub use memory::MemoryDatabase;
|
|||||||
/// Blockchain state at the time of syncing
|
/// Blockchain state at the time of syncing
|
||||||
///
|
///
|
||||||
/// Contains only the block time and height at the moment
|
/// Contains only the block time and height at the moment
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SyncTime {
|
pub struct SyncTime {
|
||||||
/// Block timestamp and height at the time of sync
|
/// Block timestamp and height at the time of sync
|
||||||
pub block_time: BlockTime,
|
pub block_time: BlockTime,
|
||||||
|
|||||||
@@ -57,7 +57,17 @@ static MIGRATIONS: &[&str] = &[
|
|||||||
"CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
|
"CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
|
||||||
"INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
|
"INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
|
||||||
"DROP TABLE utxos_old;",
|
"DROP TABLE utxos_old;",
|
||||||
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);"
|
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
|
||||||
|
// Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys
|
||||||
|
"ALTER TABLE script_pubkeys RENAME TO script_pubkeys_old;",
|
||||||
|
"DROP INDEX idx_keychain_child;",
|
||||||
|
"DROP INDEX idx_script;",
|
||||||
|
"CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
|
||||||
|
"CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
|
||||||
|
"CREATE INDEX idx_script ON script_pubkeys(script);",
|
||||||
|
"CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);",
|
||||||
|
"INSERT OR REPLACE INTO script_pubkeys SELECT keychain, child, script FROM script_pubkeys_old;",
|
||||||
|
"DROP TABLE script_pubkeys_old;"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sqlite database stored on filesystem
|
/// Sqlite database stored on filesystem
|
||||||
@@ -88,7 +98,7 @@ impl SqliteDatabase {
|
|||||||
child: u32,
|
child: u32,
|
||||||
script: &[u8],
|
script: &[u8],
|
||||||
) -> Result<i64, Error> {
|
) -> Result<i64, Error> {
|
||||||
let mut statement = self.connection.prepare_cached("INSERT INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
|
let mut statement = self.connection.prepare_cached("INSERT OR REPLACE INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
|
||||||
statement.execute(named_params! {
|
statement.execute(named_params! {
|
||||||
":keychain": keychain,
|
":keychain": keychain,
|
||||||
":child": child,
|
":child": child,
|
||||||
@@ -1096,4 +1106,42 @@ pub mod test {
|
|||||||
fn test_check_descriptor_checksum() {
|
fn test_check_descriptor_checksum() {
|
||||||
crate::database::test::test_check_descriptor_checksum(get_database());
|
crate::database::test::test_check_descriptor_checksum(get_database());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 801: https://github.com/bitcoindevkit/bdk/issues/801
|
||||||
|
#[test]
|
||||||
|
fn test_unique_spks() {
|
||||||
|
use crate::bitcoin::hashes::hex::FromHex;
|
||||||
|
use crate::database::*;
|
||||||
|
|
||||||
|
let mut db = get_database();
|
||||||
|
|
||||||
|
let script = Script::from(
|
||||||
|
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||||
|
);
|
||||||
|
let path = 42;
|
||||||
|
let keychain = KeychainKind::External;
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
db.set_script_pubkey(&script, keychain, path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut statement = db
|
||||||
|
.connection
|
||||||
|
.prepare_cached(
|
||||||
|
"select keychain,child,count(child) from script_pubkeys group by keychain,child;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut rows = statement.query([]).unwrap();
|
||||||
|
while let Some(row) = rows.next().unwrap() {
|
||||||
|
let keychain: String = row.get(0).unwrap();
|
||||||
|
let child: u32 = row.get(1).unwrap();
|
||||||
|
let count: usize = row.get(2).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
count, 1,
|
||||||
|
"keychain={}, child={}, count={}",
|
||||||
|
keychain, child, count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::calc_checksum;
|
use crate::descriptor::calc_checksum;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
// test calc_checksum() function; it should return the same value as Bitcoin Core
|
// test calc_checksum() function; it should return the same value as Bitcoin Core
|
||||||
#[test]
|
#[test]
|
||||||
@@ -155,16 +156,16 @@ mod test {
|
|||||||
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
|
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
|
||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(desc).err(),
|
calc_checksum(desc),
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||||
));
|
);
|
||||||
|
|
||||||
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
|
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(desc).err(),
|
calc_checksum(desc),
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -172,9 +173,9 @@ mod test {
|
|||||||
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
|
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
|
||||||
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
|
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(&invalid_desc).err(),
|
calc_checksum(&invalid_desc),
|
||||||
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,26 @@ impl From<crate::keys::KeyError> for Error {
|
|||||||
|
|
||||||
impl std::fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ use bitcoin::util::{psbt, taproot};
|
|||||||
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
|
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
|
||||||
use bitcoin::{Network, TxOut};
|
use bitcoin::{Network, TxOut};
|
||||||
|
|
||||||
use miniscript::descriptor::{DefiniteDescriptorKey, DescriptorType, InnerXKey, SinglePubKey};
|
use miniscript::descriptor::{
|
||||||
|
DefiniteDescriptorKey, DescriptorSecretKey, DescriptorType, InnerXKey, SinglePubKey,
|
||||||
|
};
|
||||||
pub use miniscript::{
|
pub use miniscript::{
|
||||||
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
|
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
|
||||||
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
|
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
|
||||||
@@ -240,14 +242,34 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.2.contains(&network) {
|
let (desc, keymap, networks) = self;
|
||||||
|
|
||||||
|
if !networks.contains(&network) {
|
||||||
return Err(DescriptorError::Key(KeyError::InvalidNetwork));
|
return Err(DescriptorError::Key(KeyError::InvalidNetwork));
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixup the network for keys that need it
|
// fixup the network for keys that need it in the descriptor
|
||||||
let translated = self.0.translate_pk(&mut Translator { network })?;
|
let translated = desc.translate_pk(&mut Translator { network })?;
|
||||||
|
// ...and in the key map
|
||||||
|
let fixed_keymap = keymap
|
||||||
|
.into_iter()
|
||||||
|
.map(|(mut k, mut v)| {
|
||||||
|
match (&mut k, &mut v) {
|
||||||
|
(DescriptorPublicKey::XPub(xpub), DescriptorSecretKey::XPrv(xprv)) => {
|
||||||
|
xpub.xkey.network = network;
|
||||||
|
xprv.xkey.network = network;
|
||||||
|
}
|
||||||
|
(_, DescriptorSecretKey::Single(key)) => {
|
||||||
|
key.key.network = network;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
Ok((translated, self.1))
|
(k, v)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok((translated, fixed_keymap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +581,7 @@ impl DescriptorMeta for ExtendedDescriptor {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@@ -682,23 +705,40 @@ mod test {
|
|||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap();
|
let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
|
||||||
let path = bip32::DerivationPath::from_str("m/0").unwrap();
|
let path = bip32::DerivationPath::from_str("m/0").unwrap();
|
||||||
|
|
||||||
// here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
|
// here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
|
||||||
// we are using an "xpub"
|
// we are using an "xpub"
|
||||||
let key = (xpub, path).into_descriptor_key().unwrap();
|
let key = (xprv, path.clone()).into_descriptor_key().unwrap();
|
||||||
// override it with any. this happens in some key conversions, like bip39
|
// override it with any. this happens in some key conversions, like bip39
|
||||||
let key = key.override_valid_networks(any_network());
|
let key = key.override_valid_networks(any_network());
|
||||||
|
|
||||||
// make a descriptor out of it
|
// make a descriptor out of it
|
||||||
let desc = crate::descriptor!(wpkh(key)).unwrap();
|
let desc = crate::descriptor!(wpkh(key)).unwrap();
|
||||||
// this should convert the key that supports "any_network" to the right network (testnet)
|
// this should convert the key that supports "any_network" to the right network (testnet)
|
||||||
let (wallet_desc, _) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)#y8p7e8kk");
|
let mut xprv_testnet = xprv;
|
||||||
|
xprv_testnet.network = Network::Testnet;
|
||||||
|
|
||||||
|
let xpub_testnet = bip32::ExtendedPubKey::from_priv(&secp, &xprv_testnet);
|
||||||
|
let desc_pubkey = DescriptorPublicKey::XPub(DescriptorXKey {
|
||||||
|
xkey: xpub_testnet,
|
||||||
|
origin: None,
|
||||||
|
derivation_path: path,
|
||||||
|
wildcard: Wildcard::Unhardened,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(wallet_desc.to_string(), "wpkh(tpubD6NzVbkrYhZ4XtJzoDja5snUjBNQRP5B3f4Hyn1T1x6PVPxzzVjvw6nJx2D8RBCxog9GEVjZoyStfepTz7TtKoBVdkCtnc7VCJh9dD4RAU9/0/*)#a3svx0ha");
|
||||||
|
assert_eq!(
|
||||||
|
keymap
|
||||||
|
.get(&desc_pubkey)
|
||||||
|
.map(|key| key.to_public(&secp).unwrap()),
|
||||||
|
Some(desc_pubkey)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from &str with and without checksum appended
|
// test IntoWalletDescriptor trait from &str with and without checksum appended
|
||||||
@@ -724,17 +764,11 @@ mod test {
|
|||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
|
||||||
));
|
|
||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
|
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
|
||||||
@@ -768,17 +802,11 @@ mod test {
|
|||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
||||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
|
||||||
));
|
|
||||||
|
|
||||||
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
||||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
|
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
|
||||||
@@ -812,11 +840,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
||||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
|
||||||
assert!(matches!(
|
|
||||||
result.unwrap_err(),
|
|
||||||
DescriptorError::HardenedDerivationXpub
|
|
||||||
));
|
|
||||||
|
|
||||||
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
||||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||||
|
|||||||
@@ -512,7 +512,14 @@ pub enum PolicyError {
|
|||||||
|
|
||||||
impl fmt::Display for PolicyError {
|
impl fmt::Display for PolicyError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
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"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,11 +659,11 @@ impl Policy {
|
|||||||
(0..*threshold).collect()
|
(0..*threshold).collect()
|
||||||
}
|
}
|
||||||
SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
|
SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
|
||||||
_ => vec![],
|
_ => HashSet::new(),
|
||||||
};
|
};
|
||||||
let selected = match path.get(&self.id) {
|
let selected: HashSet<_> = match path.get(&self.id) {
|
||||||
Some(arr) => arr,
|
Some(arr) => arr.iter().copied().collect(),
|
||||||
_ => &default,
|
_ => default,
|
||||||
};
|
};
|
||||||
|
|
||||||
match &self.item {
|
match &self.item {
|
||||||
@@ -664,14 +671,24 @@ impl Policy {
|
|||||||
let mapped_req = items
|
let mapped_req = items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| i.get_condition(path))
|
.map(|i| i.get_condition(path))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// if all the requirements are null we don't care about `selected` because there
|
// if all the requirements are null we don't care about `selected` because there
|
||||||
// are no requirements
|
// are no requirements
|
||||||
if mapped_req.iter().all(Condition::is_null) {
|
if mapped_req
|
||||||
|
.iter()
|
||||||
|
.all(|cond| matches!(cond, Ok(c) if c.is_null()))
|
||||||
|
{
|
||||||
return Ok(Condition::default());
|
return Ok(Condition::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure all the indexes in the `selected` list are within range
|
||||||
|
for index in &selected {
|
||||||
|
if *index >= items.len() {
|
||||||
|
return Err(PolicyError::IndexOutOfRange(*index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if we have something, make sure we have enough items. note that the user can set
|
// if we have something, make sure we have enough items. note that the user can set
|
||||||
// an empty value for this step in case of n-of-n, because `selected` is set to all
|
// an empty value for this step in case of n-of-n, because `selected` is set to all
|
||||||
// the elements above
|
// the elements above
|
||||||
@@ -680,23 +697,18 @@ impl Policy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check the selected items, see if there are conflicting requirements
|
// check the selected items, see if there are conflicting requirements
|
||||||
let mut requirements = Condition::default();
|
mapped_req
|
||||||
for item_index in selected {
|
.into_iter()
|
||||||
requirements = requirements.merge(
|
.enumerate()
|
||||||
mapped_req
|
.filter(|(index, _)| selected.contains(index))
|
||||||
.get(*item_index)
|
.try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
|
||||||
.ok_or(PolicyError::IndexOutOfRange(*item_index))?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(requirements)
|
|
||||||
}
|
}
|
||||||
SatisfiableItem::Multisig { keys, threshold } => {
|
SatisfiableItem::Multisig { keys, threshold } => {
|
||||||
if selected.len() < *threshold {
|
if selected.len() < *threshold {
|
||||||
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
|
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
|
||||||
}
|
}
|
||||||
if let Some(item) = selected.iter().find(|i| **i >= keys.len()) {
|
if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
|
||||||
return Err(PolicyError::IndexOutOfRange(*item));
|
return Err(PolicyError::IndexOutOfRange(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Condition::default())
|
Ok(Condition::default())
|
||||||
@@ -1139,6 +1151,7 @@ mod test {
|
|||||||
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
|
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
use crate::wallet::signer::SignersContainer;
|
use crate::wallet::signer::SignersContainer;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
@@ -1182,8 +1195,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||||
|
|
||||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||||
let (wallet_desc, keymap) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
@@ -1195,10 +1208,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
|
||||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 pub keys descriptor, required 2 prv keys
|
// 2 pub keys descriptor, required 2 prv keys
|
||||||
@@ -1217,19 +1228,16 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& 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 this be "Satisfaction::None" since we have no prv keys?
|
||||||
// TODO should items and conditions not be empty?
|
// TODO should items and conditions not be empty?
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
|
||||||
&& m == &2usize
|
&& m == &2usize
|
||||||
&& items.is_empty()
|
&& items.is_empty()
|
||||||
&& conditions.is_empty()
|
&& conditions.is_empty()
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,18 +1256,15 @@ mod test {
|
|||||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
|
||||||
&& m == &2usize
|
&& m == &2usize
|
||||||
&& items.len() == 1
|
&& items.len() == 1
|
||||||
&& conditions.contains_key(&0)
|
&& conditions.contains_key(&0)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1281,18 +1286,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
|
||||||
&& m == &1
|
&& m == &1
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0])
|
&& conditions.contains_key(&vec![0])
|
||||||
&& conditions.contains_key(&vec![1])
|
&& conditions.contains_key(&vec![1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1313,18 +1315,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0,1])
|
&& conditions.contains_key(&vec![0,1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1345,8 +1344,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||||
|
|
||||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||||
let (wallet_desc, keymap) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
@@ -1358,10 +1357,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint));
|
assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
|
||||||
assert!(
|
assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
|
||||||
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
|
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
|
||||||
@@ -1382,18 +1379,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
||||||
matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
||||||
matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
|
||||||
&& m == 1
|
&& m == 1
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0])
|
&& conditions.contains_key(&vec![0])
|
||||||
&& conditions.contains_key(&vec![1])
|
&& conditions.contains_key(&vec![1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,18 +1419,14 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
|
||||||
matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.len() == 3
|
&& items.len() == 3
|
||||||
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
|
&& 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![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
||||||
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1599,11 +1589,9 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
|
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0]
|
&& items == &vec![0]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
||||||
@@ -1613,11 +1601,9 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
|
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![1]
|
&& items == &vec![1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
||||||
@@ -1625,11 +1611,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0, 1]
|
&& items == &vec![0, 1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1673,11 +1657,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat, &secp)
|
.extract_policy(&signers_container, build_sat, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||||
matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.is_empty()
|
&& items.is_empty()
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy).unwrap());
|
//println!("{}", serde_json::to_string(&policy).unwrap());
|
||||||
|
|
||||||
@@ -1691,11 +1673,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat_expired, &secp)
|
.extract_policy(&signers_container, build_sat_expired, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||||
matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0]
|
&& items == &vec![0]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
||||||
|
|
||||||
@@ -1711,11 +1691,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
|
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
||||||
matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0, 1]
|
&& items == &vec![0, 1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
||||||
}
|
}
|
||||||
@@ -1790,12 +1768,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
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.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1])
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
||||||
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
||||||
@@ -1887,19 +1861,11 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
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.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
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.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1])
|
|
||||||
);
|
|
||||||
|
|
||||||
let satisfied_items = match policy_signed.item {
|
let satisfied_items = match policy_signed.item {
|
||||||
SatisfiableItem::Thresh { items, .. } => items,
|
SatisfiableItem::Thresh { items, .. } => items,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
|
|
||||||
use miniscript::{Legacy, Segwitv0};
|
use miniscript::{Legacy, Segwitv0, Tap};
|
||||||
|
|
||||||
use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
|
use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
|
||||||
use crate::descriptor::DescriptorError;
|
use crate::descriptor::DescriptorError;
|
||||||
@@ -170,6 +170,35 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// P2TR template. Expands to a descriptor `tr(key)`
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
|
/// # use bdk::Wallet;
|
||||||
|
/// # use bdk::database::MemoryDatabase;
|
||||||
|
/// # use bdk::wallet::AddressIndex::New;
|
||||||
|
/// use bdk::template::P2TR;
|
||||||
|
///
|
||||||
|
/// let key =
|
||||||
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
|
/// let mut wallet = Wallet::new(P2TR(key), None, Network::Testnet, MemoryDatabase::default())?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// wallet.get_address(New)?.to_string(),
|
||||||
|
/// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
|
||||||
|
/// );
|
||||||
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
|
/// ```
|
||||||
|
pub struct P2TR<K: IntoDescriptorKey<Tap>>(pub K);
|
||||||
|
|
||||||
|
impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
|
||||||
|
fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
|
descriptor!(tr(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// BIP44 template. Expands to `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
|
/// BIP44 template. Expands to `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
|
||||||
///
|
///
|
||||||
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
|
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
|
||||||
@@ -235,14 +264,17 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
|||||||
/// )?;
|
/// )?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
|
||||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
P2Pkh(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build(network)
|
P2Pkh(legacy::make_bipxx_public(
|
||||||
|
44, self.0, self.1, self.2, network,
|
||||||
|
)?)
|
||||||
|
.build(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +316,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
|||||||
|
|
||||||
/// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))`
|
/// 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'`.
|
/// 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 template requires the parent fingerprint to populate correctly the metadata of PSBTs.
|
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
|
||||||
///
|
///
|
||||||
@@ -311,14 +343,17 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
|||||||
/// )?;
|
/// )?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
|
||||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/0'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build(network)
|
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(
|
||||||
|
49, self.0, self.1, self.2, network,
|
||||||
|
)?)
|
||||||
|
.build(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +395,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
|||||||
|
|
||||||
/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)`
|
/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)`
|
||||||
///
|
///
|
||||||
/// This assumes that the key used has already been derived with `m/84'/0'/0'`.
|
/// 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 template requires the parent fingerprint to populate correctly the metadata of PSBTs.
|
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
|
||||||
///
|
///
|
||||||
@@ -387,14 +422,96 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
|||||||
/// )?;
|
/// )?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
|
||||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
P2Wpkh(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build(network)
|
P2Wpkh(segwit_v0::make_bipxx_public(
|
||||||
|
84, self.0, self.1, self.2, network,
|
||||||
|
)?)
|
||||||
|
.build(network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BIP86 template. Expands to `tr(key/86'/{0,1}'/0'/{0,1}/*)`
|
||||||
|
///
|
||||||
|
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
|
||||||
|
///
|
||||||
|
/// See [`Bip86Public`] for a template that can work with a `xpub`/`tpub`.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use std::str::FromStr;
|
||||||
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
|
/// # use bdk::database::MemoryDatabase;
|
||||||
|
/// # use bdk::wallet::AddressIndex::New;
|
||||||
|
/// use bdk::template::Bip86;
|
||||||
|
///
|
||||||
|
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||||
|
/// let mut wallet = Wallet::new(
|
||||||
|
/// Bip86(key.clone(), KeychainKind::External),
|
||||||
|
/// Some(Bip86(key, KeychainKind::Internal)),
|
||||||
|
/// Network::Testnet,
|
||||||
|
/// MemoryDatabase::default()
|
||||||
|
/// )?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
|
||||||
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
|
||||||
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
|
/// ```
|
||||||
|
pub struct Bip86<K: DerivableKey<Tap>>(pub K, pub KeychainKind);
|
||||||
|
|
||||||
|
impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
|
||||||
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
|
P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BIP86 public template. Expands to `tr(key/{0,1}/*)`
|
||||||
|
///
|
||||||
|
/// This assumes that the key used has already been derived with `m/86'/0'/0'` for Mainnet or `m/86'/1'/0'` for Testnet.
|
||||||
|
///
|
||||||
|
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
|
||||||
|
///
|
||||||
|
/// See [`Bip86`] for a template that does the full derivation, but requires private data
|
||||||
|
/// for the key.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use std::str::FromStr;
|
||||||
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
|
/// # use bdk::database::MemoryDatabase;
|
||||||
|
/// # use bdk::wallet::AddressIndex::New;
|
||||||
|
/// use bdk::template::Bip86Public;
|
||||||
|
///
|
||||||
|
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||||
|
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||||
|
/// let mut wallet = Wallet::new(
|
||||||
|
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
|
||||||
|
/// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)),
|
||||||
|
/// Network::Testnet,
|
||||||
|
/// MemoryDatabase::default()
|
||||||
|
/// )?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
|
||||||
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
|
||||||
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
|
/// ```
|
||||||
|
pub struct Bip86Public<K: DerivableKey<Tap>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
|
impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86Public<K> {
|
||||||
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
|
P2TR(segwit_v1::make_bipxx_public(
|
||||||
|
86, self.0, self.1, self.2, network,
|
||||||
|
)?)
|
||||||
|
.build(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,6 +557,7 @@ macro_rules! expand_make_bipxx {
|
|||||||
key: K,
|
key: K,
|
||||||
parent_fingerprint: bip32::Fingerprint,
|
parent_fingerprint: bip32::Fingerprint,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
|
network: Network,
|
||||||
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
|
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
|
||||||
let derivation_path: bip32::DerivationPath = match keychain {
|
let derivation_path: bip32::DerivationPath = match keychain {
|
||||||
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
||||||
@@ -448,7 +566,10 @@ macro_rules! expand_make_bipxx {
|
|||||||
|
|
||||||
let source_path = bip32::DerivationPath::from(vec![
|
let source_path = bip32::DerivationPath::from(vec![
|
||||||
bip32::ChildNumber::from_hardened_idx(bip)?,
|
bip32::ChildNumber::from_hardened_idx(bip)?,
|
||||||
bip32::ChildNumber::from_hardened_idx(0)?,
|
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)?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -460,6 +581,7 @@ macro_rules! expand_make_bipxx {
|
|||||||
|
|
||||||
expand_make_bipxx!(legacy, Legacy);
|
expand_make_bipxx!(legacy, Legacy);
|
||||||
expand_make_bipxx!(segwit_v0, Segwitv0);
|
expand_make_bipxx!(segwit_v0, Segwitv0);
|
||||||
|
expand_make_bipxx!(segwit_v1, Tap);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
@@ -470,7 +592,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||||
use crate::keys::ValidNetworks;
|
use crate::keys::ValidNetworks;
|
||||||
use bitcoin::network::constants::Network::Regtest;
|
use assert_matches::assert_matches;
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
|
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
|
||||||
use miniscript::Descriptor;
|
use miniscript::Descriptor;
|
||||||
|
|
||||||
@@ -488,9 +610,9 @@ mod test {
|
|||||||
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
|
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
|
||||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||||
let purpose = path.get(0).unwrap();
|
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();
|
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();
|
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||||
@@ -502,9 +624,9 @@ mod test {
|
|||||||
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
|
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
|
||||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||||
let purpose = path.get(0).unwrap();
|
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();
|
let coin_type = path.get(1).unwrap();
|
||||||
assert!(matches!(coin_type, Hardened { index: 1 }));
|
assert_matches!(coin_type, Hardened { index: 1 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,11 +634,14 @@ mod test {
|
|||||||
fn check(
|
fn check(
|
||||||
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
|
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
|
||||||
is_witness: bool,
|
is_witness: bool,
|
||||||
|
is_taproot: bool,
|
||||||
is_fixed: bool,
|
is_fixed: bool,
|
||||||
|
network: Network,
|
||||||
expected: &[&str],
|
expected: &[&str],
|
||||||
) {
|
) {
|
||||||
let (desc, _key_map, _networks) = desc.unwrap();
|
let (desc, _key_map, _networks) = desc.unwrap();
|
||||||
assert_eq!(desc.is_witness(), is_witness);
|
assert_eq!(desc.is_witness(), is_witness);
|
||||||
|
assert_eq!(desc.is_taproot(), is_taproot);
|
||||||
assert_eq!(!desc.has_wildcard(), is_fixed);
|
assert_eq!(!desc.has_wildcard(), is_fixed);
|
||||||
for i in 0..expected.len() {
|
for i in 0..expected.len() {
|
||||||
let index = i as u32;
|
let index = i as u32;
|
||||||
@@ -525,7 +650,7 @@ mod test {
|
|||||||
} else {
|
} else {
|
||||||
desc.at_derivation_index(index)
|
desc.at_derivation_index(index)
|
||||||
};
|
};
|
||||||
let address = child_desc.address(Regtest).unwrap();
|
let address = child_desc.address(network).unwrap();
|
||||||
assert_eq!(address.to_string(), *expected.get(i).unwrap());
|
assert_eq!(address.to_string(), *expected.get(i).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,7 +664,9 @@ mod test {
|
|||||||
check(
|
check(
|
||||||
P2Pkh(prvkey).build(Network::Bitcoin),
|
P2Pkh(prvkey).build(Network::Bitcoin),
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
|
Network::Regtest,
|
||||||
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
|
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -550,7 +677,9 @@ mod test {
|
|||||||
check(
|
check(
|
||||||
P2Pkh(pubkey).build(Network::Bitcoin),
|
P2Pkh(pubkey).build(Network::Bitcoin),
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
|
Network::Regtest,
|
||||||
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
|
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -564,7 +693,9 @@ mod test {
|
|||||||
check(
|
check(
|
||||||
P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
|
P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
|
Network::Regtest,
|
||||||
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
|
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -575,7 +706,9 @@ mod test {
|
|||||||
check(
|
check(
|
||||||
P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
|
P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
|
Network::Regtest,
|
||||||
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
|
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -589,7 +722,9 @@ mod test {
|
|||||||
check(
|
check(
|
||||||
P2Wpkh(prvkey).build(Network::Bitcoin),
|
P2Wpkh(prvkey).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
|
Network::Regtest,
|
||||||
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
|
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -600,11 +735,42 @@ mod test {
|
|||||||
check(
|
check(
|
||||||
P2Wpkh(pubkey).build(Network::Bitcoin),
|
P2Wpkh(pubkey).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
|
Network::Regtest,
|
||||||
&["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
|
&["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// P2TR `tr(key)`
|
||||||
|
#[test]
|
||||||
|
fn test_p2tr_template() {
|
||||||
|
let prvkey =
|
||||||
|
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
|
||||||
|
.unwrap();
|
||||||
|
check(
|
||||||
|
P2TR(prvkey).build(Network::Bitcoin),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
Network::Regtest,
|
||||||
|
&["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"],
|
||||||
|
);
|
||||||
|
|
||||||
|
let pubkey = bitcoin::PublicKey::from_str(
|
||||||
|
"03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
check(
|
||||||
|
P2TR(pubkey).build(Network::Bitcoin),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
Network::Regtest,
|
||||||
|
&["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// BIP44 `pkh(key/44'/0'/0'/{0,1}/*)`
|
// BIP44 `pkh(key/44'/0'/0'/{0,1}/*)`
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bip44_template() {
|
fn test_bip44_template() {
|
||||||
@@ -613,6 +779,8 @@ mod test {
|
|||||||
Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
|
Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5",
|
"n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5",
|
||||||
"mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP",
|
"mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP",
|
||||||
@@ -623,6 +791,8 @@ mod test {
|
|||||||
Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
|
Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
|
"muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
|
||||||
"n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
|
"n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
|
||||||
@@ -640,6 +810,8 @@ mod test {
|
|||||||
Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
|
Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"miNG7dJTzJqNbFS19svRdTCisC65dsubtR",
|
"miNG7dJTzJqNbFS19svRdTCisC65dsubtR",
|
||||||
"n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg",
|
"n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg",
|
||||||
@@ -650,6 +822,8 @@ mod test {
|
|||||||
Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
|
Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
|
"moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
|
||||||
"ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
|
"ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
|
||||||
@@ -666,6 +840,8 @@ mod test {
|
|||||||
Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
|
Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV",
|
"2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV",
|
||||||
"2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS",
|
"2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS",
|
||||||
@@ -676,6 +852,8 @@ mod test {
|
|||||||
Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
|
Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
|
"2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
|
||||||
"2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
|
"2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
|
||||||
@@ -693,6 +871,8 @@ mod test {
|
|||||||
Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
|
Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt",
|
"2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt",
|
||||||
"2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX",
|
"2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX",
|
||||||
@@ -703,6 +883,8 @@ mod test {
|
|||||||
Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
|
Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
|
"2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
|
||||||
"2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
|
"2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
|
||||||
@@ -719,6 +901,8 @@ mod test {
|
|||||||
Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
|
Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s",
|
"bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s",
|
||||||
"bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp",
|
"bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp",
|
||||||
@@ -729,6 +913,8 @@ mod test {
|
|||||||
Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
|
Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
|
"bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
|
||||||
"bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
|
"bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
|
||||||
@@ -746,6 +932,8 @@ mod test {
|
|||||||
Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
|
Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h",
|
"bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h",
|
||||||
"bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana",
|
"bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana",
|
||||||
@@ -756,6 +944,8 @@ mod test {
|
|||||||
Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
|
Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
Network::Regtest,
|
||||||
&[
|
&[
|
||||||
"bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
|
"bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
|
||||||
"bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp",
|
"bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp",
|
||||||
@@ -763,4 +953,67 @@ mod test {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BIP86 `tr(key/86'/0'/0'/{0,1}/*)`
|
||||||
|
// Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
|
||||||
|
#[test]
|
||||||
|
fn test_bip86_template() {
|
||||||
|
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap();
|
||||||
|
check(
|
||||||
|
Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Network::Bitcoin,
|
||||||
|
&[
|
||||||
|
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
|
||||||
|
"bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
|
||||||
|
"bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Network::Bitcoin,
|
||||||
|
&[
|
||||||
|
"bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
|
||||||
|
"bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
|
||||||
|
"bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BIP86 public `tr(key/{0,1}/*)`
|
||||||
|
// Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
|
||||||
|
#[test]
|
||||||
|
fn test_bip86_public_template() {
|
||||||
|
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap();
|
||||||
|
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("73c5da0a").unwrap();
|
||||||
|
check(
|
||||||
|
Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Network::Bitcoin,
|
||||||
|
&[
|
||||||
|
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
|
||||||
|
"bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
|
||||||
|
"bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Network::Bitcoin,
|
||||||
|
&[
|
||||||
|
"bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
|
||||||
|
"bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
|
||||||
|
"bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
src/error.rs
102
src/error.rs
@@ -107,7 +107,7 @@ pub enum Error {
|
|||||||
MiniscriptPsbt(MiniscriptPsbtError),
|
MiniscriptPsbt(MiniscriptPsbtError),
|
||||||
/// BIP32 error
|
/// BIP32 error
|
||||||
Bip32(bitcoin::util::bip32::Error),
|
Bip32(bitcoin::util::bip32::Error),
|
||||||
/// An ECDSA error
|
/// A secp256k1 error
|
||||||
Secp256k1(bitcoin::secp256k1::Error),
|
Secp256k1(bitcoin::secp256k1::Error),
|
||||||
/// Error serializing or deserializing JSON data
|
/// Error serializing or deserializing JSON data
|
||||||
Json(serde_json::Error),
|
Json(serde_json::Error),
|
||||||
@@ -157,6 +157,18 @@ pub enum MiniscriptPsbtError {
|
|||||||
OutputUpdate(miniscript::psbt::OutputUpdateError),
|
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
|
/// Represents the last failed [`crate::blockchain::WalletSync`] sync attempt in which we were short
|
||||||
/// on cached `scriptPubKey`s.
|
/// on cached `scriptPubKey`s.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -169,7 +181,93 @@ pub struct MissingCachedScripts {
|
|||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub mod bip39;
|
|||||||
/// Set of valid networks for a key
|
/// Set of valid networks for a key
|
||||||
pub type ValidNetworks = HashSet<Network>;
|
pub type ValidNetworks = HashSet<Network>;
|
||||||
|
|
||||||
/// Create a set containing mainnet, testnet and regtest
|
/// Create a set containing mainnet, testnet, signet, and regtest
|
||||||
pub fn any_network() -> ValidNetworks {
|
pub fn any_network() -> ValidNetworks {
|
||||||
vec![
|
vec![
|
||||||
Network::Bitcoin,
|
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
|
// 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.
|
// but since it is not meant to be part of the public api we hide it from the docs.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn extract(
|
pub fn extract(
|
||||||
@@ -375,7 +375,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
|
|||||||
/// `(DerivableKey, KeySource, DerivationPath)` tuples.
|
/// `(DerivableKey, KeySource, DerivationPath)` tuples.
|
||||||
///
|
///
|
||||||
/// For key types that don't encode any indication about the path to use (like bip39), it's
|
/// For key types that don't encode any indication about the path to use (like bip39), it's
|
||||||
/// generally recommended to implemented this trait instead of [`IntoDescriptorKey`]. The same
|
/// generally recommended to implement this trait instead of [`IntoDescriptorKey`]. The same
|
||||||
/// rules regarding script context and valid networks apply.
|
/// rules regarding script context and valid networks apply.
|
||||||
///
|
///
|
||||||
/// ## Examples
|
/// ## Examples
|
||||||
@@ -935,7 +935,14 @@ impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError);
|
|||||||
|
|
||||||
impl std::fmt::Display for KeyError {
|
impl std::fmt::Display for KeyError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ compile_error!(
|
|||||||
#[cfg(feature = "keys-bip39")]
|
#[cfg(feature = "keys-bip39")]
|
||||||
extern crate bip39;
|
extern crate bip39;
|
||||||
|
|
||||||
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
|
#[cfg(feature = "async-interface")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate async_trait;
|
extern crate async_trait;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -256,6 +256,9 @@ pub extern crate rusqlite;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate assert_matches;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
|
|||||||
@@ -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.
|
/// 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>;
|
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.
|
/// If the PSBT is missing a TxOut for an input returns None.
|
||||||
fn fee_amount(&self) -> Option<u64>;
|
fn fee_amount(&self) -> Option<u64>;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs all avaliable tests.
|
/// Runs all available tests.
|
||||||
fn run(&self) {
|
fn run(&self) {
|
||||||
let test_client = &mut TestClient::default();
|
let test_client = &mut TestClient::default();
|
||||||
|
|
||||||
|
|||||||
120
src/types.rs
120
src/types.rs
@@ -230,7 +230,6 @@ pub struct TransactionDetails {
|
|||||||
pub transaction: Option<Transaction>,
|
pub transaction: Option<Transaction>,
|
||||||
/// Transaction id
|
/// Transaction id
|
||||||
pub txid: Txid,
|
pub txid: Txid,
|
||||||
|
|
||||||
/// Received value (sats)
|
/// Received value (sats)
|
||||||
/// Sum of owned outputs of this transaction.
|
/// Sum of owned outputs of this transaction.
|
||||||
pub received: u64,
|
pub received: u64,
|
||||||
@@ -242,11 +241,25 @@ pub struct TransactionDetails {
|
|||||||
/// Server backend, but it could be `None` with a Bitcoin RPC node without txindex that receive
|
/// Server backend, but it could be `None` with a Bitcoin RPC node without txindex that receive
|
||||||
/// funds while offline.
|
/// funds while offline.
|
||||||
pub fee: Option<u64>,
|
pub fee: Option<u64>,
|
||||||
/// If the transaction is confirmed, contains height and timestamp of the block containing the
|
/// If the transaction is confirmed, contains height and Unix timestamp of the block containing the
|
||||||
/// transaction, unconfirmed transaction contains `None`.
|
/// transaction, unconfirmed transaction contains `None`.
|
||||||
pub confirmation_time: Option<BlockTime>,
|
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
|
/// Block height and timestamp of a block
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct BlockTime {
|
pub struct BlockTime {
|
||||||
@@ -256,6 +269,20 @@ pub struct BlockTime {
|
|||||||
pub timestamp: u64,
|
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
|
/// **DEPRECATED**: Confirmation time of a transaction
|
||||||
///
|
///
|
||||||
/// The structure has been renamed to `BlockTime`
|
/// The structure has been renamed to `BlockTime`
|
||||||
@@ -334,6 +361,95 @@ impl std::iter::Sum for Balance {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn can_store_feerate_in_const() {
|
fn can_store_feerate_in_const() {
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ use crate::{error::Error, Utxo};
|
|||||||
use bitcoin::consensus::encode::serialize;
|
use bitcoin::consensus::encode::serialize;
|
||||||
use bitcoin::Script;
|
use bitcoin::Script;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
#[cfg(test)]
|
|
||||||
use rand::{rngs::StdRng, SeedableRng};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
@@ -671,6 +671,7 @@ impl BranchAndBoundCoinSelection {
|
|||||||
optional_utxos.shuffle(&mut thread_rng());
|
optional_utxos.shuffle(&mut thread_rng());
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
|
use rand::{rngs::StdRng, SeedableRng};
|
||||||
let seed = [0; 32];
|
let seed = [0; 32];
|
||||||
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
||||||
optional_utxos.shuffle(&mut rng);
|
optional_utxos.shuffle(&mut rng);
|
||||||
@@ -1522,24 +1523,22 @@ mod test {
|
|||||||
let database = MemoryDatabase::default();
|
let database = MemoryDatabase::default();
|
||||||
let drain_script = Script::default();
|
let drain_script = Script::default();
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
vec![],
|
||||||
vec![],
|
utxos,
|
||||||
utxos,
|
FeeRate::from_sat_per_vb(10.0),
|
||||||
FeeRate::from_sat_per_vb(10.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_000,
|
available: 300_000,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1552,24 +1551,22 @@ mod test {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
|
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
required,
|
||||||
required,
|
optional,
|
||||||
optional,
|
FeeRate::from_sat_per_vb(10.0),
|
||||||
FeeRate::from_sat_per_vb(10.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_010,
|
available: 300_010,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1578,23 +1575,21 @@ mod test {
|
|||||||
let database = MemoryDatabase::default();
|
let database = MemoryDatabase::default();
|
||||||
let drain_script = Script::default();
|
let drain_script = Script::default();
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
utxos,
|
||||||
utxos,
|
vec![],
|
||||||
vec![],
|
FeeRate::from_sat_per_vb(10_000.0),
|
||||||
FeeRate::from_sat_per_vb(10_000.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_010,
|
available: 300_010,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,15 +134,11 @@ impl FullyNodedExport {
|
|||||||
let blockheight = match wallet.database.borrow().iter_txs(false) {
|
let blockheight = match wallet.database.borrow().iter_txs(false) {
|
||||||
_ if !include_blockheight => 0,
|
_ if !include_blockheight => 0,
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
Ok(txs) => {
|
Ok(txs) => txs
|
||||||
let mut heights = txs
|
.into_iter()
|
||||||
.into_iter()
|
.filter_map(|tx| tx.confirmation_time.map(|c| c.height))
|
||||||
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(0))
|
.min()
|
||||||
.collect::<Vec<_>>();
|
.unwrap_or(0),
|
||||||
heights.sort_unstable();
|
|
||||||
|
|
||||||
*heights.last().unwrap_or(&0)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let export = FullyNodedExport {
|
let export = FullyNodedExport {
|
||||||
@@ -249,6 +245,22 @@ mod test {
|
|||||||
fee: Some(500),
|
fee: Some(500),
|
||||||
confirmation_time: Some(BlockTime {
|
confirmation_time: Some(BlockTime {
|
||||||
timestamp: 12345678,
|
timestamp: 12345678,
|
||||||
|
height: 5001,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.set_tx(&TransactionDetails {
|
||||||
|
transaction: None,
|
||||||
|
txid: Txid::from_str(
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
received: 25_000,
|
||||||
|
sent: 0,
|
||||||
|
fee: Some(300),
|
||||||
|
confirmation_time: Some(BlockTime {
|
||||||
|
timestamp: 12345677,
|
||||||
height: 5000,
|
height: 5000,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,9 +24,12 @@
|
|||||||
//! # use std::sync::Arc;
|
//! # use std::sync::Arc;
|
||||||
//! #
|
//! #
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! let devices = HWIClient::enumerate()?;
|
//! let mut devices = HWIClient::enumerate()?;
|
||||||
//! let first_device = devices.first().expect("No devices found!");
|
//! if devices.is_empty() {
|
||||||
//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
|
//! panic!("No devices found!");
|
||||||
|
//! }
|
||||||
|
//! let first_device = devices.remove(0)?;
|
||||||
|
//! let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
|
||||||
//!
|
//!
|
||||||
//! # let mut wallet = Wallet::new(
|
//! # let mut wallet = Wallet::new(
|
||||||
//! # "",
|
//! # "",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use std::cell::RefCell;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ pub enum AddressIndex {
|
|||||||
Reset(u32),
|
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`
|
/// For convenience this automatically derefs to `Address`
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct AddressInfo {
|
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
|
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's
|
||||||
/// [`SignerOrdering`]
|
/// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that has the value true if the PSBT was finalized, or false otherwise.
|
||||||
///
|
///
|
||||||
/// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
|
/// 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*
|
/// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
|
||||||
@@ -1719,14 +1719,11 @@ where
|
|||||||
let max_rounds = if has_wildcard { 100 } else { 1 };
|
let max_rounds = if has_wildcard { 100 } else { 1 };
|
||||||
|
|
||||||
for _ in 0..max_rounds {
|
for _ in 0..max_rounds {
|
||||||
let sync_res =
|
let sync_res = if run_setup {
|
||||||
if run_setup {
|
maybe_await!(blockchain.wallet_setup(&self.database, new_progress()))
|
||||||
maybe_await!(blockchain
|
} else {
|
||||||
.wallet_setup(self.database.borrow_mut().deref_mut(), new_progress()))
|
maybe_await!(blockchain.wallet_sync(&self.database, 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
|
// If the error is the special `MissingCachedScripts` error, we return the number of
|
||||||
// scripts we should ensure cached.
|
// scripts we should ensure cached.
|
||||||
@@ -1851,6 +1848,7 @@ pub fn get_funded_wallet(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
||||||
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
@@ -2938,6 +2936,25 @@ pub(crate) mod test {
|
|||||||
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144));
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_tx_policy_path_ignored_subtree_with_csv() {
|
||||||
|
let (wallet, _, _) = get_funded_wallet("wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),or_i(and_v(v:pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(30)),and_v(v:pkh(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(90)))))");
|
||||||
|
|
||||||
|
let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
|
||||||
|
let root_id = external_policy.id;
|
||||||
|
// child #0 is pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)
|
||||||
|
let path = vec![(root_id, vec![0])].into_iter().collect();
|
||||||
|
|
||||||
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
|
let mut builder = wallet.build_tx();
|
||||||
|
builder
|
||||||
|
.add_recipient(addr.script_pubkey(), 30_000)
|
||||||
|
.policy_path(path, KeychainKind::External);
|
||||||
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_tx_global_xpubs_with_origin() {
|
fn test_create_tx_global_xpubs_with_origin() {
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
@@ -4425,11 +4442,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the TX uses non-standard sighashes"
|
"Signing should have failed because the TX uses non-standard sighashes"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||||
Error::Signer(SignerError::NonStandardSighash)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -4912,16 +4927,10 @@ pub(crate) mod test {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
result.is_err(),
|
result,
|
||||||
"Signing should have failed because the witness_utxo is missing"
|
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||||
);
|
"Signing should have failed with the correct error 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
|
// restore the witness_utxo
|
||||||
@@ -4935,9 +4944,9 @@ pub(crate) mod test {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.is_ok(), "Signing should have worked");
|
assert_matches!(
|
||||||
assert!(
|
result,
|
||||||
result.unwrap(),
|
Ok(true),
|
||||||
"Should finalize the input since we can produce signatures"
|
"Should finalize the input since we can produce signatures"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -5262,11 +5271,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the TX uses non-standard sighashes"
|
"Signing should have failed because the TX uses non-standard sighashes"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||||
Error::Signer(SignerError::NonStandardSighash)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5282,11 +5289,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the witness_utxo is missing"
|
"Signing should have failed because the witness_utxo is missing"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5367,26 +5372,26 @@ pub(crate) mod test {
|
|||||||
builder
|
builder
|
||||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||||
.current_height(confirmation_time);
|
.current_height(confirmation_time);
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
builder.finish().unwrap_err(),
|
builder.finish(),
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
needed: _,
|
needed: _,
|
||||||
available: 0
|
available: 0
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
|
|
||||||
// Still unspendable...
|
// Still unspendable...
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder
|
builder
|
||||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||||
.current_height(not_yet_mature_time);
|
.current_height(not_yet_mature_time);
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
builder.finish().unwrap_err(),
|
builder.finish(),
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
needed: _,
|
needed: _,
|
||||||
available: 0
|
available: 0
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
|
|
||||||
// ...Now the coinbase is mature :)
|
// ...Now the coinbase is mature :)
|
||||||
let sync_time = SyncTime {
|
let sync_time = SyncTime {
|
||||||
@@ -5428,10 +5433,7 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
builder.add_recipient(addr.script_pubkey(), 0);
|
builder.add_recipient(addr.script_pubkey(), 0);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(builder.finish(), Err(Error::OutputBelowDustLimit(0)));
|
||||||
builder.finish().unwrap_err(),
|
|
||||||
Error::OutputBelowDustLimit(0)
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
|
|
||||||
@@ -5546,11 +5548,14 @@ pub(crate) mod test {
|
|||||||
use hwi::types::HWIChain;
|
use hwi::types::HWIChain;
|
||||||
use hwi::HWIClient;
|
use hwi::HWIClient;
|
||||||
|
|
||||||
let devices = HWIClient::enumerate().unwrap();
|
let mut devices = HWIClient::enumerate().unwrap();
|
||||||
let device = devices.first().expect("No devices found");
|
if devices.is_empty() {
|
||||||
let client = HWIClient::get_client(device, true, HWIChain::Regtest).unwrap();
|
panic!("No devices found!");
|
||||||
let descriptors = client.get_descriptors(None).unwrap();
|
}
|
||||||
let custom_signer = HWISigner::from_device(device, HWIChain::Regtest).unwrap();
|
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]);
|
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
|
||||||
wallet.add_signer(
|
wallet.add_signer(
|
||||||
|
|||||||
@@ -180,7 +180,22 @@ impl From<sighash::Error> for SignerError {
|
|||||||
|
|
||||||
impl fmt::Display for SignerError {
|
impl fmt::Display for SignerError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -998,6 +1013,7 @@ mod signers_container_tests {
|
|||||||
use crate::descriptor;
|
use crate::descriptor;
|
||||||
use crate::descriptor::IntoWalletDescriptor;
|
use crate::descriptor::IntoWalletDescriptor;
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::secp256k1::{All, Secp256k1};
|
use bitcoin::secp256k1::{All, Secp256k1};
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
@@ -1067,17 +1083,17 @@ mod signers_container_tests {
|
|||||||
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
|
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
|
||||||
signers.add_external(id3.clone(), SignerOrdering(3), signer3.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(id1), Some(signer) if is_equal(signer, &signer1));
|
||||||
assert!(matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2)));
|
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(id3.clone()), Some(signer) if is_equal(signer, &signer3));
|
||||||
|
|
||||||
// The `signer4` has the same ID as `signer3` but lower ordering.
|
// The `signer4` has the same ID as `signer3` but lower ordering.
|
||||||
// It should be found by `id3` instead of `signer3`.
|
// It should be found by `id3` instead of `signer3`.
|
||||||
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
|
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
|
// 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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|||||||
@@ -91,7 +91,12 @@ pub enum VerifyError {
|
|||||||
|
|
||||||
impl fmt::Display for VerifyError {
|
impl fmt::Display for VerifyError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +113,7 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::database::{BatchOperations, MemoryDatabase};
|
use crate::database::{BatchOperations, MemoryDatabase};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::{Transaction, Txid};
|
use bitcoin::{Transaction, Txid};
|
||||||
@@ -137,9 +143,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = verify_tx(&signed_tx, &database, &blockchain);
|
let result = verify_tx(&signed_tx, &database, &blockchain);
|
||||||
assert!(result.is_err(), "Should fail with missing input tx");
|
assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(),
|
||||||
assert!(
|
|
||||||
matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid()),
|
|
||||||
"Error should be a `MissingInputTx` error"
|
"Error should be a `MissingInputTx` error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -147,9 +151,9 @@ mod test {
|
|||||||
database.set_raw_tx(&prev_tx).unwrap();
|
database.set_raw_tx(&prev_tx).unwrap();
|
||||||
|
|
||||||
let result = verify_tx(&unsigned_tx, &database, &blockchain);
|
let result = verify_tx(&unsigned_tx, &database, &blockchain);
|
||||||
assert!(result.is_err(), "Should fail since the TX is unsigned");
|
assert_matches!(
|
||||||
assert!(
|
result,
|
||||||
matches!(result, Err(VerifyError::Consensus(_))),
|
Err(VerifyError::Consensus(_)),
|
||||||
"Error should be a `Consensus` error"
|
"Error should be a `Consensus` error"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user