Compare commits

..

41 Commits

Author SHA1 Message Date
Alekos Filini
549cd24812 Bump version to 0.6.0 2021-04-14 17:27:28 +02:00
Alekos Filini
a841b5d635 Use published bdk-testutils-macros 2021-04-14 17:26:40 +02:00
Alekos Filini
16ceb6cb30 Bump version of bdk-testutils-macros 2021-04-14 17:25:11 +02:00
Alekos Filini
edfd7d454c Merge commit 'refs/pull/325/head' of github.com:bitcoindevkit/bdk into release/0.6.0 2021-04-13 09:25:47 +02:00
Alekos Filini
1d874e50c2 Merge commit 'refs/pull/326/head' of github.com:bitcoindevkit/bdk into release/0.6.0 2021-04-13 09:25:27 +02:00
Richard Ulrich
98127cc5da Allow setting RBF when bumping the fee of a transaction. This enables to further bump the fee. 2021-04-13 09:18:46 +02:00
Richard Ulrich
e243107bb6 Adding tests to demonstrate that we can't keep RBF when bumping the fee of a transaction. 2021-04-13 09:18:43 +02:00
Steve Myers
237a8d4e69 [ci] Update 'Build docs' job to use nightly-2021-03-23 2021-04-12 10:33:54 -07:00
Steve Myers
7f4042ba1b Bump version to 0.6.0-rc.1 2021-04-09 15:30:34 -07:00
Riccardo Casatta
192965413c Convert upper-case acronyms as suggested by CamelCase convention
see https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
2021-04-07 22:14:54 +02:00
Riccardo Casatta
745be7bea8 remove format! from assert! (will be an error in rust edition 2021) 2021-04-07 22:09:08 +02:00
Riccardo Casatta
b6007e05c1 upgrade CI rust version to 1.51.0 2021-04-07 22:08:56 +02:00
Steve Myers
f53654d9f4 Merge commit 'refs/pull/314/head' of github.com:bitcoindevkit/bdk 2021-04-06 10:21:07 -07:00
Daniel Karzel
e5ecc7f541 Avoid over-/underflow error in coin_select
Adds fix for edge-cases involving small UTXOs (where value < fee) where the coin_select calculation would panic with overflow/underflow errors.
Bitcoin is limited to 21*(10^6), so any Bitcoin amount fits into i64.
2021-04-06 10:21:55 +10:00
LLFourn
882a9c27cc Use tagged serialization for blockchain config
also make the config types Clone and PartialEq
2021-04-03 15:30:49 +11:00
Steve Myers
1e6b8e12b2 Merge commit 'refs/pull/310/head' of github.com:bitcoindevkit/bdk 2021-03-31 16:06:53 -07:00
Steve Myers
b226658977 [ci] update MSRV to 1.46.0 2021-03-29 11:17:50 -07:00
Alekos Filini
6d6776eb58 Merge branch 'release/0.5.1' 2021-03-29 19:48:00 +02:00
Alekos Filini
f1f844a5b6 Bump version to 0.5.2-dev 2021-03-29 19:10:47 +02:00
Alekos Filini
a3e45358de Bump version to 0.5.1 2021-03-29 18:28:06 +02:00
Alekos Filini
07e79f6e8a Update CHANGELOG.md 2021-03-29 18:28:04 +02:00
Steve Myers
d94b8f87a3 Pin hyper version to =0.14.4 2021-03-29 10:12:56 +02:00
Steve Myers
fdb895d26c Update DEVELOPMENT_CYCLE for unreleased dev-dependencies 2021-03-22 10:48:39 -07:00
Steve Myers
7041e96737 Fix new test to use new get_address() fn 2021-03-22 10:26:56 -07:00
Steve Myers
199f716ebb Fix bdk-testutils-macros version 2021-03-22 10:24:21 -07:00
Steve Myers
b12e358c1d Fix 0.5.1-dev CHANGELOG.md 2021-03-20 11:42:00 -07:00
Alekos Filini
f786f0e624 Merge branch 'release/0.5.0' of github.com:bitcoindevkit/bdk 2021-03-17 22:27:44 +01:00
Alekos Filini
71e0472dc9 Bump version to 0.5.1-dev 2021-03-17 20:58:23 +01:00
Alekos Filini
c456a252f8 Merge commit 'refs/pull/296/head' of github.com:bitcoindevkit/bdk 2021-03-17 11:30:31 +01:00
Riccardo Casatta
d837a762fc update changelog and fix docs 2021-03-17 11:24:48 +01:00
davemo88
e82dfa971e brevity 2021-03-16 10:20:07 -04:00
davemo88
cc17ac8859 update changelog 2021-03-15 21:58:03 -04:00
davemo88
3798b4d115 add get_psbt_input 2021-03-15 21:50:51 -04:00
Steve Myers
2d0f6c4ec5 [wallet] Add get_address(AddressIndex::Reset(u32)), update CHANGELOG 2021-03-15 09:13:23 -07:00
Steve Myers
f3b475ff0e [wallet] Refactor get_*_address() into get_address(AddressIndex), update CHANGELOG 2021-03-15 08:58:11 -07:00
Steve Myers
41ae202d02 [wallet] Add get_unused_address() function, update CHANGELOG 2021-03-15 08:58:09 -07:00
Steve Myers
fef6176275 [wallet] Add fetch_index() helper function 2021-03-15 08:58:07 -07:00
Riccardo Casatta
14ae64e09d [policy] Populate satisfaction with singatures already present in a PSBT 2021-03-08 16:58:56 +01:00
Riccardo Casatta
48215675b0 [policy] uncomment and update 4 tests: 2 ignored and 2 restored 2021-03-08 16:51:43 +01:00
Riccardo Casatta
37fa35b24a [policy] pass existing context instead of new one 2021-03-08 16:51:42 +01:00
Riccardo Casatta
23ec9c3ba0 [policy] pass secp context to setup_keys 2021-03-08 16:51:40 +01:00
34 changed files with 1099 additions and 546 deletions

View File

@@ -10,8 +10,8 @@ jobs:
strategy: strategy:
matrix: matrix:
rust: rust:
- 1.50.0 # STABLE - 1.51.0 # STABLE
- 1.45.0 # MSRV - 1.46.0 # MSRV
features: features:
- default - default
- minimal - minimal
@@ -98,7 +98,7 @@ jobs:
- name: Install rustup - name: Install rustup
run: curl https://sh.rustup.rs -sSf | sh -s -- -y run: curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Set default toolchain - name: Set default toolchain
run: $HOME/.cargo/bin/rustup default 1.50.0 # STABLE run: $HOME/.cargo/bin/rustup default 1.51.0 # STABLE
- name: Set profile - name: Set profile
run: $HOME/.cargo/bin/rustup set profile minimal run: $HOME/.cargo/bin/rustup set profile minimal
- name: Update toolchain - name: Update toolchain
@@ -131,7 +131,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.50.0 # STABLE run: rustup default 1.51.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
@@ -148,7 +148,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set default toolchain - name: Set default toolchain
run: rustup default 1.50.0 # STABLE run: rustup default 1.51.0 # STABLE
- name: Set profile - name: Set profile
run: rustup set profile minimal run: rustup set profile minimal
- name: Add clippy - name: Add clippy

View File

@@ -17,17 +17,14 @@ jobs:
~/.cargo/git ~/.cargo/git
target target
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Install nightly toolchain - name: Set default toolchain
uses: actions-rs/toolchain@v1 run: rustup default nightly-2021-03-23
with: - name: Set profile
profile: minimal run: rustup set profile minimal
toolchain: nightly - name: Update toolchain
override: true run: rustup update
- name: Build docs - name: Build docs
uses: actions-rs/cargo@v1 run: cargo rustdoc --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
with:
command: rustdoc
args: --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:

View File

@@ -6,6 +6,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Misc
#### Changed
- New minimum supported rust version is 1.46.0
- Changed `AnyBlockchainConfig` to use serde tagged representation.
### Descriptor
#### Added
- Added ability to analyze a `PSBT` to check which and how many signatures are already available
### Wallet
#### Changed
- `get_new_address()` refactored to `get_address(AddressIndex::New)` to support different `get_address()` index selection strategies
#### Added
- Added `get_address(AddressIndex::LastUnused)` which returns the last derived address if it has not been used or if used in a received transaction returns a new address
- Added `get_address(AddressIndex::Peek(u32))` which returns a derived address for a specified descriptor index but does not change the current index
- Added `get_address(AddressIndex::Reset(u32))` which returns a derived address for a specified descriptor index and resets current index to the given value
- Added `get_psbt_input` to create the corresponding psbt input for a local utxo.
#### Fixed
- Fixed `coin_select` calculation for UTXOs where `value < fee` that caused over-/underflow errors.
## [v0.5.1] - [v0.5.0]
### Misc
#### Changed
- Pin `hyper` to `=0.14.4` to make it compile on Rust 1.45
## [v0.5.0] - [v0.4.0] ## [v0.5.0] - [v0.4.0]
### Misc ### Misc
@@ -293,3 +321,4 @@ final transaction is created by calling `finish` on the builder.
[v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0 [v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0
[v0.4.0]: https://github.com/bitcoindevkit/bdk/compare/v0.3.0...v0.4.0 [v0.4.0]: https://github.com/bitcoindevkit/bdk/compare/v0.3.0...v0.4.0
[v0.5.0]: https://github.com/bitcoindevkit/bdk/compare/v0.4.0...v0.5.0 [v0.5.0]: https://github.com/bitcoindevkit/bdk/compare/v0.4.0...v0.5.0
[v0.5.1]: https://github.com/bitcoindevkit/bdk/compare/v0.5.0...v0.5.1

View File

@@ -46,7 +46,7 @@ Every new feature should be covered by functional tests where possible.
When refactoring, structure your PR to make it easy to review and don't When refactoring, structure your PR to make it easy to review and don't
hesitate to split it into multiple small, focused PRs. hesitate to split it into multiple small, focused PRs.
The Minimal Supported Rust Version is 1.45 (enforced by our CI). The Minimal Supported Rust Version is 1.46 (enforced by our CI).
Commits should cover both the issue fixed and the solution's rationale. Commits should cover both the issue fixed and the solution's rationale.
These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind. These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk" name = "bdk"
version = "0.5.0" version = "0.6.0"
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"
@@ -60,7 +60,7 @@ test-md-docs = ["electrum"]
[dev-dependencies] [dev-dependencies]
bdk-testutils = "0.4" bdk-testutils = "0.4"
bdk-testutils-macros = "0.4" bdk-testutils-macros = "0.5"
serial_test = "0.4" serial_test = "0.4"
lazy_static = "1.4" lazy_static = "1.4"
env_logger = "0.7" env_logger = "0.7"

View File

@@ -20,7 +20,7 @@ As soon as the release is tagged and published, the `release` branch will be mer
## Making the Release ## Making the Release
What follows are notes and procedures that maintaners can refer to when making releases. All the commits and tags must be signed and, ideally, also [timestamped](https://github.com/opentimestamps/opentimestamps-client/blob/master/doc/git-integration.md). What follows are notes and procedures that maintainers can refer to when making releases. All the commits and tags must be signed and, ideally, also [timestamped](https://github.com/opentimestamps/opentimestamps-client/blob/master/doc/git-integration.md).
Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordingly, our "minor" releases will only affect the "patch" value. Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordingly, our "minor" releases will only affect the "patch" value.
@@ -39,7 +39,8 @@ Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordin
11. Publish **all** the updated crates to crates.io. 11. Publish **all** the updated crates to crates.io.
12. Make a new commit to bump the version value to `x.y.(z+1)-dev`. The message should be "Bump version to x.y.(z+1)-dev". 12. Make a new commit to bump the version value to `x.y.(z+1)-dev`. The message should be "Bump version to x.y.(z+1)-dev".
13. Merge the release branch back into `master`. 13. Merge the release branch back into `master`.
14. Create the release on GitHub: go to "tags", click on the dots on the right and select "Create Release". Then set the title to `vx.y.z` and write down some brief release notes. 14. If the `master` branch contains any unreleased changes to the `bdk-macros`, `bdk-testutils`, or `bdk-testutils-macros` crates, change the `bdk` Cargo.toml `[dev-dependencies]` to point to the local path (ie. `bdk-testutils-macros = { path = "./testutils-macros"}`)
15. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs. 15. Create the release on GitHub: go to "tags", click on the dots on the right and select "Create Release". Then set the title to `vx.y.z` and write down some brief release notes.
16. Announce the release on Twitter, Discord and Telegram. 16. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
17. Celebrate :tada: 17. Announce the release on Twitter, Discord and Telegram.
18. Celebrate :tada:

View File

@@ -13,7 +13,7 @@
<a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a> <a href="https://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://codecov.io/gh/bitcoindevkit/bdk"><img src="https://codecov.io/gh/bitcoindevkit/bdk/branch/master/graph/badge.svg"/></a> <a href="https://codecov.io/gh/bitcoindevkit/bdk"><img src="https://codecov.io/gh/bitcoindevkit/bdk/branch/master/graph/badge.svg"/></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/2020/07/16/Rust-1.45.0.html"><img alt="Rustc Version 1.45+" src="https://img.shields.io/badge/rustc-1.45%2B-lightgrey.svg"/></a> <a href="https://blog.rust-lang.org/2020/08/27/Rust-1.46.0.html"><img alt="Rustc Version 1.46+" src="https://img.shields.io/badge/rustc-1.46%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>
@@ -67,6 +67,7 @@ fn main() -> Result<(), bdk::Error> {
```rust ```rust
use bdk::{Wallet, database::MemoryDatabase}; use bdk::{Wallet, database::MemoryDatabase};
use bdk::wallet::AddressIndex::New;
fn main() -> Result<(), bdk::Error> { fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new_offline( let wallet = Wallet::new_offline(
@@ -76,9 +77,9 @@ fn main() -> Result<(), bdk::Error> {
MemoryDatabase::default(), MemoryDatabase::default(),
)?; )?;
println!("Address #0: {}", wallet.get_new_address()?); println!("Address #0: {}", wallet.get_address(New)?);
println!("Address #1: {}", wallet.get_new_address()?); println!("Address #1: {}", wallet.get_address(New)?);
println!("Address #2: {}", wallet.get_new_address()?); println!("Address #2: {}", wallet.get_address(New)?);
Ok(()) Ok(())
} }
@@ -92,6 +93,7 @@ use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain}; use bdk::blockchain::{noop_progress, ElectrumBlockchain};
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
use bitcoin::consensus::serialize; use bitcoin::consensus::serialize;
@@ -107,7 +109,7 @@ fn main() -> Result<(), bdk::Error> {
wallet.sync(noop_progress(), None)?; wallet.sync(noop_progress(), None)?;
let send_to = wallet.get_new_address()?; let send_to = wallet.get_address(New)?;
let (psbt, details) = { let (psbt, details) = {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder

View File

@@ -13,11 +13,12 @@ use std::sync::Arc;
use bdk::bitcoin; use bdk::bitcoin;
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::descriptor::HDKeyPaths; use bdk::descriptor::HdKeyPaths;
use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError}; use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
use bdk::KeychainKind; use bdk::KeychainKind;
use bdk::Wallet; use bdk::Wallet;
use bdk::wallet::AddressIndex::New;
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;
use bitcoin::util::bip32::Fingerprint; use bitcoin::util::bip32::Fingerprint;
use bitcoin::{Network, Script}; use bitcoin::{Network, Script};
@@ -28,7 +29,7 @@ impl AddressValidator for DummyValidator {
fn validate( fn validate(
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
script: &Script, script: &Script,
) -> Result<(), AddressValidatorError> { ) -> Result<(), AddressValidatorError> {
let (_, path) = hd_keypaths let (_, path) = hd_keypaths
@@ -52,9 +53,9 @@ fn main() -> Result<(), bdk::Error> {
wallet.add_address_validator(Arc::new(DummyValidator)); wallet.add_address_validator(Arc::new(DummyValidator));
wallet.get_new_address()?; wallet.get_address(New)?;
wallet.get_new_address()?; wallet.get_address(New)?;
wallet.get_new_address()?; wallet.get_address(New)?;
Ok(()) Ok(())
} }

View File

@@ -28,6 +28,7 @@ use miniscript::policy::Concrete;
use miniscript::Descriptor; use miniscript::Descriptor;
use bdk::database::memory::MemoryDatabase; use bdk::database::memory::MemoryDatabase;
use bdk::wallet::AddressIndex::New;
use bdk::{KeychainKind, Wallet}; use bdk::{KeychainKind, Wallet};
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
@@ -90,7 +91,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.unwrap_or(Network::Testnet); .unwrap_or(Network::Testnet);
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?; let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
info!("... First address: {}", wallet.get_new_address()?); info!("... First address: {}", wallet.get_address(New)?);
if matches.is_present("parsed_policy") { if matches.is_present("parsed_policy") {
let spending_policy = wallet.policies(KeychainKind::External)?; let spending_policy = wallet.policies(KeychainKind::External)?;

View File

@@ -3,7 +3,7 @@
# Run various invocations of cargo check # Run various invocations of cargo check
features=( "default" "compiler" "electrum" "esplora" "compact_filters" "key-value-db" "async-interface" "all-keys" "keys-bip39" ) features=( "default" "compiler" "electrum" "esplora" "compact_filters" "key-value-db" "async-interface" "all-keys" "keys-bip39" )
toolchains=( "+stable" "+1.45" "+nightly" ) toolchains=( "+stable" "+1.46" "+nightly" )
main() { main() {
check_src check_src

View File

@@ -177,7 +177,34 @@ impl_from!(compact_filters::CompactFiltersBlockchain, AnyBlockchain, CompactFilt
/// This allows storing a single configuration that can be loaded into an [`AnyBlockchain`] /// This allows storing a single configuration that can be loaded into an [`AnyBlockchain`]
/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime /// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime
/// will find this particularly useful. /// will find this particularly useful.
#[derive(Debug, serde::Serialize, serde::Deserialize)] ///
/// This type can be serialized from a JSON object like:
///
/// ```
/// # #[cfg(feature = "electrum")]
/// # {
/// use bdk::blockchain::{electrum::ElectrumBlockchainConfig, AnyBlockchainConfig};
/// let config: AnyBlockchainConfig = serde_json::from_str(
/// r#"{
/// "type" : "electrum",
/// "url" : "ssl://electrum.blockstream.info:50002",
/// "retry": 2
/// }"#,
/// )
/// .unwrap();
/// assert_eq!(
/// config,
/// AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
/// url: "ssl://electrum.blockstream.info:50002".into(),
/// retry: 2,
/// socks5: None,
/// timeout: None
/// })
/// );
/// # }
/// ```
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AnyBlockchainConfig { pub enum AnyBlockchainConfig {
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
#[cfg_attr(docsrs, doc(cfg(feature = "electrum")))] #[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]

View File

@@ -237,7 +237,7 @@ impl Blockchain for CompactFiltersBlockchain {
let skip_blocks = self.skip_blocks.unwrap_or(0); let skip_blocks = self.skip_blocks.unwrap_or(0);
let cf_sync = Arc::new(CFSync::new(Arc::clone(&self.headers), skip_blocks, 0x00)?); let cf_sync = Arc::new(CfSync::new(Arc::clone(&self.headers), skip_blocks, 0x00)?);
let initial_height = self.headers.get_height()?; let initial_height = self.headers.get_height()?;
let total_bundles = (first_peer.get_version().start_height as usize) let total_bundles = (first_peer.get_version().start_height as usize)
@@ -456,7 +456,7 @@ impl Blockchain for CompactFiltersBlockchain {
} }
/// Data to connect to a Bitcoin P2P peer /// Data to connect to a Bitcoin P2P peer
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct BitcoinPeerConfig { pub struct BitcoinPeerConfig {
/// Peer address such as 127.0.0.1:18333 /// Peer address such as 127.0.0.1:18333
pub address: String, pub address: String,
@@ -467,7 +467,7 @@ pub struct BitcoinPeerConfig {
} }
/// Configuration for a [`CompactFiltersBlockchain`] /// Configuration for a [`CompactFiltersBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct CompactFiltersBlockchainConfig { pub struct CompactFiltersBlockchainConfig {
/// List of peers to try to connect to for asking headers and filters /// List of peers to try to connect to for asking headers and filters
pub peers: Vec<BitcoinPeerConfig>, pub peers: Vec<BitcoinPeerConfig>,
@@ -537,11 +537,11 @@ pub enum CompactFiltersError {
NoPeers, NoPeers,
/// Internal database error /// Internal database error
DB(rocksdb::Error), Db(rocksdb::Error),
/// Internal I/O error /// Internal I/O error
IO(std::io::Error), Io(std::io::Error),
/// Invalid BIP158 filter /// Invalid BIP158 filter
BIP158(bitcoin::util::bip158::Error), Bip158(bitcoin::util::bip158::Error),
/// Internal system time error /// Internal system time error
Time(std::time::SystemTimeError), Time(std::time::SystemTimeError),
@@ -557,9 +557,9 @@ impl fmt::Display for CompactFiltersError {
impl std::error::Error for CompactFiltersError {} impl std::error::Error for CompactFiltersError {}
impl_error!(rocksdb::Error, DB, CompactFiltersError); impl_error!(rocksdb::Error, Db, CompactFiltersError);
impl_error!(std::io::Error, IO, CompactFiltersError); impl_error!(std::io::Error, Io, CompactFiltersError);
impl_error!(bitcoin::util::bip158::Error, BIP158, CompactFiltersError); impl_error!(bitcoin::util::bip158::Error, Bip158, CompactFiltersError);
impl_error!(std::time::SystemTimeError, Time, CompactFiltersError); impl_error!(std::time::SystemTimeError, Time, CompactFiltersError);
impl From<crate::error::Error> for CompactFiltersError { impl From<crate::error::Error> for CompactFiltersError {

View File

@@ -120,7 +120,7 @@ impl Encodable for BundleStatus {
BundleStatus::Init => { BundleStatus::Init => {
written += 0x00u8.consensus_encode(&mut e)?; written += 0x00u8.consensus_encode(&mut e)?;
} }
BundleStatus::CFHeaders { cf_headers } => { BundleStatus::CfHeaders { cf_headers } => {
written += 0x01u8.consensus_encode(&mut e)?; written += 0x01u8.consensus_encode(&mut e)?;
written += VarInt(cf_headers.len() as u64).consensus_encode(&mut e)?; written += VarInt(cf_headers.len() as u64).consensus_encode(&mut e)?;
for header in cf_headers { for header in cf_headers {
@@ -171,7 +171,7 @@ impl Decodable for BundleStatus {
cf_headers.push(FilterHeader::consensus_decode(&mut d)?); cf_headers.push(FilterHeader::consensus_decode(&mut d)?);
} }
Ok(BundleStatus::CFHeaders { cf_headers }) Ok(BundleStatus::CfHeaders { cf_headers })
} }
0x02 => { 0x02 => {
let num = VarInt::consensus_decode(&mut d)?; let num = VarInt::consensus_decode(&mut d)?;
@@ -623,26 +623,26 @@ impl<T: StoreType> fmt::Debug for ChainStore<T> {
pub enum BundleStatus { pub enum BundleStatus {
Init, Init,
CFHeaders { cf_headers: Vec<FilterHeader> }, CfHeaders { cf_headers: Vec<FilterHeader> },
CFilters { cf_filters: Vec<Vec<u8>> }, CFilters { cf_filters: Vec<Vec<u8>> },
Processed { cf_filters: Vec<Vec<u8>> }, Processed { cf_filters: Vec<Vec<u8>> },
Tip { cf_filters: Vec<Vec<u8>> }, Tip { cf_filters: Vec<Vec<u8>> },
Pruned, Pruned,
} }
pub struct CFStore { pub struct CfStore {
store: Arc<RwLock<DB>>, store: Arc<RwLock<DB>>,
filter_type: u8, filter_type: u8,
} }
type BundleEntry = (BundleStatus, FilterHeader); type BundleEntry = (BundleStatus, FilterHeader);
impl CFStore { impl CfStore {
pub fn new( pub fn new(
headers_store: &ChainStore<Full>, headers_store: &ChainStore<Full>,
filter_type: u8, filter_type: u8,
) -> Result<Self, CompactFiltersError> { ) -> Result<Self, CompactFiltersError> {
let cf_store = CFStore { let cf_store = CfStore {
store: Arc::clone(&headers_store.store), store: Arc::clone(&headers_store.store),
filter_type, filter_type,
}; };
@@ -782,7 +782,7 @@ impl CFStore {
} }
let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key(); let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key();
let value = (BundleStatus::CFHeaders { cf_headers }, checkpoint); let value = (BundleStatus::CfHeaders { cf_headers }, checkpoint);
read_store.put(key, value.serialize())?; read_store.put(key, value.serialize())?;

View File

@@ -25,22 +25,22 @@ use crate::error::Error;
pub(crate) const BURIED_CONFIRMATIONS: usize = 100; pub(crate) const BURIED_CONFIRMATIONS: usize = 100;
pub struct CFSync { pub struct CfSync {
headers_store: Arc<ChainStore<Full>>, headers_store: Arc<ChainStore<Full>>,
cf_store: Arc<CFStore>, cf_store: Arc<CfStore>,
skip_blocks: usize, skip_blocks: usize,
bundles: Mutex<VecDeque<(BundleStatus, FilterHeader, usize)>>, bundles: Mutex<VecDeque<(BundleStatus, FilterHeader, usize)>>,
} }
impl CFSync { impl CfSync {
pub fn new( pub fn new(
headers_store: Arc<ChainStore<Full>>, headers_store: Arc<ChainStore<Full>>,
skip_blocks: usize, skip_blocks: usize,
filter_type: u8, filter_type: u8,
) -> Result<Self, CompactFiltersError> { ) -> Result<Self, CompactFiltersError> {
let cf_store = Arc::new(CFStore::new(&headers_store, filter_type)?); let cf_store = Arc::new(CfStore::new(&headers_store, filter_type)?);
Ok(CFSync { Ok(CfSync {
headers_store, headers_store,
cf_store, cf_store,
skip_blocks, skip_blocks,
@@ -151,7 +151,7 @@ impl CFSync {
checkpoint, checkpoint,
headers_resp.filter_hashes, headers_resp.filter_hashes,
)? { )? {
BundleStatus::CFHeaders { cf_headers } => cf_headers, BundleStatus::CfHeaders { cf_headers } => cf_headers,
_ => return Err(CompactFiltersError::InvalidResponse), _ => return Err(CompactFiltersError::InvalidResponse),
}; };
@@ -171,7 +171,7 @@ impl CFSync {
.cf_store .cf_store
.advance_to_cf_filters(index, checkpoint, cf_headers, filters)?; .advance_to_cf_filters(index, checkpoint, cf_headers, filters)?;
} }
if let BundleStatus::CFHeaders { cf_headers } = status { if let BundleStatus::CfHeaders { cf_headers } = status {
log::trace!("status: CFHeaders"); log::trace!("status: CFHeaders");
peer.get_cf_filters( peer.get_cf_filters(

View File

@@ -33,7 +33,7 @@ use bitcoin::{BlockHeader, Script, Transaction, Txid};
use electrum_client::{Client, ConfigBuilder, ElectrumApi, Socks5Config}; use electrum_client::{Client, ConfigBuilder, ElectrumApi, Socks5Config};
use self::utils::{ELSGetHistoryRes, ElectrumLikeSync}; use self::utils::{ElectrumLikeSync, ElsGetHistoryRes};
use super::*; use super::*;
use crate::database::BatchDatabase; use crate::database::BatchDatabase;
use crate::error::Error; use crate::error::Error;
@@ -107,7 +107,7 @@ impl ElectrumLikeSync for Client {
fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>( fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>(
&self, &self,
scripts: I, scripts: I,
) -> Result<Vec<Vec<ELSGetHistoryRes>>, Error> { ) -> Result<Vec<Vec<ElsGetHistoryRes>>, Error> {
self.batch_script_get_history(scripts) self.batch_script_get_history(scripts)
.map(|v| { .map(|v| {
v.into_iter() v.into_iter()
@@ -116,7 +116,7 @@ impl ElectrumLikeSync for Client {
.map( .map(
|electrum_client::GetHistoryRes { |electrum_client::GetHistoryRes {
height, tx_hash, .. height, tx_hash, ..
}| ELSGetHistoryRes { }| ElsGetHistoryRes {
height, height,
tx_hash, tx_hash,
}, },
@@ -144,7 +144,7 @@ impl ElectrumLikeSync for Client {
} }
/// Configuration for an [`ElectrumBlockchain`] /// Configuration for an [`ElectrumBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct ElectrumBlockchainConfig { pub struct ElectrumBlockchainConfig {
/// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port /// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port
/// ///

View File

@@ -39,7 +39,7 @@ use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::{sha256, Hash}; use bitcoin::hashes::{sha256, Hash};
use bitcoin::{BlockHash, BlockHeader, Script, Transaction, Txid}; use bitcoin::{BlockHash, BlockHeader, Script, Transaction, Txid};
use self::utils::{ELSGetHistoryRes, ElectrumLikeSync}; use self::utils::{ElectrumLikeSync, ElsGetHistoryRes};
use super::*; use super::*;
use crate::database::BatchDatabase; use crate::database::BatchDatabase;
use crate::error::Error; use crate::error::Error;
@@ -210,7 +210,7 @@ impl UrlClient {
async fn _script_get_history( async fn _script_get_history(
&self, &self,
script: &Script, script: &Script,
) -> Result<Vec<ELSGetHistoryRes>, EsploraError> { ) -> Result<Vec<ElsGetHistoryRes>, EsploraError> {
let mut result = Vec::new(); let mut result = Vec::new();
let scripthash = Self::script_to_scripthash(script); let scripthash = Self::script_to_scripthash(script);
@@ -227,7 +227,7 @@ impl UrlClient {
.json::<Vec<EsploraGetHistory>>() .json::<Vec<EsploraGetHistory>>()
.await? .await?
.into_iter() .into_iter()
.map(|x| ELSGetHistoryRes { .map(|x| ElsGetHistoryRes {
tx_hash: x.txid, tx_hash: x.txid,
height: x.status.block_height.unwrap_or(0) as i32, height: x.status.block_height.unwrap_or(0) as i32,
}), }),
@@ -261,7 +261,7 @@ impl UrlClient {
debug!("... adding {} confirmed transactions", len); debug!("... adding {} confirmed transactions", len);
result.extend(response.into_iter().map(|x| ELSGetHistoryRes { result.extend(response.into_iter().map(|x| ElsGetHistoryRes {
tx_hash: x.txid, tx_hash: x.txid,
height: x.status.block_height.unwrap_or(0) as i32, height: x.status.block_height.unwrap_or(0) as i32,
})); }));
@@ -291,7 +291,7 @@ impl ElectrumLikeSync for UrlClient {
fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script>>( fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script>>(
&self, &self,
scripts: I, scripts: I,
) -> Result<Vec<Vec<ELSGetHistoryRes>>, Error> { ) -> Result<Vec<Vec<ElsGetHistoryRes>>, Error> {
let future = async { let future = async {
let mut results = vec![]; let mut results = vec![];
for chunk in ChunksIterator::new(scripts.into_iter(), self.concurrency as usize) { for chunk in ChunksIterator::new(scripts.into_iter(), self.concurrency as usize) {
@@ -299,7 +299,7 @@ impl ElectrumLikeSync for UrlClient {
for script in chunk { for script in chunk {
futs.push(self._script_get_history(&script)); futs.push(self._script_get_history(&script));
} }
let partial_results: Vec<Vec<ELSGetHistoryRes>> = futs.try_collect().await?; let partial_results: Vec<Vec<ElsGetHistoryRes>> = futs.try_collect().await?;
results.extend(partial_results); results.extend(partial_results);
} }
Ok(stream::iter(results).collect().await) Ok(stream::iter(results).collect().await)
@@ -361,7 +361,7 @@ struct EsploraGetHistory {
} }
/// Configuration for an [`EsploraBlockchain`] /// Configuration for an [`EsploraBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct EsploraBlockchainConfig { pub struct EsploraBlockchainConfig {
/// Base URL of the esplora service /// Base URL of the esplora service
/// ///

View File

@@ -26,7 +26,7 @@ use crate::wallet::time::Instant;
use crate::wallet::utils::ChunksIterator; use crate::wallet::utils::ChunksIterator;
#[derive(Debug)] #[derive(Debug)]
pub struct ELSGetHistoryRes { pub struct ElsGetHistoryRes {
pub height: i32, pub height: i32,
pub tx_hash: Txid, pub tx_hash: Txid,
} }
@@ -37,7 +37,7 @@ pub trait ElectrumLikeSync {
fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>( fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>(
&self, &self,
scripts: I, scripts: I,
) -> Result<Vec<Vec<ELSGetHistoryRes>>, Error>; ) -> Result<Vec<Vec<ElsGetHistoryRes>>, Error>;
fn els_batch_transaction_get<'s, I: IntoIterator<Item = &'s Txid> + Clone>( fn els_batch_transaction_get<'s, I: IntoIterator<Item = &'s Txid> + Clone>(
&self, &self,
@@ -77,7 +77,7 @@ pub trait ElectrumLikeSync {
for (i, chunk) in ChunksIterator::new(script_iter, stop_gap).enumerate() { for (i, chunk) in ChunksIterator::new(script_iter, stop_gap).enumerate() {
// TODO if i == last, should create another chunk of addresses in db // TODO if i == last, should create another chunk of addresses in db
let call_result: Vec<Vec<ELSGetHistoryRes>> = let call_result: Vec<Vec<ElsGetHistoryRes>> =
maybe_await!(self.els_batch_script_get_history(chunk.iter()))?; maybe_await!(self.els_batch_script_get_history(chunk.iter()))?;
let max_index = call_result let max_index = call_result
.iter() .iter()
@@ -87,7 +87,7 @@ pub trait ElectrumLikeSync {
if let Some(max) = max_index { if let Some(max) = max_index {
max_indexes.insert(keychain, max + (i * chunk_size) as u32); max_indexes.insert(keychain, max + (i * chunk_size) as u32);
} }
let flattened: Vec<ELSGetHistoryRes> = call_result.into_iter().flatten().collect(); let flattened: Vec<ElsGetHistoryRes> = call_result.into_iter().flatten().collect();
debug!("#{} of {:?} results:{}", i, keychain, flattened.len()); debug!("#{} of {:?} results:{}", i, keychain, flattened.len());
if flattened.is_empty() { if flattened.is_empty() {
// Didn't find anything in the last `stop_gap` script_pubkeys, breaking // Didn't find anything in the last `stop_gap` script_pubkeys, breaking

View File

@@ -39,7 +39,7 @@ macro_rules! impl_batch_operations {
} }
fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> { fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); let key = MapKey::Utxo(Some(&utxo.outpoint)).as_map_key();
let value = json!({ let value = json!({
"t": utxo.txout, "t": utxo.txout,
"i": utxo.keychain, "i": utxo.keychain,
@@ -108,7 +108,7 @@ macro_rules! impl_batch_operations {
} }
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
let res = self.remove(key); let res = self.remove(key);
let res = $process_delete!(res); let res = $process_delete!(res);
@@ -222,7 +222,7 @@ impl Database for Tree {
} }
fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> { fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
let key = MapKey::UTXO(None).as_map_key(); let key = MapKey::Utxo(None).as_map_key();
self.scan_prefix(key) self.scan_prefix(key)
.map(|x| -> Result<_, Error> { .map(|x| -> Result<_, Error> {
let (k, v) = x?; let (k, v) = x?;
@@ -293,7 +293,7 @@ impl Database for Tree {
} }
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
self.get(key)? self.get(key)?
.map(|b| -> Result<_, Error> { .map(|b| -> Result<_, Error> {
let mut val: serde_json::Value = serde_json::from_slice(&b)?; let mut val: serde_json::Value = serde_json::from_slice(&b)?;

View File

@@ -36,7 +36,7 @@ use crate::types::*;
pub(crate) enum MapKey<'a> { pub(crate) enum MapKey<'a> {
Path((Option<KeychainKind>, Option<u32>)), Path((Option<KeychainKind>, Option<u32>)),
Script(Option<&'a Script>), Script(Option<&'a Script>),
UTXO(Option<&'a OutPoint>), Utxo(Option<&'a OutPoint>),
RawTx(Option<&'a Txid>), RawTx(Option<&'a Txid>),
Transaction(Option<&'a Txid>), Transaction(Option<&'a Txid>),
LastIndex(KeychainKind), LastIndex(KeychainKind),
@@ -54,7 +54,7 @@ impl MapKey<'_> {
v v
} }
MapKey::Script(_) => b"s".to_vec(), MapKey::Script(_) => b"s".to_vec(),
MapKey::UTXO(_) => b"u".to_vec(), MapKey::Utxo(_) => b"u".to_vec(),
MapKey::RawTx(_) => b"r".to_vec(), MapKey::RawTx(_) => b"r".to_vec(),
MapKey::Transaction(_) => b"t".to_vec(), MapKey::Transaction(_) => b"t".to_vec(),
MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(), MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(),
@@ -66,7 +66,7 @@ impl MapKey<'_> {
match self { match self {
MapKey::Path((_, Some(child))) => child.to_be_bytes().to_vec(), MapKey::Path((_, Some(child))) => child.to_be_bytes().to_vec(),
MapKey::Script(Some(s)) => serialize(*s), MapKey::Script(Some(s)) => serialize(*s),
MapKey::UTXO(Some(s)) => serialize(*s), MapKey::Utxo(Some(s)) => serialize(*s),
MapKey::RawTx(Some(s)) => serialize(*s), MapKey::RawTx(Some(s)) => serialize(*s),
MapKey::Transaction(Some(s)) => serialize(*s), MapKey::Transaction(Some(s)) => serialize(*s),
_ => vec![], _ => vec![],
@@ -145,7 +145,7 @@ impl BatchOperations for MemoryDatabase {
} }
fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> { fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); let key = MapKey::Utxo(Some(&utxo.outpoint)).as_map_key();
self.map self.map
.insert(key, Box::new((utxo.txout.clone(), utxo.keychain))); .insert(key, Box::new((utxo.txout.clone(), utxo.keychain)));
@@ -211,7 +211,7 @@ impl BatchOperations for MemoryDatabase {
} }
} }
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
let res = self.map.remove(&key); let res = self.map.remove(&key);
self.deleted_keys.push(key); self.deleted_keys.push(key);
@@ -304,7 +304,7 @@ impl Database for MemoryDatabase {
} }
fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> { fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
let key = MapKey::UTXO(None).as_map_key(); let key = MapKey::Utxo(None).as_map_key();
self.map self.map
.range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
.map(|(k, v)| { .map(|(k, v)| {
@@ -370,7 +370,7 @@ impl Database for MemoryDatabase {
} }
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
Ok(self.map.get(&key).map(|b| { Ok(self.map.get(&key).map(|b| {
let (txout, keychain) = b.downcast_ref().cloned().unwrap(); let (txout, keychain) = b.downcast_ref().cloned().unwrap();
LocalUtxo { LocalUtxo {

View File

@@ -15,7 +15,7 @@
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Invalid HD Key path, such as having a wildcard but a length != 1 /// Invalid HD Key path, such as having a wildcard but a length != 1
InvalidHDKeyPath, InvalidHdKeyPath,
/// The provided descriptor doesn't match its checksum /// The provided descriptor doesn't match its checksum
InvalidDescriptorChecksum, InvalidDescriptorChecksum,
/// The descriptor contains hardened derivation steps on public extended keys /// The descriptor contains hardened derivation steps on public extended keys
@@ -32,11 +32,11 @@ pub enum Error {
InvalidDescriptorCharacter(char), InvalidDescriptorCharacter(char),
/// BIP32 error /// BIP32 error
BIP32(bitcoin::util::bip32::Error), Bip32(bitcoin::util::bip32::Error),
/// Error during base58 decoding /// Error during base58 decoding
Base58(bitcoin::util::base58::Error), Base58(bitcoin::util::base58::Error),
/// Key-related error /// Key-related error
PK(bitcoin::util::key::Error), Pk(bitcoin::util::key::Error),
/// Miniscript error /// Miniscript error
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
/// Hex decoding error /// Hex decoding error
@@ -47,7 +47,7 @@ impl From<crate::keys::KeyError> for Error {
fn from(key_error: crate::keys::KeyError) -> Error { fn from(key_error: crate::keys::KeyError) -> Error {
match key_error { match key_error {
crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner), crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner), crate::keys::KeyError::Bip32(inner) => Error::Bip32(inner),
e => Error::Key(e), e => Error::Key(e),
} }
} }
@@ -61,9 +61,9 @@ impl std::fmt::Display for Error {
impl std::error::Error for Error {} impl std::error::Error for Error {}
impl_error!(bitcoin::util::bip32::Error, BIP32); impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::util::base58::Error, Base58); impl_error!(bitcoin::util::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, PK); impl_error!(bitcoin::util::key::Error, Pk);
impl_error!(miniscript::Error, Miniscript); impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::hashes::hex::Error, Hex); impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(crate::descriptor::policy::PolicyError, Policy); impl_error!(crate::descriptor::policy::PolicyError, Policy);

View File

@@ -56,7 +56,7 @@ pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
/// ///
/// [`psbt::Input`]: bitcoin::util::psbt::Input /// [`psbt::Input`]: bitcoin::util::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output /// [`psbt::Output`]: bitcoin::util::psbt::Output
pub type HDKeyPaths = BTreeMap<PublicKey, KeySource>; pub type HdKeyPaths = BTreeMap<PublicKey, KeySource>;
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`] /// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
pub trait IntoWalletDescriptor { pub trait IntoWalletDescriptor {
@@ -329,7 +329,7 @@ impl XKeyUtils for DescriptorXKey<ExtendedPrivKey> {
} }
pub(crate) trait DerivedDescriptorMeta { pub(crate) trait DerivedDescriptorMeta {
fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>; fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HdKeyPaths, DescriptorError>;
} }
pub(crate) trait DescriptorMeta { pub(crate) trait DescriptorMeta {
@@ -337,7 +337,7 @@ pub(crate) trait DescriptorMeta {
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>; fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
fn derive_from_hd_keypaths<'s>( fn derive_from_hd_keypaths<'s>(
&self, &self,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx, secp: &'s SecpCtx,
) -> Option<DerivedDescriptor<'s>>; ) -> Option<DerivedDescriptor<'s>>;
fn derive_from_psbt_input<'s>( fn derive_from_psbt_input<'s>(
@@ -406,7 +406,7 @@ impl DescriptorMeta for ExtendedDescriptor {
fn derive_from_hd_keypaths<'s>( fn derive_from_hd_keypaths<'s>(
&self, &self,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx, secp: &'s SecpCtx,
) -> Option<DerivedDescriptor<'s>> { ) -> Option<DerivedDescriptor<'s>> {
let index: HashMap<_, _> = hd_keypaths.values().map(|(a, b)| (a, b)).collect(); let index: HashMap<_, _> = hd_keypaths.values().map(|(a, b)| (a, b)).collect();
@@ -505,7 +505,7 @@ impl DescriptorMeta for ExtendedDescriptor {
} }
impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> { impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> { fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HdKeyPaths, DescriptorError> {
let mut answer = BTreeMap::new(); let mut answer = BTreeMap::new();
self.for_each_key(|key| { self.for_each_key(|key| {
if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() { if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
@@ -537,7 +537,7 @@ mod test {
use bitcoin::util::{bip32, psbt}; use bitcoin::util::{bip32, psbt};
use super::*; use super::*;
use crate::psbt::PSBTUtils; use crate::psbt::PsbtUtils;
#[test] #[test]
fn test_derive_from_psbt_input_wpkh_wif() { fn test_derive_from_psbt_input_wpkh_wif() {

View File

@@ -46,22 +46,30 @@ use bitcoin::util::bip32::Fingerprint;
use bitcoin::PublicKey; use bitcoin::PublicKey;
use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner}; use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; use miniscript::{
Descriptor, ForEachKey, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal,
ToPublicKey,
};
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace}; use log::{debug, error, info, trace};
use crate::descriptor::{DerivedDescriptorKey, ExtractPolicy}; use crate::descriptor::{
DerivedDescriptor, DerivedDescriptorKey, DescriptorMeta, ExtendedDescriptor, ExtractPolicy,
};
use crate::psbt::PsbtUtils;
use crate::wallet::signer::{SignerId, SignersContainer}; use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{self, SecpCtx}; use crate::wallet::utils::{self, SecpCtx};
use super::checksum::get_checksum; use super::checksum::get_checksum;
use super::error::Error; use super::error::Error;
use super::XKeyUtils; use super::XKeyUtils;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use miniscript::psbt::PsbtInputSatisfier;
/// Raw public key or extended key fingerprint /// Raw public key or extended key fingerprint
#[derive(Debug, Clone, Default, Serialize)] #[derive(Debug, Clone, Default, Serialize)]
pub struct PKOrF { pub struct PkOrF {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pubkey: Option<PublicKey>, pubkey: Option<PublicKey>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -70,14 +78,14 @@ pub struct PKOrF {
fingerprint: Option<Fingerprint>, fingerprint: Option<Fingerprint>,
} }
impl PKOrF { impl PkOrF {
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self { fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
match k { match k {
DescriptorPublicKey::SinglePub(pubkey) => PKOrF { DescriptorPublicKey::SinglePub(pubkey) => PkOrF {
pubkey: Some(pubkey.key), pubkey: Some(pubkey.key),
..Default::default() ..Default::default()
}, },
DescriptorPublicKey::XPub(xpub) => PKOrF { DescriptorPublicKey::XPub(xpub) => PkOrF {
fingerprint: Some(xpub.root_fingerprint(secp)), fingerprint: Some(xpub.root_fingerprint(secp)),
..Default::default() ..Default::default()
}, },
@@ -85,7 +93,7 @@ impl PKOrF {
} }
fn from_key_hash(k: hash160::Hash) -> Self { fn from_key_hash(k: hash160::Hash) -> Self {
PKOrF { PkOrF {
pubkey_hash: Some(k), pubkey_hash: Some(k),
..Default::default() ..Default::default()
} }
@@ -98,26 +106,26 @@ impl PKOrF {
pub enum SatisfiableItem { pub enum SatisfiableItem {
// Leaves // Leaves
/// Signature for a raw public key /// Signature for a raw public key
Signature(PKOrF), Signature(PkOrF),
/// Signature for an extended key fingerprint /// Signature for an extended key fingerprint
SignatureKey(PKOrF), SignatureKey(PkOrF),
/// SHA256 preimage hash /// SHA256 preimage hash
SHA256Preimage { Sha256Preimage {
/// The digest value /// The digest value
hash: sha256::Hash, hash: sha256::Hash,
}, },
/// Double SHA256 preimage hash /// Double SHA256 preimage hash
HASH256Preimage { Hash256Preimage {
/// The digest value /// The digest value
hash: sha256d::Hash, hash: sha256d::Hash,
}, },
/// RIPEMD160 preimage hash /// RIPEMD160 preimage hash
RIPEMD160Preimage { Ripemd160Preimage {
/// The digest value /// The digest value
hash: ripemd160::Hash, hash: ripemd160::Hash,
}, },
/// SHA256 then RIPEMD160 preimage hash /// SHA256 then RIPEMD160 preimage hash
HASH160Preimage { Hash160Preimage {
/// The digest value /// The digest value
hash: hash160::Hash, hash: hash160::Hash,
}, },
@@ -134,7 +142,7 @@ pub enum SatisfiableItem {
/// Multi-signature public keys with threshold count /// Multi-signature public keys with threshold count
Multisig { Multisig {
/// The raw public key or extended key fingerprint /// The raw public key or extended key fingerprint
keys: Vec<PKOrF>, keys: Vec<PkOrF>,
/// The required threshold count /// The required threshold count
threshold: usize, threshold: usize,
}, },
@@ -256,7 +264,7 @@ pub enum Satisfaction {
n: usize, n: usize,
/// Threshold /// Threshold
m: usize, m: usize,
/// The items that can be satisfied by the descriptor /// The items that can be satisfied by the descriptor or are satisfied in the PSBT
items: Vec<usize>, items: Vec<usize>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Whether the items are sorted in lexicographic order (used by `sortedmulti`) /// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
@@ -431,7 +439,7 @@ pub struct Policy {
/// Type of this policy node /// Type of this policy node
#[serde(flatten)] #[serde(flatten)]
pub item: SatisfiableItem, pub item: SatisfiableItem,
/// How a much given PSBT already satisfies this polcy node **(currently unused)** /// How much a given PSBT already satisfies this policy node in terms of signatures
pub satisfaction: Satisfaction, pub satisfaction: Satisfaction,
/// How the wallet's descriptor can satisfy this policy node /// How the wallet's descriptor can satisfy this policy node
pub contribution: Satisfaction, pub contribution: Satisfaction,
@@ -577,7 +585,7 @@ impl Policy {
return Ok(None); return Ok(None);
} }
let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k, secp)).collect(); let parsed_keys = keys.iter().map(|k| PkOrF::from_key(k, secp)).collect();
let mut contribution = Satisfaction::Partial { let mut contribution = Satisfaction::Partial {
n: keys.len(), n: keys.len(),
@@ -690,6 +698,52 @@ impl Policy {
_ => Ok(Condition::default()), _ => Ok(Condition::default()),
} }
} }
/// fill `self.satisfaction` with the signatures we already have in the PSBT
pub fn fill_satisfactions(
&mut self,
psbt: &PSBT,
desc: &ExtendedDescriptor, // can't put in self because non Serialize
secp: &SecpCtx,
) -> Result<(), Error> {
// Start from an empty Satisfaction (like a contribution without signers)
let policy = desc.extract_policy(&SignersContainer::default(), &secp)?;
if let Some(policy) = policy {
self.satisfaction = policy.contribution;
for (i, input) in psbt.inputs.iter().enumerate() {
let s = PsbtInputSatisfier::new(psbt, i);
let derived_desc = desc.derive_from_psbt_input(input, psbt.get_utxo_for(i), secp);
self.add_satisfaction(s, derived_desc);
}
self.satisfaction.finalize();
}
Ok(())
}
fn add_satisfaction<S: Satisfier<bitcoin::PublicKey>>(
&mut self,
satisfier: S,
derived_desc: Option<DerivedDescriptor>,
) {
if let Some(derived_desc) = derived_desc {
let mut index = 0;
derived_desc.for_each_key(|k| {
if satisfier.lookup_sig(&k.as_key().to_public_key()).is_some() {
//TODO check signature verifies
let _ = self.satisfaction.add(
&Satisfaction::Complete {
condition: Default::default(),
},
index,
);
}
index += 1;
true
});
}
}
} }
impl From<SatisfiableItem> for Policy { impl From<SatisfiableItem> for Policy {
@@ -706,7 +760,7 @@ fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
} }
fn signature(key: &DescriptorPublicKey, signers: &SignersContainer, secp: &SecpCtx) -> Policy { fn signature(key: &DescriptorPublicKey, signers: &SignersContainer, secp: &SecpCtx) -> Policy {
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(key, secp)).into(); let mut policy: Policy = SatisfiableItem::Signature(PkOrF::from_key(key, secp)).into();
policy.contribution = if signers.find(signer_id(key, secp)).is_some() { policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
Satisfaction::Complete { Satisfaction::Complete {
@@ -727,7 +781,7 @@ fn signature_key(
let key_hash = DerivedDescriptorKey::new(key.clone(), secp) let key_hash = DerivedDescriptorKey::new(key.clone(), secp)
.to_public_key() .to_public_key()
.to_pubkeyhash(); .to_pubkeyhash();
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(key_hash)).into(); let mut policy: Policy = SatisfiableItem::Signature(PkOrF::from_key_hash(key_hash)).into();
if signers.find(SignerId::PkHash(key_hash)).is_some() { if signers.find(SignerId::PkHash(key_hash)).is_some() {
policy.contribution = Satisfaction::Complete { policy.contribution = Satisfaction::Complete {
@@ -771,15 +825,15 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
Some(policy) Some(policy)
} }
Terminal::Sha256(hash) => Some(SatisfiableItem::SHA256Preimage { hash: *hash }.into()), Terminal::Sha256(hash) => Some(SatisfiableItem::Sha256Preimage { hash: *hash }.into()),
Terminal::Hash256(hash) => { Terminal::Hash256(hash) => {
Some(SatisfiableItem::HASH256Preimage { hash: *hash }.into()) Some(SatisfiableItem::Hash256Preimage { hash: *hash }.into())
} }
Terminal::Ripemd160(hash) => { Terminal::Ripemd160(hash) => {
Some(SatisfiableItem::RIPEMD160Preimage { hash: *hash }.into()) Some(SatisfiableItem::Ripemd160Preimage { hash: *hash }.into())
} }
Terminal::Hash160(hash) => { Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into()) Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
} }
Terminal::Multi(k, pks) => Policy::make_multisig(pks, signers, *k, false, secp)?, Terminal::Multi(k, pks) => Policy::make_multisig(pks, signers, *k, false, secp)?,
// Identities // Identities
@@ -879,12 +933,15 @@ mod test {
use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor}; use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
use super::*; use super::*;
use crate::bitcoin::consensus::deserialize;
use crate::descriptor::derived::AsDerived;
use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh}; use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh};
use crate::keys::{DescriptorKey, IntoDescriptorKey}; use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer; use crate::wallet::signer::SignersContainer;
use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32; use bitcoin::util::bip32;
use bitcoin::Network; use bitcoin::Network;
use miniscript::DescriptorTrait;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
@@ -895,9 +952,10 @@ mod test {
fn setup_keys<Ctx: ScriptContext>( fn setup_keys<Ctx: ScriptContext>(
tprv: &str, tprv: &str,
path: &str,
secp: &SecpCtx,
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) { ) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
let secp: Secp256k1<All> = Secp256k1::new(); let path = bip32::DerivationPath::from_str(path).unwrap();
let path = bip32::DerivationPath::from_str(PATH).unwrap();
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap(); let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv); let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv);
let fingerprint = tprv.fingerprint(&secp); let fingerprint = tprv.fingerprint(&secp);
@@ -913,14 +971,14 @@ mod test {
fn test_extract_policy_for_wpkh() { fn test_extract_policy_for_wpkh() {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
let desc = descriptor!(wpkh(pubkey)).unwrap(); let desc = descriptor!(wpkh(pubkey)).unwrap();
let (wallet_desc, keymap) = desc let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet) .into_wallet_descriptor(&secp, Network::Testnet)
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -935,7 +993,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -948,63 +1006,66 @@ mod test {
} }
// 2 pub keys descriptor, required 2 prv keys // 2 pub keys descriptor, required 2 prv keys
// #[test] #[test]
// fn test_extract_policy_for_sh_multi_partial_0of2() { fn test_extract_policy_for_sh_multi_partial_0of2() {
// let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let secp = Secp256k1::new();
// let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
// let desc = descriptor!(sh(multi 2, pubkey0, pubkey1)).unwrap(); let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
// let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let desc = descriptor!(sh(multi(2, pubkey0, pubkey1))).unwrap();
// let signers_container = Arc::new(SignersContainer::from(keymap)); let (wallet_desc, keymap) = desc
// let policy = wallet_desc .into_wallet_descriptor(&secp, Network::Testnet)
// .extract_policy(signers_container) .unwrap();
// .unwrap() let signers_container = Arc::new(SignersContainer::from(keymap));
// .unwrap(); let policy = wallet_desc
// .extract_policy(&signers_container, &secp)
// assert!( .unwrap()
// matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 .unwrap();
// && &keys[0].fingerprint.unwrap() == &fingerprint0
// && &keys[1].fingerprint.unwrap() == &fingerprint1) assert!(
// ); matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
// && &keys[0].fingerprint.unwrap() == &fingerprint0
// // TODO should this be "Satisfaction::None" since we have no prv keys? && &keys[1].fingerprint.unwrap() == &fingerprint1)
// // TODO should items and conditions not be empty? );
// assert!( // TODO should this be "Satisfaction::None" since we have no prv keys?
// matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions} if n == &2 // TODO should items and conditions not be empty?
// && m == &2 assert!(
// && items.is_empty() matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
// && conditions.is_empty() && m == &2usize
// ) && items.is_empty()
// ); && conditions.is_empty()
// } )
);
}
// 1 prv and 1 pub key descriptor, required 2 prv keys // 1 prv and 1 pub key descriptor, required 2 prv keys
// #[test] #[test]
// fn test_extract_policy_for_sh_multi_partial_1of2() { fn test_extract_policy_for_sh_multi_partial_1of2() {
// let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let secp = Secp256k1::new();
// let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
// let desc = descriptor!(sh(multi 2, prvkey0, pubkey1)).unwrap(); let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
// let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let desc = descriptor!(sh(multi(2, prvkey0, pubkey1))).unwrap();
// let signers_container = Arc::new(SignersContainer::from(keymap)); let (wallet_desc, keymap) = desc
// let policy = wallet_desc .into_wallet_descriptor(&secp, Network::Testnet)
// .extract_policy(signers_container) .unwrap();
// .unwrap() let signers_container = Arc::new(SignersContainer::from(keymap));
// .unwrap(); let policy = wallet_desc
// .extract_policy(&signers_container, &secp)
// assert!( .unwrap()
// matches!(&policy.item, Multisig { keys, threshold } if threshold == &2 .unwrap();
// && &keys[0].fingerprint.unwrap() == &fingerprint0 assert!(
// && &keys[1].fingerprint.unwrap() == &fingerprint1) matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
// ); && &keys[0].fingerprint.unwrap() == &fingerprint0
// && &keys[1].fingerprint.unwrap() == &fingerprint1)
// // TODO should this be "Satisfaction::Partial" since we have only one of two prv keys? );
// assert!(
// matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions} if n == &2 assert!(
// && m == &2 matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
// && items.len() == 2 && m == &2usize
// && conditions.contains_key(&vec![0,1]) && items.len() == 1
// ) && conditions.contains_key(&0)
// ); )
// } );
}
// 1 prv and 1 pub key descriptor, required 1 prv keys // 1 prv and 1 pub key descriptor, required 1 prv keys
#[test] #[test]
@@ -1012,15 +1073,15 @@ mod test {
fn test_extract_policy_for_sh_multi_complete_1of2() { fn test_extract_policy_for_sh_multi_complete_1of2() {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap(); let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet) .into_wallet_descriptor(&secp, Network::Testnet)
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1044,15 +1105,15 @@ mod test {
fn test_extract_policy_for_sh_multi_complete_2of2() { fn test_extract_policy_for_sh_multi_complete_2of2() {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap(); let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet) .into_wallet_descriptor(&secp, Network::Testnet)
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1077,7 +1138,7 @@ mod test {
fn test_extract_policy_for_single_wpkh() { fn test_extract_policy_for_single_wpkh() {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR); let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
let desc = descriptor!(wpkh(pubkey)).unwrap(); let desc = descriptor!(wpkh(pubkey)).unwrap();
let (wallet_desc, keymap) = desc let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet) .into_wallet_descriptor(&secp, Network::Testnet)
@@ -1085,7 +1146,7 @@ mod test {
let single_key = wallet_desc.derive(0); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1101,7 +1162,7 @@ mod test {
let single_key = wallet_desc.derive(0); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1119,8 +1180,8 @@ mod test {
fn test_extract_policy_for_single_wsh_multi_complete_1of2() { fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR); let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR); let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap(); let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet) .into_wallet_descriptor(&secp, Network::Testnet)
@@ -1128,7 +1189,7 @@ mod test {
let single_key = wallet_desc.derive(0); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1154,8 +1215,8 @@ mod test {
fn test_extract_policy_for_wsh_multi_timelock() { fn test_extract_policy_for_wsh_multi_timelock() {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR); let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
let sequence = 50; let sequence = 50;
#[rustfmt::skip] #[rustfmt::skip]
let desc = descriptor!(wsh(thresh( let desc = descriptor!(wsh(thresh(
@@ -1171,7 +1232,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new()) .extract_policy(&signers_container, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1192,66 +1253,82 @@ mod test {
// - mixed timelocks should fail // - mixed timelocks should fail
// #[test] #[test]
// fn test_extract_policy_for_wsh_mixed_timelocks() { #[ignore]
// let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); fn test_extract_policy_for_wsh_mixed_timelocks() {
// let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds let secp = Secp256k1::new();
// let locktime_blocks = 100; let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
// let locktime_seconds = locktime_blocks + locktime_threshold; let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds
// let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_seconds), (after locktime_blocks)))).unwrap(); let locktime_blocks = 100;
// let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap(); let locktime_seconds = locktime_blocks + locktime_threshold;
// let signers_container = Arc::new(SignersContainer::from(keymap)); let desc = descriptor!(sh(and_v(
// let policy = wallet_desc v: pk(prvkey0),
// .extract_policy(signers_container) and_v(v: after(locktime_seconds), after(locktime_blocks))
// .unwrap() )))
// .unwrap(); .unwrap();
// let (wallet_desc, keymap) = desc
// println!("desc policy = {:?}", policy); // TODO remove .into_wallet_descriptor(&secp, Network::Testnet)
// .unwrap();
// // TODO how should this fail with mixed timelocks? let signers_container = Arc::new(SignersContainer::from(keymap));
// } let policy = wallet_desc
.extract_policy(&signers_container, &secp)
.unwrap()
.unwrap();
println!("desc policy = {:?}", policy); // TODO remove
// TODO how should this fail with mixed timelocks?
}
// - multiple timelocks of the same type should be correctly merged together // - multiple timelocks of the same type should be correctly merged together
#[test]
#[ignore]
fn test_extract_policy_for_multiple_same_timelocks() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let locktime_blocks0 = 100;
let locktime_blocks1 = 200;
let desc = descriptor!(sh(and_v(
v: pk(prvkey0),
and_v(v: after(locktime_blocks0), after(locktime_blocks1))
)))
.unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc
.extract_policy(&signers_container, &secp)
.unwrap()
.unwrap();
println!("desc policy = {:?}", policy); // TODO remove
// TODO how should this merge timelocks?
let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR, PATH, &secp);
let locktime_seconds0 = 500000100;
let locktime_seconds1 = 500000200;
let desc = descriptor!(sh(and_v(
v: pk(prvkey1),
and_v(v: after(locktime_seconds0), after(locktime_seconds1))
)))
.unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc
.extract_policy(&signers_container, &secp)
.unwrap()
.unwrap();
// #[test] println!("desc policy = {:?}", policy); // TODO remove
// fn test_extract_policy_for_multiple_same_timelocks() {
// let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR); // TODO how should this merge timelocks?
// let locktime_blocks0 = 100; }
// let locktime_blocks1 = 200;
// let desc = descriptor!(sh (and_v (+v pk prvkey0), (and_v (+v after locktime_blocks0), (after locktime_blocks1)))).unwrap();
// let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
// let signers_container = Arc::new(SignersContainer::from(keymap));
// let policy = wallet_desc
// .extract_policy(signers_container)
// .unwrap()
// .unwrap();
//
// println!("desc policy = {:?}", policy); // TODO remove
//
// // TODO how should this merge timelocks?
//
// let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR);
// let locktime_seconds0 = 500000100;
// let locktime_seconds1 = 500000200;
// let desc = descriptor!(sh (and_v (+v pk prvkey1), (and_v (+v after locktime_seconds0), (after locktime_seconds1)))).unwrap();
// let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
// let signers_container = Arc::new(SignersContainer::from(keymap));
// let policy = wallet_desc
// .extract_policy(signers_container)
// .unwrap()
// .unwrap();
//
// println!("desc policy = {:?}", policy); // TODO remove
//
// // TODO how should this merge timelocks?
// }
#[test] #[test]
fn test_get_condition_multisig() { fn test_get_condition_multisig() {
let secp = Secp256k1::gen_new(); let secp = Secp256k1::new();
let (_, pk0, _) = setup_keys(TPRV0_STR); let (_, pk0, _) = setup_keys(TPRV0_STR, PATH, &secp);
let (_, pk1, _) = setup_keys(TPRV1_STR); let (_, pk1, _) = setup_keys(TPRV1_STR, PATH, &secp);
let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap(); let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap();
let (wallet_desc, keymap) = desc let (wallet_desc, keymap) = desc
@@ -1291,4 +1368,77 @@ mod test {
policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect()); policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect());
assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5))); assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
} }
#[test]
fn test_extract_satisfaction() {
const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
const ALICE_BOB_PATH: &str = "m/0'";
const ALICE_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
const BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBAQVHUiEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZsshAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIUq4iBgL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiAwcLu4+AAAAgAAAAAAiBgN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmywzJEXwuAAAAgAAAAAAAAA==";
const ALICE_BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEI2wQARzBEAiAY9Iy41HlWFzUOnKgfoG7b7ijI1eeMEoFpZtXH3IKR1QIgWtw7QvZf9TLeCAwr0e5psEHd3gD/5ufvvNXroSTUq4EBSDBFAiEA+cw7TOTMJJbq8CeWlu+kbDt+iKsrvurjHVZYS+sLNhkCIHrAIs+HWyku1JoQ7Av3NXs7tKOoadNFFLbAjH1GeGp2AUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSrgAA";
let secp = Secp256k1::new();
let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
let desc = descriptor!(wsh(multi(2, prvkey_alice, prvkey_bob))).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let addr = wallet_desc
.as_derived(0, &secp)
.address(Network::Testnet)
.unwrap();
assert_eq!(
"tb1qg3cwv3xt50gdg875qvjjpfgaps86gtk4rz0ejvp6ttc5ldnlxuvqlcn0xk",
addr.to_string()
);
let signers_container = Arc::new(SignersContainer::from(keymap));
let original_policy = wallet_desc
.extract_policy(&signers_container, &secp)
.unwrap()
.unwrap();
let psbt: PSBT = deserialize(&base64::decode(ALICE_SIGNED_PSBT).unwrap()).unwrap();
let mut policy_clone = original_policy.clone();
policy_clone
.fill_satisfactions(&psbt, &wallet_desc, &secp)
.unwrap();
assert!(
matches!(&policy_clone.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0]
)
);
let mut policy_clone = original_policy.clone();
let psbt: PSBT = deserialize(&base64::decode(BOB_SIGNED_PSBT).unwrap()).unwrap();
policy_clone
.fill_satisfactions(&psbt, &wallet_desc, &secp)
.unwrap();
assert!(
matches!(&policy_clone.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![1]
)
);
let mut policy_clone = original_policy.clone();
let psbt: PSBT = deserialize(&base64::decode(ALICE_BOB_SIGNED_PSBT).unwrap()).unwrap();
policy_clone
.fill_satisfactions(&psbt, &wallet_desc, &secp)
.unwrap();
assert!(
matches!(&policy_clone.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0, 1]
)
);
}
} }

View File

@@ -74,26 +74,27 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet}; /// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2PKH; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::P2Pkh;
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// P2PKH(key), /// P2Pkh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default(), /// MemoryDatabase::default(),
/// )?; /// )?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet.get_new_address()?.to_string(), /// wallet.get_address(New)?.to_string(),
/// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT" /// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
/// ); /// );
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct P2PKH<K: IntoDescriptorKey<Legacy>>(pub K); pub struct P2Pkh<K: IntoDescriptorKey<Legacy>>(pub K);
impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> { impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
descriptor!(pkh(self.0)) descriptor!(pkh(self.0))
} }
@@ -107,27 +108,28 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet}; /// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2WPKH_P2SH; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::P2Wpkh_P2Sh;
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// P2WPKH_P2SH(key), /// P2Wpkh_P2Sh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default(), /// MemoryDatabase::default(),
/// )?; /// )?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet.get_new_address()?.to_string(), /// wallet.get_address(New)?.to_string(),
/// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5" /// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
/// ); /// );
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct P2WPKH_P2SH<K: IntoDescriptorKey<Segwitv0>>(pub K); pub struct P2Wpkh_P2Sh<K: IntoDescriptorKey<Segwitv0>>(pub K);
impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> { impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
descriptor!(sh(wpkh(self.0))) descriptor!(sh(wpkh(self.0)))
} }
@@ -141,26 +143,27 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet}; /// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2WPKH; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::P2Wpkh;
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// P2WPKH(key), /// P2Wpkh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default(), /// MemoryDatabase::default(),
/// )?; /// )?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet.get_new_address()?.to_string(), /// wallet.get_address(New)?.to_string(),
/// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd" /// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
/// ); /// );
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct P2WPKH<K: IntoDescriptorKey<Segwitv0>>(pub K); pub struct P2Wpkh<K: IntoDescriptorKey<Segwitv0>>(pub K);
impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> { impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
descriptor!(wpkh(self.0)) descriptor!(wpkh(self.0))
} }
@@ -170,7 +173,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
/// ///
/// 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`).
/// ///
/// See [`BIP44Public`] for a template that can work with a `xpub`/`tpub`. /// See [`Bip44Public`] for a template that can work with a `xpub`/`tpub`.
/// ///
/// ## Example /// ## Example
/// ///
@@ -179,25 +182,26 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP44; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP44(key.clone(), KeychainKind::External), /// Bip44(key.clone(), KeychainKind::External),
/// Some(BIP44(key, KeychainKind::Internal)), /// Some(Bip44(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.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'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind); pub struct Bip44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> { impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
P2PKH(legacy::make_bipxx_private(44, self.0, self.1)?).build() P2Pkh(legacy::make_bipxx_private(44, self.0, self.1)?).build()
} }
} }
@@ -207,7 +211,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
/// ///
/// 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.
/// ///
/// See [`BIP44`] for a template that does the full derivation, but requires private data /// See [`Bip44`] for a template that does the full derivation, but requires private data
/// for the key. /// for the key.
/// ///
/// ## Example /// ## Example
@@ -217,26 +221,27 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP44Public; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP44Public(key.clone(), fingerprint, KeychainKind::External), /// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP44Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.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'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # 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) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
P2PKH(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build() P2Pkh(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build()
} }
} }
@@ -244,7 +249,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
/// ///
/// 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`).
/// ///
/// See [`BIP49Public`] for a template that can work with a `xpub`/`tpub`. /// See [`Bip49Public`] for a template that can work with a `xpub`/`tpub`.
/// ///
/// ## Example /// ## Example
/// ///
@@ -253,25 +258,26 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP49; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP49(key.clone(), KeychainKind::External), /// Bip49(key.clone(), KeychainKind::External),
/// Some(BIP49(key, KeychainKind::Internal)), /// Some(Bip49(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.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\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind); pub struct Bip49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> { impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
P2WPKH_P2SH(segwit_v0::make_bipxx_private(49, self.0, self.1)?).build() P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1)?).build()
} }
} }
@@ -281,7 +287,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
/// ///
/// 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.
/// ///
/// See [`BIP49`] for a template that does the full derivation, but requires private data /// See [`Bip49`] for a template that does the full derivation, but requires private data
/// for the key. /// for the key.
/// ///
/// ## Example /// ## Example
@@ -291,26 +297,27 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP49Public; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP49Public(key.clone(), fingerprint, KeychainKind::External), /// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP49Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.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\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # 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) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
P2WPKH_P2SH(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build() P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build()
} }
} }
@@ -318,7 +325,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
/// ///
/// 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`).
/// ///
/// See [`BIP84Public`] for a template that can work with a `xpub`/`tpub`. /// See [`Bip84Public`] for a template that can work with a `xpub`/`tpub`.
/// ///
/// ## Example /// ## Example
/// ///
@@ -327,25 +334,26 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP84; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP84(key.clone(), KeychainKind::External), /// Bip84(key.clone(), KeychainKind::External),
/// Some(BIP84(key, KeychainKind::Internal)), /// Some(Bip84(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.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\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind); pub struct Bip84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> { impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
P2WPKH(segwit_v0::make_bipxx_private(84, self.0, self.1)?).build() P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1)?).build()
} }
} }
@@ -355,7 +363,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
/// ///
/// 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.
/// ///
/// See [`BIP84`] for a template that does the full derivation, but requires private data /// See [`Bip84`] for a template that does the full derivation, but requires private data
/// for the key. /// for the key.
/// ///
/// ## Example /// ## Example
@@ -365,26 +373,27 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP84Public; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP84Public(key.clone(), fingerprint, KeychainKind::External), /// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP84Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.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\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # 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) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
P2WPKH(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build() P2Wpkh(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build()
} }
} }
@@ -487,7 +496,7 @@ mod test {
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
.unwrap(); .unwrap();
check( check(
P2PKH(prvkey).build(), P2Pkh(prvkey).build(),
false, false,
true, true,
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
@@ -498,7 +507,7 @@ mod test {
) )
.unwrap(); .unwrap();
check( check(
P2PKH(pubkey).build(), P2Pkh(pubkey).build(),
false, false,
true, true,
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
@@ -512,7 +521,7 @@ mod test {
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
.unwrap(); .unwrap();
check( check(
P2WPKH_P2SH(prvkey).build(), P2Wpkh_P2Sh(prvkey).build(),
true, true,
true, true,
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
@@ -523,7 +532,7 @@ mod test {
) )
.unwrap(); .unwrap();
check( check(
P2WPKH_P2SH(pubkey).build(), P2Wpkh_P2Sh(pubkey).build(),
true, true,
true, true,
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
@@ -537,7 +546,7 @@ mod test {
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
.unwrap(); .unwrap();
check( check(
P2WPKH(prvkey).build(), P2Wpkh(prvkey).build(),
true, true,
true, true,
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
@@ -548,7 +557,7 @@ mod test {
) )
.unwrap(); .unwrap();
check( check(
P2WPKH(pubkey).build(), P2Wpkh(pubkey).build(),
true, true,
true, true,
&["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
@@ -560,7 +569,7 @@ mod test {
fn test_bip44_template() { fn test_bip44_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
BIP44(prvkey, KeychainKind::External).build(), Bip44(prvkey, KeychainKind::External).build(),
false, false,
false, false,
&[ &[
@@ -570,7 +579,7 @@ mod test {
], ],
); );
check( check(
BIP44(prvkey, KeychainKind::Internal).build(), Bip44(prvkey, KeychainKind::Internal).build(),
false, false,
false, false,
&[ &[
@@ -587,7 +596,7 @@ mod test {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
BIP44Public(pubkey, fingerprint, KeychainKind::External).build(), Bip44Public(pubkey, fingerprint, KeychainKind::External).build(),
false, false,
false, false,
&[ &[
@@ -597,7 +606,7 @@ mod test {
], ],
); );
check( check(
BIP44Public(pubkey, fingerprint, KeychainKind::Internal).build(), Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(),
false, false,
false, false,
&[ &[
@@ -613,7 +622,7 @@ mod test {
fn test_bip49_template() { fn test_bip49_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
BIP49(prvkey, KeychainKind::External).build(), Bip49(prvkey, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -623,7 +632,7 @@ mod test {
], ],
); );
check( check(
BIP49(prvkey, KeychainKind::Internal).build(), Bip49(prvkey, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[
@@ -640,7 +649,7 @@ mod test {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
BIP49Public(pubkey, fingerprint, KeychainKind::External).build(), Bip49Public(pubkey, fingerprint, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -650,7 +659,7 @@ mod test {
], ],
); );
check( check(
BIP49Public(pubkey, fingerprint, KeychainKind::Internal).build(), Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[
@@ -666,7 +675,7 @@ mod test {
fn test_bip84_template() { fn test_bip84_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
BIP84(prvkey, KeychainKind::External).build(), Bip84(prvkey, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -676,7 +685,7 @@ mod test {
], ],
); );
check( check(
BIP84(prvkey, KeychainKind::Internal).build(), Bip84(prvkey, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[
@@ -693,7 +702,7 @@ mod test {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
BIP84Public(pubkey, fingerprint, KeychainKind::External).build(), Bip84Public(pubkey, fingerprint, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -703,7 +712,7 @@ mod test {
], ],
); );
check( check(
BIP84Public(pubkey, fingerprint, KeychainKind::Internal).build(), Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[

View File

@@ -47,7 +47,7 @@ pub enum Error {
/// the desired outputs plus fee, if there is not such combination this error is thrown /// the desired outputs plus fee, if there is not such combination this error is thrown
BnBNoExactMatch, BnBNoExactMatch,
/// Happens when trying to spend an UTXO that is not in the internal database /// Happens when trying to spend an UTXO that is not in the internal database
UnknownUTXO, UnknownUtxo,
/// Thrown when a tx is not found in the internal database /// Thrown when a tx is not found in the internal database
TransactionNotFound, TransactionNotFound,
/// Happens when trying to bump a transaction that is already confirmed /// Happens when trying to bump a transaction that is already confirmed
@@ -97,15 +97,15 @@ pub enum Error {
/// Miniscript error /// Miniscript error
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
/// BIP32 error /// BIP32 error
BIP32(bitcoin::util::bip32::Error), Bip32(bitcoin::util::bip32::Error),
/// An ECDSA error /// An ECDSA 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),
/// Hex decoding error /// Hex decoding error
Hex(bitcoin::hashes::hex::Error), Hex(bitcoin::hashes::hex::Error),
/// Partially signed bitcoin transaction error /// Partially signed bitcoin transaction error
PSBT(bitcoin::util::psbt::Error), Psbt(bitcoin::util::psbt::Error),
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey), //KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
//MissingInputUTXO(usize), //MissingInputUTXO(usize),
@@ -158,7 +158,7 @@ impl From<crate::keys::KeyError> for Error {
fn from(key_error: crate::keys::KeyError) -> Error { fn from(key_error: crate::keys::KeyError) -> Error {
match key_error { match key_error {
crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner), crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner), crate::keys::KeyError::Bip32(inner) => Error::Bip32(inner),
crate::keys::KeyError::InvalidChecksum => Error::ChecksumMismatch, crate::keys::KeyError::InvalidChecksum => Error::ChecksumMismatch,
e => Error::Key(e), e => Error::Key(e),
} }
@@ -167,11 +167,11 @@ impl From<crate::keys::KeyError> for Error {
impl_error!(bitcoin::consensus::encode::Error, Encode); impl_error!(bitcoin::consensus::encode::Error, Encode);
impl_error!(miniscript::Error, Miniscript); impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::util::bip32::Error, BIP32); impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::secp256k1::Error, Secp256k1); impl_error!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, JSON); impl_error!(serde_json::Error, Json);
impl_error!(bitcoin::hashes::hex::Error, Hex); impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(bitcoin::util::psbt::Error, PSBT); impl_error!(bitcoin::util::psbt::Error, Psbt);
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
impl_error!(electrum_client::Error, Electrum); impl_error!(electrum_client::Error, Electrum);

View File

@@ -873,13 +873,13 @@ pub enum KeyError {
Message(String), Message(String),
/// BIP32 error /// BIP32 error
BIP32(bitcoin::util::bip32::Error), Bip32(bitcoin::util::bip32::Error),
/// Miniscript error /// Miniscript error
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
} }
impl_error!(miniscript::Error, Miniscript, KeyError); impl_error!(miniscript::Error, Miniscript, KeyError);
impl_error!(bitcoin::util::bip32::Error, BIP32, KeyError); 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 {

View File

@@ -43,7 +43,7 @@
//! interact with the bitcoin P2P network. //! interact with the bitcoin P2P network.
//! //!
//! ```toml //! ```toml
//! bdk = "0.5.0" //! bdk = "0.6.0"
//! ``` //! ```
//! //!
//! ## Sync the balance of a descriptor //! ## Sync the balance of a descriptor
@@ -80,18 +80,19 @@
//! ``` //! ```
//! use bdk::{Wallet}; //! use bdk::{Wallet};
//! use bdk::database::MemoryDatabase; //! use bdk::database::MemoryDatabase;
//! use bdk::wallet::AddressIndex::New;
//! //!
//! fn main() -> Result<(), bdk::Error> { //! fn main() -> Result<(), bdk::Error> {
//! let wallet = Wallet::new_offline( //! let wallet = Wallet::new_offline(
//! "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, //! bitcoin::Network::Testnet,
//! MemoryDatabase::default(), //! MemoryDatabase::default(),
//! )?; //! )?;
//! //!
//! println!("Address #0: {}", wallet.get_new_address()?); //! println!("Address #0: {}", wallet.get_address(New)?);
//! println!("Address #1: {}", wallet.get_new_address()?); //! println!("Address #1: {}", wallet.get_address(New)?);
//! println!("Address #2: {}", wallet.get_new_address()?); //! println!("Address #2: {}", wallet.get_address(New)?);
//! //!
//! Ok(()) //! Ok(())
//! } //! }
@@ -109,6 +110,7 @@
//! use bdk::electrum_client::Client; //! use bdk::electrum_client::Client;
//! //!
//! use bitcoin::consensus::serialize; //! use bitcoin::consensus::serialize;
//! use bdk::wallet::AddressIndex::New;
//! //!
//! fn main() -> Result<(), bdk::Error> { //! fn main() -> Result<(), bdk::Error> {
//! let client = Client::new("ssl://electrum.blockstream.info:60002")?; //! let client = Client::new("ssl://electrum.blockstream.info:60002")?;
@@ -122,7 +124,7 @@
//! //!
//! wallet.sync(noop_progress(), None)?; //! wallet.sync(noop_progress(), None)?;
//! //!
//! let send_to = wallet.get_new_address()?; //! let send_to = wallet.get_address(New)?;
//! let (psbt, details) = wallet.build_tx() //! let (psbt, details) = wallet.build_tx()
//! .add_recipient(send_to.script_pubkey(), 50_000) //! .add_recipient(send_to.script_pubkey(), 50_000)
//! .enable_rbf() //! .enable_rbf()
@@ -249,7 +251,7 @@ pub(crate) mod types;
pub mod wallet; pub mod wallet;
pub use descriptor::template; pub use descriptor::template;
pub use descriptor::HDKeyPaths; pub use descriptor::HdKeyPaths;
pub use error::Error; pub use error::Error;
pub use types::*; pub use types::*;
pub use wallet::address_validator; pub use wallet::address_validator;

View File

@@ -12,11 +12,11 @@
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::TxOut; use bitcoin::TxOut;
pub trait PSBTUtils { pub trait PsbtUtils {
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>; fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
} }
impl PSBTUtils for PSBT { impl PsbtUtils for PSBT {
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> { fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
let tx = &self.global.unsigned_tx; let tx = &self.global.unsigned_tx;

View File

@@ -20,7 +20,7 @@
//! An address validator can be attached to a [`Wallet`](super::Wallet) by using the //! An address validator can be attached to a [`Wallet`](super::Wallet) by using the
//! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and //! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and
//! whenever a new address is generated (either explicitly by the user with //! whenever a new address is generated (either explicitly by the user with
//! [`Wallet::get_new_address`](super::Wallet::get_new_address) or internally to create a change //! [`Wallet::get_address`](super::Wallet::get_address) or internally to create a change
//! address) all the attached validators will be polled, in sequence. All of them must complete //! address) all the attached validators will be polled, in sequence. All of them must complete
//! successfully to continue. //! successfully to continue.
//! //!
@@ -32,6 +32,7 @@
//! # use bdk::address_validator::*; //! # use bdk::address_validator::*;
//! # use bdk::database::*; //! # use bdk::database::*;
//! # use bdk::*; //! # use bdk::*;
//! # use bdk::wallet::AddressIndex::New;
//! #[derive(Debug)] //! #[derive(Debug)]
//! struct PrintAddressAndContinue; //! struct PrintAddressAndContinue;
//! //!
@@ -39,7 +40,7 @@
//! fn validate( //! fn validate(
//! &self, //! &self,
//! keychain: KeychainKind, //! keychain: KeychainKind,
//! hd_keypaths: &HDKeyPaths, //! hd_keypaths: &HdKeyPaths,
//! script: &Script //! script: &Script
//! ) -> Result<(), AddressValidatorError> { //! ) -> Result<(), AddressValidatorError> {
//! let address = Address::from_script(script, Network::Testnet) //! let address = Address::from_script(script, Network::Testnet)
@@ -57,7 +58,7 @@
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?; //! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue)); //! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
//! //!
//! let address = wallet.get_new_address()?; //! let address = wallet.get_address(New)?;
//! println!("Address: {}", address); //! println!("Address: {}", address);
//! # Ok::<(), bdk::Error>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```
@@ -66,7 +67,7 @@ use std::fmt;
use bitcoin::Script; use bitcoin::Script;
use crate::descriptor::HDKeyPaths; use crate::descriptor::HdKeyPaths;
use crate::types::KeychainKind; use crate::types::KeychainKind;
/// Errors that can be returned to fail the validation of an address /// Errors that can be returned to fail the validation of an address
@@ -104,7 +105,7 @@ pub trait AddressValidator: Send + Sync + fmt::Debug {
fn validate( fn validate(
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
script: &Script, script: &Script,
) -> Result<(), AddressValidatorError>; ) -> Result<(), AddressValidatorError>;
} }
@@ -115,6 +116,7 @@ mod test {
use super::*; use super::*;
use crate::wallet::test::{get_funded_wallet, get_test_wpkh}; use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
use crate::wallet::AddressIndex::New;
#[derive(Debug)] #[derive(Debug)]
struct TestValidator; struct TestValidator;
@@ -122,7 +124,7 @@ mod test {
fn validate( fn validate(
&self, &self,
_keychain: KeychainKind, _keychain: KeychainKind,
_hd_keypaths: &HDKeyPaths, _hd_keypaths: &HdKeyPaths,
_script: &bitcoin::Script, _script: &bitcoin::Script,
) -> Result<(), AddressValidatorError> { ) -> Result<(), AddressValidatorError> {
Err(AddressValidatorError::InvalidScript) Err(AddressValidatorError::InvalidScript)
@@ -135,7 +137,7 @@ mod test {
let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh());
wallet.add_address_validator(Arc::new(TestValidator)); wallet.add_address_validator(Arc::new(TestValidator));
wallet.get_new_address().unwrap(); wallet.get_address(New).unwrap();
} }
#[test] #[test]

View File

@@ -91,6 +91,7 @@ use rand::seq::SliceRandom;
use rand::thread_rng; use rand::thread_rng;
#[cfg(test)] #[cfg(test)]
use rand::{rngs::StdRng, SeedableRng}; use rand::{rngs::StdRng, SeedableRng};
use std::convert::TryInto;
/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not /// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
/// overridden /// overridden
@@ -303,32 +304,39 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
.collect(); .collect();
// Mapping every (UTXO, usize) to an output group. // Mapping every (UTXO, usize) to an output group.
// Filtering UTXOs with an effective_value < 0, as the fee paid for
// adding them is more than their value
let optional_utxos: Vec<OutputGroup> = optional_utxos let optional_utxos: Vec<OutputGroup> = optional_utxos
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.filter(|u| u.effective_value > 0)
.collect(); .collect();
let curr_value = required_utxos let curr_value = required_utxos
.iter() .iter()
.fold(0, |acc, x| acc + x.effective_value as u64); .fold(0, |acc, x| acc + x.effective_value);
let curr_available_value = optional_utxos let curr_available_value = optional_utxos
.iter() .iter()
.fold(0, |acc, x| acc + x.effective_value as u64); .fold(0, |acc, x| acc + x.effective_value);
let actual_target = fee_amount.ceil() as u64 + amount_needed; let actual_target = fee_amount.ceil() as u64 + amount_needed;
let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb();
if curr_available_value + curr_value < actual_target { let expected = (curr_available_value + curr_value)
.try_into()
.map_err(|_| {
Error::Generic("Sum of UTXO spendable values does not fit into u64".to_string())
})?;
if expected < actual_target {
return Err(Error::InsufficientFunds { return Err(Error::InsufficientFunds {
needed: actual_target, needed: actual_target,
available: curr_available_value + curr_value, available: expected,
}); });
} }
let actual_target = actual_target
.try_into()
.expect("Bitcoin amount to fit into i64");
Ok(self Ok(self
.bnb( .bnb(
required_utxos.clone(), required_utxos.clone(),
@@ -359,9 +367,9 @@ impl BranchAndBoundCoinSelection {
&self, &self,
required_utxos: Vec<OutputGroup>, required_utxos: Vec<OutputGroup>,
mut optional_utxos: Vec<OutputGroup>, mut optional_utxos: Vec<OutputGroup>,
mut curr_value: u64, mut curr_value: i64,
mut curr_available_value: u64, mut curr_available_value: i64,
actual_target: u64, actual_target: i64,
fee_amount: f32, fee_amount: f32,
cost_of_change: f32, cost_of_change: f32,
) -> Result<CoinSelectionResult, Error> { ) -> Result<CoinSelectionResult, Error> {
@@ -387,7 +395,7 @@ impl BranchAndBoundCoinSelection {
// or the selected value is out of range. // or the selected value is out of range.
// Go back and try other branch // Go back and try other branch
if curr_value + curr_available_value < actual_target if curr_value + curr_available_value < actual_target
|| curr_value > actual_target + cost_of_change as u64 || curr_value > actual_target + cost_of_change as i64
{ {
backtrack = true; backtrack = true;
} else if curr_value >= actual_target { } else if curr_value >= actual_target {
@@ -413,8 +421,7 @@ impl BranchAndBoundCoinSelection {
// Walk backwards to find the last included UTXO that still needs to have its omission branch traversed. // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
while let Some(false) = current_selection.last() { while let Some(false) = current_selection.last() {
current_selection.pop(); current_selection.pop();
curr_available_value += curr_available_value += optional_utxos[current_selection.len()].effective_value;
optional_utxos[current_selection.len()].effective_value as u64;
} }
if current_selection.last_mut().is_none() { if current_selection.last_mut().is_none() {
@@ -432,17 +439,17 @@ impl BranchAndBoundCoinSelection {
} }
let utxo = &optional_utxos[current_selection.len() - 1]; let utxo = &optional_utxos[current_selection.len() - 1];
curr_value -= utxo.effective_value as u64; curr_value -= utxo.effective_value;
} else { } else {
// Moving forwards, continuing down this branch // Moving forwards, continuing down this branch
let utxo = &optional_utxos[current_selection.len()]; let utxo = &optional_utxos[current_selection.len()];
// Remove this utxo from the curr_available_value utxo amount // Remove this utxo from the curr_available_value utxo amount
curr_available_value -= utxo.effective_value as u64; curr_available_value -= utxo.effective_value;
// Inclusion branch first (Largest First Exploration) // Inclusion branch first (Largest First Exploration)
current_selection.push(true); current_selection.push(true);
curr_value += utxo.effective_value as u64; curr_value += utxo.effective_value;
} }
} }
@@ -469,8 +476,8 @@ impl BranchAndBoundCoinSelection {
&self, &self,
required_utxos: Vec<OutputGroup>, required_utxos: Vec<OutputGroup>,
mut optional_utxos: Vec<OutputGroup>, mut optional_utxos: Vec<OutputGroup>,
curr_value: u64, curr_value: i64,
actual_target: u64, actual_target: i64,
fee_amount: f32, fee_amount: f32,
) -> CoinSelectionResult { ) -> CoinSelectionResult {
#[cfg(not(test))] #[cfg(not(test))]
@@ -488,7 +495,7 @@ impl BranchAndBoundCoinSelection {
if *curr_value >= actual_target { if *curr_value >= actual_target {
None None
} else { } else {
*curr_value += utxo.effective_value as u64; *curr_value += utxo.effective_value;
Some(utxo) Some(utxo)
} }
}) })
@@ -532,13 +539,15 @@ mod test {
const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2; const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2;
const FEE_AMOUNT: f32 = 50.0;
fn get_test_utxos() -> Vec<WeightedUtxo> { fn get_test_utxos() -> Vec<WeightedUtxo> {
vec![ vec![
WeightedUtxo { WeightedUtxo {
satisfaction_weight: P2WPKH_WITNESS_SIZE, satisfaction_weight: P2WPKH_WITNESS_SIZE,
utxo: Utxo::Local(LocalUtxo { utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str( outpoint: OutPoint::from_str(
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", "0000000000000000000000000000000000000000000000000000000000000000:0",
) )
.unwrap(), .unwrap(),
txout: TxOut { txout: TxOut {
@@ -552,7 +561,21 @@ mod test {
satisfaction_weight: P2WPKH_WITNESS_SIZE, satisfaction_weight: P2WPKH_WITNESS_SIZE,
utxo: Utxo::Local(LocalUtxo { utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str( outpoint: OutPoint::from_str(
"65d92ddff6b6dc72c89624a6491997714b90f6004f928d875bc0fd53f264fa85:0", "0000000000000000000000000000000000000000000000000000000000000001:0",
)
.unwrap(),
txout: TxOut {
value: FEE_AMOUNT as u64 - 40,
script_pubkey: Script::new(),
},
keychain: KeychainKind::External,
}),
},
WeightedUtxo {
satisfaction_weight: P2WPKH_WITNESS_SIZE,
utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str(
"0000000000000000000000000000000000000000000000000000000000000002:0",
) )
.unwrap(), .unwrap(),
txout: TxOut { txout: TxOut {
@@ -629,9 +652,9 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 2); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_000); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 186.0); assert_eq!(result.fee_amount, 254.0);
} }
#[test] #[test]
@@ -650,9 +673,9 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 2); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_000); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 186.0); assert_eq!(result.fee_amount, 254.0);
} }
#[test] #[test]
@@ -748,13 +771,34 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
20_000, 20_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 2); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_000); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 186.0); assert_eq!(result.fee_amount, 254.0);
}
#[test]
fn test_bnb_coin_selection_optional_are_enough() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let result = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
vec![],
utxos,
FeeRate::from_sat_per_vb(1.0),
299756,
FEE_AMOUNT,
)
.unwrap();
assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300010);
assert_eq!(result.fee_amount, 254.0);
} }
#[test] #[test]
@@ -848,9 +892,7 @@ mod test {
.map(|u| OutputGroup::new(u, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
let curr_available_value = utxos let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
.iter()
.fold(0, |acc, x| acc + x.effective_value as u64);
let size_of_change = 31; let size_of_change = 31;
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
@@ -876,9 +918,7 @@ mod test {
.map(|u| OutputGroup::new(u, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
let curr_available_value = utxos let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
.iter()
.fold(0, |acc, x| acc + x.effective_value as u64);
let size_of_change = 31; let size_of_change = 31;
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
@@ -911,13 +951,11 @@ mod test {
let curr_value = 0; let curr_value = 0;
let curr_available_value = utxos let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
.iter()
.fold(0, |acc, x| acc + x.effective_value as u64);
// 2*(value of 1 utxo) - 2*(1 utxo fees with 1.0sat/vbyte fee rate) - // 2*(value of 1 utxo) - 2*(1 utxo fees with 1.0sat/vbyte fee rate) -
// cost_of_change + 5. // cost_of_change + 5.
let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as u64 + 5; let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as i64 + 5;
let result = BranchAndBoundCoinSelection::new(size_of_change) let result = BranchAndBoundCoinSelection::new(size_of_change)
.bnb( .bnb(
@@ -951,10 +989,10 @@ mod test {
let curr_available_value = optional_utxos let curr_available_value = optional_utxos
.iter() .iter()
.fold(0, |acc, x| acc + x.effective_value as u64); .fold(0, |acc, x| acc + x.effective_value);
let target_amount = optional_utxos[3].effective_value as u64 let target_amount =
+ optional_utxos[23].effective_value as u64; optional_utxos[3].effective_value + optional_utxos[23].effective_value;
let result = BranchAndBoundCoinSelection::new(0) let result = BranchAndBoundCoinSelection::new(0)
.bnb( .bnb(
@@ -967,7 +1005,7 @@ mod test {
0.0, 0.0,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected_amount(), target_amount); assert_eq!(result.selected_amount(), target_amount as u64);
} }
} }
@@ -988,7 +1026,7 @@ mod test {
vec![], vec![],
utxos, utxos,
0, 0,
target_amount, target_amount as i64,
50.0, 50.0,
); );

View File

@@ -24,8 +24,9 @@ use bitcoin::secp256k1::Secp256k1;
use bitcoin::consensus::encode::serialize; use bitcoin::consensus::encode::serialize;
use bitcoin::util::base58; use bitcoin::util::base58;
use bitcoin::util::psbt::raw::Key as PSBTKey; use bitcoin::util::psbt::raw::Key as PSBTKey;
use bitcoin::util::psbt::Input;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{Address, Network, OutPoint, Script, Transaction, TxOut, Txid}; use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid};
use miniscript::descriptor::DescriptorTrait; use miniscript::descriptor::DescriptorTrait;
use miniscript::psbt::PsbtInputSatisfier; use miniscript::psbt::PsbtInputSatisfier;
@@ -58,7 +59,7 @@ use crate::descriptor::{
Policy, XKeyUtils, Policy, XKeyUtils,
}; };
use crate::error::Error; use crate::error::Error;
use crate::psbt::PSBTUtils; use crate::psbt::PsbtUtils;
use crate::types::*; use crate::types::*;
const CACHE_ADDR_BATCH_SIZE: u32 = 100; const CACHE_ADDR_BATCH_SIZE: u32 = 100;
@@ -67,7 +68,7 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100;
/// ///
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a /// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets /// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets
/// needs to operate, like [generating addresses](Wallet::get_new_address), [returning the balance](Wallet::get_balance), /// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance),
/// [creating transactions](Wallet::build_tx), etc. /// [creating transactions](Wallet::build_tx), etc.
/// ///
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided /// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
@@ -162,14 +163,89 @@ where
} }
} }
/// The address index selection strategy to use to derived an address from the wallet's external
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
#[derive(Debug)]
pub enum AddressIndex {
/// Return a new address after incrementing the current descriptor index.
New,
/// Return the address for the current descriptor index if it has not been used in a received
/// transaction. Otherwise return a new address as with [`AddressIndex::New`].
///
/// Use with caution, if the wallet has not yet detected an address has been used it could
/// return an already used address. This function is primarily meant for situations where the
/// caller is untrusted; for example when deriving donation addresses on-demand for a public
/// web page.
LastUnused,
/// Return the address for a specific descriptor index. Does not change the current descriptor
/// index used by `AddressIndex::New` and `AddressIndex::LastUsed`.
///
/// Use with caution, if an index is given that is less than the current descriptor index
/// then the returned address may have already been used.
Peek(u32),
/// Return the address for a specific descriptor index and reset the current descriptor index
/// used by `AddressIndex::New` and `AddressIndex::LastUsed` to this value.
///
/// Use with caution, if an index is given that is less than the current descriptor index
/// then the returned address and subsequent addresses returned by calls to `AddressIndex::New`
/// and `AddressIndex::LastUsed` may have already been used. Also if the index is reset to a
/// value earlier than the [`crate::blockchain::Blockchain`] stop_gap (default is 20) then a
/// larger stop_gap should be used to monitor for all possibly used addresses.
Reset(u32),
}
// offline actions, always available // offline actions, always available
impl<B, D> Wallet<B, D> impl<B, D> Wallet<B, D>
where where
D: BatchDatabase, D: BatchDatabase,
{ {
/// Return a newly generated address using the external descriptor // Return a newly derived address using the external descriptor
pub fn get_new_address(&self) -> Result<Address, Error> { fn get_new_address(&self) -> Result<Address, Error> {
let index = self.fetch_and_increment_index(KeychainKind::External)?; let incremented_index = self.fetch_and_increment_index(KeychainKind::External)?;
self.descriptor
.as_derived(incremented_index, &self.secp)
.address(self.network)
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
}
// Return the the last previously derived address if it has not been used in a received
// transaction. Otherwise return a new address using [`Wallet::get_new_address`].
fn get_unused_address(&self) -> Result<Address, Error> {
let current_index = self.fetch_index(KeychainKind::External)?;
let derived_key = self.descriptor.as_derived(current_index, &self.secp);
let script_pubkey = derived_key.script_pubkey();
let found_used = self
.list_transactions(true)?
.iter()
.flat_map(|tx_details| tx_details.transaction.as_ref())
.flat_map(|tx| tx.output.iter())
.any(|o| o.script_pubkey == script_pubkey);
if found_used {
self.get_new_address()
} else {
derived_key
.address(self.network)
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
}
}
// Return derived address for the external descriptor at a specific index
fn peek_address(&self, index: u32) -> Result<Address, Error> {
self.descriptor
.as_derived(index, &self.secp)
.address(self.network)
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
}
// Return derived address for the external descriptor at a specific index and reset current
// address index
fn reset_address(&self, index: u32) -> Result<Address, Error> {
self.set_index(KeychainKind::External, index)?;
self.descriptor self.descriptor
.as_derived(index, &self.secp) .as_derived(index, &self.secp)
@@ -177,6 +253,18 @@ where
.map_err(|_| Error::ScriptDoesntHaveAddressForm) .map_err(|_| Error::ScriptDoesntHaveAddressForm)
} }
/// Return a derived address using the external descriptor, see [`AddressIndex`] for
/// available address index selection strategies. If none of the keys in the descriptor are derivable
/// (ie. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
pub fn get_address(&self, address_index: AddressIndex) -> Result<Address, Error> {
match address_index {
AddressIndex::New => self.get_new_address(),
AddressIndex::LastUnused => self.get_unused_address(),
AddressIndex::Peek(index) => self.peek_address(index),
AddressIndex::Reset(index) => self.reset_address(index),
}
}
/// Return whether or not a `script` is part of this wallet (either internal or external) /// Return whether or not a `script` is part of this wallet (either internal or external)
pub fn is_mine(&self, script: &Script) -> Result<bool, Error> { pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
self.database.borrow().is_mine(script) self.database.borrow().is_mine(script)
@@ -377,13 +465,13 @@ where
(None, Some(csv)) => csv, (None, Some(csv)) => csv,
// RBF with a specific value but that value is too high // RBF with a specific value but that value is too high
(Some(tx_builder::RBFValue::Value(rbf)), _) if rbf >= 0xFFFFFFFE => { (Some(tx_builder::RbfValue::Value(rbf)), _) if rbf >= 0xFFFFFFFE => {
return Err(Error::Generic( return Err(Error::Generic(
"Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into(), "Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into(),
)) ))
} }
// RBF with a specific value requested, but the value is incompatible with CSV // RBF with a specific value requested, but the value is incompatible with CSV
(Some(tx_builder::RBFValue::Value(rbf)), Some(csv)) (Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
if !check_nsequence_rbf(rbf, csv) => if !check_nsequence_rbf(rbf, csv) =>
{ {
return Err(Error::Generic(format!( return Err(Error::Generic(format!(
@@ -393,7 +481,7 @@ where
} }
// RBF enabled with the default value with CSV also enabled. CSV takes precedence // RBF enabled with the default value with CSV also enabled. CSV takes precedence
(Some(tx_builder::RBFValue::Default), Some(csv)) => csv, (Some(tx_builder::RbfValue::Default), Some(csv)) => csv,
// Valid RBF, either default or with a specific value. We ignore the `CSV` value // Valid RBF, either default or with a specific value. We ignore the `CSV` value
// because we've already checked it before // because we've already checked it before
(Some(rbf), _) => rbf.get_value(), (Some(rbf), _) => rbf.get_value(),
@@ -662,7 +750,7 @@ where
.database .database
.borrow() .borrow()
.get_previous_output(&txin.previous_output)? .get_previous_output(&txin.previous_output)?
.ok_or(Error::UnknownUTXO)?; .ok_or(Error::UnknownUtxo)?;
let (weight, keychain) = match self let (weight, keychain) = match self
.database .database
@@ -967,6 +1055,25 @@ where
Ok(index) Ok(index)
} }
fn fetch_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
let index = match descriptor.is_deriveable() {
false => Some(0),
true => self.database.borrow_mut().get_last_index(keychain)?,
};
if let Some(i) = index {
Ok(i)
} else {
self.fetch_and_increment_index(keychain)
}
}
fn set_index(&self, keychain: KeychainKind, index: u32) -> Result<(), Error> {
self.database.borrow_mut().set_last_index(keychain, index)?;
Ok(())
}
fn cache_addresses( fn cache_addresses(
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
@@ -1144,41 +1251,21 @@ where
None => continue, None => continue,
}; };
// Only set it if the params has a custom one, otherwise leave blank which defaults to
// SIGHASH_ALL
if let Some(sighash_type) = params.sighash {
psbt_input.sighash_type = Some(sighash_type);
}
match utxo { match utxo {
Utxo::Local(utxo) => { Utxo::Local(utxo) => {
// Try to find the prev_script in our db to figure out if this is internal or external, *psbt_input = match self.get_psbt_input(
// and the derivation index utxo,
let (keychain, child) = match self params.sighash,
.database params.force_non_witness_utxo,
.borrow() ) {
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)? Ok(psbt_input) => psbt_input,
{ Err(e) => match e {
Some(x) => x, Error::UnknownUtxo => Input {
None => continue, sighash_type: params.sighash,
}; ..Input::default()
},
let desc = self.get_descriptor_for_keychain(keychain); _ => return Err(e),
let derived_descriptor = desc.as_derived(child, &self.secp); },
psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
psbt_input.witness_script = derived_descriptor.psbt_witness_script();
let prev_output = input.previous_output;
if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? {
if desc.is_witness() {
psbt_input.witness_utxo =
Some(prev_tx.output[prev_output.vout as usize].clone());
}
if !desc.is_witness() || params.force_non_witness_utxo {
psbt_input.non_witness_utxo = Some(prev_tx);
}
} }
} }
Utxo::Foreign { Utxo::Foreign {
@@ -1226,6 +1313,45 @@ where
Ok(psbt) Ok(psbt)
} }
/// get the corresponding PSBT Input for a LocalUtxo
pub fn get_psbt_input(
&self,
utxo: LocalUtxo,
sighash_type: Option<SigHashType>,
force_non_witness_utxo: bool,
) -> Result<Input, Error> {
// Try to find the prev_script in our db to figure out if this is internal or external,
// and the derivation index
let (keychain, child) = self
.database
.borrow()
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
.ok_or(Error::UnknownUtxo)?;
let mut psbt_input = Input {
sighash_type,
..Input::default()
};
let desc = self.get_descriptor_for_keychain(keychain);
let derived_descriptor = desc.as_derived(child, &self.secp);
psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
psbt_input.witness_script = derived_descriptor.psbt_witness_script();
let prev_output = utxo.outpoint;
if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? {
if desc.is_witness() {
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
}
if !desc.is_witness() || force_non_witness_utxo {
psbt_input.non_witness_utxo = Some(prev_tx);
}
}
Ok(psbt_input)
}
fn add_input_hd_keypaths(&self, psbt: &mut PSBT) -> Result<(), Error> { fn add_input_hd_keypaths(&self, psbt: &mut PSBT) -> Result<(), Error> {
let mut input_utxos = Vec::with_capacity(psbt.inputs.len()); let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
for n in 0..psbt.inputs.len() { for n in 0..psbt.inputs.len() {
@@ -1370,6 +1496,7 @@ mod test {
use crate::types::KeychainKind; use crate::types::KeychainKind;
use super::*; use super::*;
use crate::wallet::AddressIndex::{LastUnused, New, Peek, Reset};
#[test] #[test]
fn test_cache_addresses_fixed() { fn test_cache_addresses_fixed() {
@@ -1383,11 +1510,11 @@ mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
wallet.get_new_address().unwrap().to_string(), wallet.get_address(New).unwrap().to_string(),
"tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835" "tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835"
); );
assert_eq!( assert_eq!(
wallet.get_new_address().unwrap().to_string(), wallet.get_address(New).unwrap().to_string(),
"tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835" "tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835"
); );
@@ -1411,11 +1538,11 @@ mod test {
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!( assert_eq!(
wallet.get_new_address().unwrap().to_string(), wallet.get_address(New).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
); );
assert_eq!( assert_eq!(
wallet.get_new_address().unwrap().to_string(), wallet.get_address(New).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
); );
@@ -1439,7 +1566,7 @@ mod test {
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap(); let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!( assert_eq!(
wallet.get_new_address().unwrap().to_string(), wallet.get_address(New).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
); );
assert!(wallet assert!(wallet
@@ -1450,7 +1577,7 @@ mod test {
.is_some()); .is_some());
for _ in 0..CACHE_ADDR_BATCH_SIZE { for _ in 0..CACHE_ADDR_BATCH_SIZE {
wallet.get_new_address().unwrap(); wallet.get_address(New).unwrap();
} }
assert!(wallet assert!(wallet
@@ -1496,13 +1623,30 @@ mod test {
) )
.unwrap(); .unwrap();
let txid = crate::populate_test_db!( let funding_address_kix = 0;
wallet.database.borrow_mut(),
testutils! { let tx_meta = testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) @tx ( (@external descriptors, funding_address_kix) => 50_000 ) (@confirmations 1)
}, };
Some(100)
); wallet
.database
.borrow_mut()
.set_script_pubkey(
&bitcoin::Address::from_str(&tx_meta.output.iter().next().unwrap().to_address)
.unwrap()
.script_pubkey(),
KeychainKind::External,
funding_address_kix,
)
.unwrap();
wallet
.database
.borrow_mut()
.set_last_index(KeychainKind::External, funding_address_kix)
.unwrap();
let txid = crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, Some(100));
(wallet, descriptors, txid) (wallet, descriptors, txid)
} }
@@ -1529,9 +1673,9 @@ mod test {
let fee_rate = $fee_rate.as_sat_vb(); let fee_rate = $fee_rate.as_sat_vb();
if !dust_change { if !dust_change {
assert!((tx_fee_rate - fee_rate).abs() < 0.5, format!("Expected fee rate of {}, the tx has {}", fee_rate, tx_fee_rate)); assert!((tx_fee_rate - fee_rate).abs() < 0.5, "Expected fee rate of {}, the tx has {}", fee_rate, tx_fee_rate);
} else { } else {
assert!(tx_fee_rate >= fee_rate, format!("Expected fee rate of at least {}, the tx has {}", fee_rate, tx_fee_rate)); assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {}, the tx has {}", fee_rate, tx_fee_rate);
} }
}); });
} }
@@ -1547,7 +1691,7 @@ mod test {
#[should_panic(expected = "NoUtxosSelected")] #[should_panic(expected = "NoUtxosSelected")]
fn test_create_tx_manually_selected_empty_utxos() { fn test_create_tx_manually_selected_empty_utxos() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1559,7 +1703,7 @@ mod test {
#[should_panic(expected = "Invalid version `0`")] #[should_panic(expected = "Invalid version `0`")]
fn test_create_tx_version_0() { fn test_create_tx_version_0() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1573,7 +1717,7 @@ mod test {
)] )]
fn test_create_tx_version_1_csv() { fn test_create_tx_version_1_csv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1584,7 +1728,7 @@ mod test {
#[test] #[test]
fn test_create_tx_custom_version() { fn test_create_tx_custom_version() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1597,7 +1741,7 @@ mod test {
#[test] #[test]
fn test_create_tx_default_locktime() { fn test_create_tx_default_locktime() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
@@ -1608,7 +1752,7 @@ mod test {
#[test] #[test]
fn test_create_tx_default_locktime_cltv() { fn test_create_tx_default_locktime_cltv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
@@ -1619,7 +1763,7 @@ mod test {
#[test] #[test]
fn test_create_tx_custom_locktime() { fn test_create_tx_custom_locktime() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1632,7 +1776,7 @@ mod test {
#[test] #[test]
fn test_create_tx_custom_locktime_compatible_with_cltv() { fn test_create_tx_custom_locktime_compatible_with_cltv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1648,7 +1792,7 @@ mod test {
)] )]
fn test_create_tx_custom_locktime_incompatible_with_cltv() { fn test_create_tx_custom_locktime_incompatible_with_cltv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1659,7 +1803,7 @@ mod test {
#[test] #[test]
fn test_create_tx_no_rbf_csv() { fn test_create_tx_no_rbf_csv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
@@ -1670,7 +1814,7 @@ mod test {
#[test] #[test]
fn test_create_tx_with_default_rbf_csv() { fn test_create_tx_with_default_rbf_csv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1687,7 +1831,7 @@ mod test {
)] )]
fn test_create_tx_with_custom_rbf_csv() { fn test_create_tx_with_custom_rbf_csv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1698,7 +1842,7 @@ mod test {
#[test] #[test]
fn test_create_tx_no_rbf_cltv() { fn test_create_tx_no_rbf_cltv() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv()); let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
@@ -1710,7 +1854,7 @@ mod test {
#[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")] #[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
fn test_create_tx_invalid_rbf_sequence() { fn test_create_tx_invalid_rbf_sequence() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1721,7 +1865,7 @@ mod test {
#[test] #[test]
fn test_create_tx_custom_rbf_sequence() { fn test_create_tx_custom_rbf_sequence() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1734,7 +1878,7 @@ mod test {
#[test] #[test]
fn test_create_tx_default_sequence() { fn test_create_tx_default_sequence() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
@@ -1748,7 +1892,7 @@ mod test {
)] )]
fn test_create_tx_change_policy_no_internal() { fn test_create_tx_change_policy_no_internal() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1759,7 +1903,7 @@ mod test {
#[test] #[test]
fn test_create_tx_single_recipient_drain_wallet() { fn test_create_tx_single_recipient_drain_wallet() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -1776,7 +1920,7 @@ mod test {
#[test] #[test]
fn test_create_tx_default_fee_rate() { fn test_create_tx_default_fee_rate() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, details) = builder.finish().unwrap(); let (psbt, details) = builder.finish().unwrap();
@@ -1787,7 +1931,7 @@ mod test {
#[test] #[test]
fn test_create_tx_custom_fee_rate() { fn test_create_tx_custom_fee_rate() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1800,7 +1944,7 @@ mod test {
#[test] #[test]
fn test_create_tx_absolute_fee() { fn test_create_tx_absolute_fee() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -1819,7 +1963,7 @@ mod test {
#[test] #[test]
fn test_create_tx_absolute_zero_fee() { fn test_create_tx_absolute_zero_fee() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -1839,7 +1983,7 @@ mod test {
#[should_panic(expected = "InsufficientFunds")] #[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_absolute_high_fee() { fn test_create_tx_absolute_high_fee() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -1853,7 +1997,7 @@ mod test {
use super::tx_builder::TxOrdering; use super::tx_builder::TxOrdering;
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -1871,7 +2015,7 @@ mod test {
#[test] #[test]
fn test_create_tx_skip_change_dust() { fn test_create_tx_skip_change_dust() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 49_800); builder.add_recipient(addr.script_pubkey(), 49_800);
let (psbt, details) = builder.finish().unwrap(); let (psbt, details) = builder.finish().unwrap();
@@ -1885,7 +2029,7 @@ mod test {
#[should_panic(expected = "InsufficientFunds")] #[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_single_recipient_dust_amount() { fn test_create_tx_single_recipient_dust_amount() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
// very high fee rate, so that the only output would be below dust // very high fee rate, so that the only output would be below dust
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@@ -1898,12 +2042,12 @@ mod test {
#[test] #[test]
fn test_create_tx_ordering_respected() { fn test_create_tx_ordering_respected() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 30_000) .add_recipient(addr.script_pubkey(), 30_000)
.add_recipient(addr.script_pubkey(), 10_000) .add_recipient(addr.script_pubkey(), 10_000)
.ordering(super::tx_builder::TxOrdering::BIP69Lexicographic); .ordering(super::tx_builder::TxOrdering::Bip69Lexicographic);
let (psbt, details) = builder.finish().unwrap(); let (psbt, details) = builder.finish().unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 3); assert_eq!(psbt.global.unsigned_tx.output.len(), 3);
@@ -1918,7 +2062,7 @@ mod test {
#[test] #[test]
fn test_create_tx_default_sighash() { fn test_create_tx_default_sighash() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 30_000); builder.add_recipient(addr.script_pubkey(), 30_000);
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
@@ -1929,7 +2073,7 @@ mod test {
#[test] #[test]
fn test_create_tx_custom_sighash() { fn test_create_tx_custom_sighash() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 30_000) .add_recipient(addr.script_pubkey(), 30_000)
@@ -1948,7 +2092,7 @@ mod test {
use std::str::FromStr; use std::str::FromStr;
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -1972,7 +2116,7 @@ mod test {
let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
// cache some addresses // cache some addresses
wallet.get_new_address().unwrap(); wallet.get_address(New).unwrap();
let addr = testutils!(@external descriptors, 5); let addr = testutils!(@external descriptors, 5);
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@@ -1997,7 +2141,7 @@ mod test {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -2022,7 +2166,7 @@ mod test {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -2047,7 +2191,7 @@ mod test {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))"); get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -2069,7 +2213,7 @@ mod test {
fn test_create_tx_non_witness_utxo() { fn test_create_tx_non_witness_utxo() {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -2084,7 +2228,7 @@ mod test {
fn test_create_tx_only_witness_utxo() { fn test_create_tx_only_witness_utxo() {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -2099,7 +2243,7 @@ mod test {
fn test_create_tx_shwpkh_has_witness_utxo() { fn test_create_tx_shwpkh_has_witness_utxo() {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -2114,7 +2258,7 @@ mod test {
fn test_create_tx_both_non_witness_utxo_and_witness_utxo() { fn test_create_tx_both_non_witness_utxo_and_witness_utxo() {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -2233,7 +2377,7 @@ mod test {
use bitcoin::util::psbt::raw::Key; use bitcoin::util::psbt::raw::Key;
let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -2433,13 +2577,23 @@ mod test {
} }
} }
#[test]
fn test_get_psbt_input() {
// this should grab a known good utxo and set the input
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
for utxo in wallet.list_unspent().unwrap() {
let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap();
assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some());
}
}
#[test] #[test]
#[should_panic( #[should_panic(
expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")" expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")"
)] )]
fn test_create_tx_global_xpubs_origin_missing() { fn test_create_tx_global_xpubs_origin_missing() {
let (wallet, _, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); let (wallet, _, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -2454,7 +2608,7 @@ mod test {
use bitcoin::util::psbt::raw::Key; use bitcoin::util::psbt::raw::Key;
let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)"); let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -2478,7 +2632,7 @@ mod test {
#[should_panic(expected = "IrreplaceableTransaction")] #[should_panic(expected = "IrreplaceableTransaction")]
fn test_bump_fee_irreplaceable_tx() { fn test_bump_fee_irreplaceable_tx() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, mut details) = builder.finish().unwrap(); let (psbt, mut details) = builder.finish().unwrap();
@@ -2496,7 +2650,7 @@ mod test {
#[should_panic(expected = "TransactionConfirmed")] #[should_panic(expected = "TransactionConfirmed")]
fn test_bump_fee_confirmed_tx() { fn test_bump_fee_confirmed_tx() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000); builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, mut details) = builder.finish().unwrap(); let (psbt, mut details) = builder.finish().unwrap();
@@ -2515,7 +2669,7 @@ mod test {
#[should_panic(expected = "FeeRateTooLow")] #[should_panic(expected = "FeeRateTooLow")]
fn test_bump_fee_low_fee_rate() { fn test_bump_fee_low_fee_rate() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -2537,7 +2691,7 @@ mod test {
#[should_panic(expected = "FeeTooLow")] #[should_panic(expected = "FeeTooLow")]
fn test_bump_fee_low_abs() { fn test_bump_fee_low_abs() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -2559,7 +2713,7 @@ mod test {
#[should_panic(expected = "FeeTooLow")] #[should_panic(expected = "FeeTooLow")]
fn test_bump_fee_zero_abs() { fn test_bump_fee_zero_abs() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 25_000) .add_recipient(addr.script_pubkey(), 25_000)
@@ -2605,7 +2759,7 @@ mod test {
.unwrap(); .unwrap();
let mut builder = wallet.build_fee_bump(txid).unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(2.5)); builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (psbt, details) = builder.finish().unwrap();
assert_eq!(details.sent, original_details.sent); assert_eq!(details.sent, original_details.sent);
@@ -2666,6 +2820,7 @@ mod test {
let mut builder = wallet.build_fee_bump(txid).unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_absolute(200); builder.fee_absolute(200);
builder.enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (psbt, details) = builder.finish().unwrap();
assert_eq!(details.sent, original_details.sent); assert_eq!(details.sent, original_details.sent);
@@ -3302,7 +3457,7 @@ mod test {
#[test] #[test]
fn test_sign_single_xprv() { fn test_sign_single_xprv() {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -3319,7 +3474,7 @@ mod test {
#[test] #[test]
fn test_sign_single_xprv_with_master_fingerprint_and_path() { fn test_sign_single_xprv_with_master_fingerprint_and_path() {
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -3336,7 +3491,7 @@ mod test {
#[test] #[test]
fn test_sign_single_xprv_bip44_path() { fn test_sign_single_xprv_bip44_path() {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)"); let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -3353,7 +3508,7 @@ mod test {
#[test] #[test]
fn test_sign_single_xprv_sh_wpkh() { fn test_sign_single_xprv_sh_wpkh() {
let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))"); let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -3371,7 +3526,7 @@ mod test {
fn test_sign_single_wif() { fn test_sign_single_wif() {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -3388,7 +3543,7 @@ mod test {
#[test] #[test]
fn test_sign_single_xprv_no_hd_keypaths() { fn test_sign_single_xprv_no_hd_keypaths() {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
@@ -3457,4 +3612,142 @@ mod test {
"should finalized input it signed" "should finalized input it signed"
) )
} }
#[test]
fn test_unused_address() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_address(LastUnused).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet.get_address(LastUnused).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
}
#[test]
fn test_next_unused_address() {
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let descriptors = testutils!(@descriptors (descriptor));
let wallet = Wallet::new_offline(
&descriptors.0,
None,
Network::Testnet,
MemoryDatabase::new(),
)
.unwrap();
assert_eq!(
wallet.get_address(LastUnused).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
// use the above address
crate::populate_test_db!(
wallet.database.borrow_mut(),
testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
Some(100),
);
assert_eq!(
wallet.get_address(LastUnused).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
}
#[test]
fn test_peek_address_at_index() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_address(Peek(1)).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
assert_eq!(
wallet.get_address(Peek(0)).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet.get_address(Peek(2)).unwrap().to_string(),
"tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
);
// current new address is not affected
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
}
#[test]
fn test_peek_address_at_index_not_derivable() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_address(Peek(1)).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
assert_eq!(
wallet.get_address(Peek(0)).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
assert_eq!(
wallet.get_address(Peek(2)).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
}
#[test]
fn test_reset_address_index() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
// new index 0
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
// new index 1
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
// new index 2
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
"tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
);
// reset index 1 again
assert_eq!(
wallet.get_address(Reset(1)).unwrap().to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
// new index 2 again
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
"tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
);
}
} }

View File

@@ -146,7 +146,7 @@ pub enum SignerError {
/// The `witness_script` field of the transaction is requied to sign this input /// The `witness_script` field of the transaction is requied to sign this input
MissingWitnessScript, MissingWitnessScript,
/// The fingerprint and derivation path are missing from the psbt input /// The fingerprint and derivation path are missing from the psbt input
MissingHDKeypath, MissingHdKeypath,
} }
impl fmt::Display for SignerError { impl fmt::Display for SignerError {

View File

@@ -143,7 +143,7 @@ pub(crate) struct TxParams {
pub(crate) sighash: Option<SigHashType>, pub(crate) sighash: Option<SigHashType>,
pub(crate) ordering: TxOrdering, pub(crate) ordering: TxOrdering,
pub(crate) locktime: Option<u32>, pub(crate) locktime: Option<u32>,
pub(crate) rbf: Option<RBFValue>, pub(crate) rbf: Option<RbfValue>,
pub(crate) version: Option<Version>, pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy, pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) force_non_witness_utxo: bool, pub(crate) force_non_witness_utxo: bool,
@@ -278,7 +278,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> { pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> {
let utxos = outpoints let utxos = outpoints
.iter() .iter()
.map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUTXO)) .map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUtxo))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
for utxo in utxos { for utxo in utxos {
@@ -523,6 +523,26 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
pub fn finish(self) -> Result<(PSBT, TransactionDetails), Error> { pub fn finish(self) -> Result<(PSBT, TransactionDetails), Error> {
self.wallet.create_tx(self.coin_selection, self.params) self.wallet.create_tx(self.coin_selection, self.params)
} }
/// Enable signaling RBF
///
/// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(&mut self) -> &mut Self {
self.params.rbf = Some(RbfValue::Default);
self
}
/// Enable signaling RBF with a specific nSequence value
///
/// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
/// and the given `nsequence` is lower than the CSV value.
///
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
self.params.rbf = Some(RbfValue::Value(nsequence));
self
}
} }
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> { impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> {
@@ -558,26 +578,6 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
self self
} }
/// Enable signaling RBF
///
/// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(&mut self) -> &mut Self {
self.params.rbf = Some(RBFValue::Default);
self
}
/// Enable signaling RBF with a specific nSequence value
///
/// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
/// and the given `nsequence` is lower than the CSV value.
///
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
self.params.rbf = Some(RBFValue::Value(nsequence));
self
}
} }
// methods supported only by bump_fee // methods supported only by bump_fee
@@ -612,7 +612,7 @@ pub enum TxOrdering {
/// Unchanged /// Unchanged
Untouched, Untouched,
/// BIP69 / Lexicographic /// BIP69 / Lexicographic
BIP69Lexicographic, Bip69Lexicographic,
} }
impl Default for TxOrdering { impl Default for TxOrdering {
@@ -638,7 +638,7 @@ impl TxOrdering {
tx.output.shuffle(&mut rng); tx.output.shuffle(&mut rng);
} }
TxOrdering::BIP69Lexicographic => { TxOrdering::Bip69Lexicographic => {
tx.input.sort_unstable_by_key(|txin| { tx.input.sort_unstable_by_key(|txin| {
(txin.previous_output.txid, txin.previous_output.vout) (txin.previous_output.txid, txin.previous_output.vout)
}); });
@@ -665,16 +665,16 @@ impl Default for Version {
/// ///
/// Has a default value of `0xFFFFFFFD` /// Has a default value of `0xFFFFFFFD`
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub(crate) enum RBFValue { pub(crate) enum RbfValue {
Default, Default,
Value(u32), Value(u32),
} }
impl RBFValue { impl RbfValue {
pub(crate) fn get_value(&self) -> u32 { pub(crate) fn get_value(&self) -> u32 {
match self { match self {
RBFValue::Default => 0xFFFFFFFD, RbfValue::Default => 0xFFFFFFFD,
RBFValue::Value(v) => *v, RbfValue::Value(v) => *v,
} }
} }
} }
@@ -759,7 +759,7 @@ mod test {
let original_tx = ordering_test_tx!(); let original_tx = ordering_test_tx!();
let mut tx = original_tx; let mut tx = original_tx;
TxOrdering::BIP69Lexicographic.sort_tx(&mut tx); TxOrdering::Bip69Lexicographic.sort_tx(&mut tx);
assert_eq!( assert_eq!(
tx.input[0].previous_output, tx.input[0].previous_output,

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk-testutils-macros" name = "bdk-testutils-macros"
version = "0.4.0" version = "0.5.0"
authors = ["Alekos Filini <alekos.filini@gmail.com>"] authors = ["Alekos Filini <alekos.filini@gmail.com>"]
edition = "2018" edition = "2018"
homepage = "https://bitcoindevkit.org" homepage = "https://bitcoindevkit.org"

View File

@@ -71,6 +71,7 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
use #root_ident::database::MemoryDatabase; use #root_ident::database::MemoryDatabase;
use #root_ident::types::KeychainKind; use #root_ident::types::KeychainKind;
use #root_ident::{Wallet, TxBuilder, FeeRate}; use #root_ident::{Wallet, TxBuilder, FeeRate};
use #root_ident::wallet::AddressIndex::New;
use super::*; use super::*;
@@ -532,7 +533,7 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
#[serial] #[serial]
fn test_sync_receive_coinbase() { fn test_sync_receive_coinbase() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, descriptors, mut test_client) = init_single_sig();
let wallet_addr = wallet.get_new_address().unwrap(); let wallet_addr = wallet.get_address(New).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0); assert_eq!(wallet.get_balance().unwrap(), 0);