Compare commits

...

25 Commits

Author SHA1 Message Date
Alekos Filini
1420cf8d0f Bump version to 0.3.1-dev 2021-01-20 10:58:04 -05:00
Alekos Filini
bddd418c8e Bump version to 0.3.0 2021-01-20 10:39:24 -05:00
Alekos Filini
49db898acb Update CHANGELOG.md in preparation of tag v0.3.0 2021-01-20 10:27:28 -05:00
Tobin Harding
01585227c5 Use contains combinator
As suggested by clippy, use the `contains` combinator instead of doing
manual range check on floats.
2021-01-18 11:39:37 -08:00
Justin Moon
52b45c5b89 [wallet] Add "needed" and "available" metadata to Error::InsufficientFunds 2021-01-18 11:15:10 +01:00
Alekos Filini
733355a6ae Bump version to 0.3.0-rc.1 2021-01-12 21:41:30 +01:00
Alekos Filini
6955a7776d Merge commit 'refs/pull/264/head' of github.com:bitcoindevkit/bdk 2021-01-12 14:02:41 +01:00
Alekos Filini
bf04a2cf69 descriptor: Use DescriptorError instead of Error when reasonable
Change the return type of the `descriptor!()` macro and `ToWalletDescriptor` to
avoid having to map errors.

Also introduce more checks to validate descriptors built using the macro.
2021-01-12 12:21:22 +01:00
Riccardo Casatta
2b669afd3e Permit to not set timeout in ElectrumBlockchainConfig
Allowing to use socks5 which requires None timeout
2021-01-11 14:06:56 +01:00
Steve Myers
8510b2b86e Fix crates.io license info 2021-01-09 10:41:48 -08:00
Alekos Filini
a95a9f754c Merge commit 'refs/pull/260/head' of github.com:bitcoindevkit/bdk 2021-01-05 16:06:32 +01:00
Alekos Filini
3980b90bff Merge commit 'refs/pull/248/head' of github.com:bitcoindevkit/bdk 2021-01-05 16:04:53 +01:00
Alekos Filini
b2bd1b5831 Merge commit 'refs/pull/257/head' of github.com:bitcoindevkit/bdk 2021-01-05 16:01:15 +01:00
Steve Myers
aa31c96821 [ci] Fail 'Build docs' job if warnings 2021-01-04 16:39:11 -08:00
Steve Myers
f74bfdd493 Remove 'cli.rs' module, 'cli-utils' feature and 'repl.rs' example 2020-12-31 09:44:30 -08:00
Steve Myers
5034ca2267 Fix clippy warnings for compact_filters feature 2020-12-30 19:23:35 -08:00
Steve Myers
8094263028 [ci] Fix clippy step to check matrix features 2020-12-30 19:23:00 -08:00
LLFourn
0c9c0716a4 [wallet] Fix details.fees being wrong when change is dust 2020-12-29 16:36:35 +11:00
Alekos Filini
c2b2da7601 Merge commit 'refs/pull/252/head' of github.com:bitcoindevkit/bdk 2020-12-23 18:39:05 +01:00
Alekos Filini
407f14add9 Merge commit 'refs/pull/250/head' of github.com:bitcoindevkit/bdk 2020-12-23 17:48:59 +01:00
LLFourn
656c9c9da8 Use () to indicate a missing blockchain
So that:
1. There are no runtime errors
2. There less type annotations needed
3. Less traits and stuff to document
2020-12-23 14:52:29 +11:00
LLFourn
a578d20282 Fix incredibly annoying cargo-fmt problem
I must have a newer version of cargo-fmt which stops me from making
commits every time because of this.
2020-12-22 14:37:53 +11:00
Steve Myers
2e222c7ad9 [docs] Add badges for crates.io, mit license. Fix docs.rs badge and link 2020-12-21 20:14:25 +01:00
Alekos Filini
7d6cd6d4f5 Fix the changelog after release v0.2.0 2020-12-21 20:14:23 +01:00
Alekos Filini
e31bd812ed Bump version to 0.2.1-dev 2020-12-21 14:51:49 +01:00
36 changed files with 404 additions and 1243 deletions

View File

@@ -18,7 +18,7 @@ jobs:
- name: Install tarpaulin
run: cargo install cargo-tarpaulin
- name: Tarpaulin
run: cargo tarpaulin --features all-keys,cli-utils,compiler,esplora,compact_filters --run-types Tests,Doctests --exclude-files "testutils/*" --out Xml
run: cargo tarpaulin --features all-keys,compiler,esplora,compact_filters --run-types Tests,Doctests --exclude-files "testutils/*" --out Xml
- name: Publish to codecov.io
uses: codecov/codecov-action@v1.0.15

View File

@@ -20,7 +20,7 @@ jobs:
- key-value-db
- electrum
- compact_filters
- cli-utils,esplora,key-value-db,electrum
- esplora,key-value-db,electrum
- compiler
steps:
- name: checkout
@@ -44,7 +44,7 @@ jobs:
- name: Build
run: cargo build --features ${{ matrix.features }} --no-default-features
- name: Clippy
run: cargo clippy -- -D warnings
run: cargo clippy --features ${{ matrix.features }} --no-default-features -- -D warnings
- name: Test
run: cargo test --features ${{ matrix.features }} --no-default-features
@@ -130,7 +130,7 @@ jobs:
- name: Add target wasm32
run: rustup target add wasm32-unknown-unknown
- name: Check
run: cargo check --target wasm32-unknown-unknown --features cli-utils,esplora --no-default-features
run: cargo check --target wasm32-unknown-unknown --features esplora --no-default-features
fmt:
name: Rust fmt

View File

@@ -27,7 +27,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: rustdoc
args: --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs
args: --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
- name: Upload artifact
uses: actions/upload-artifact@v2
with:

View File

@@ -6,6 +6,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [v0.3.0] - [v0.2.0]
### Descriptor
#### Changed
- Added an alias `DescriptorError` for `descriptor::error::Error`
- Changed the error returned by `descriptor!()` and `fragment!()` to `DescriptorError`
- Changed the error type in `ToWalletDescriptor` to `DescriptorError`
- Improved checks on descriptors built using the macros
### Blockchain
#### Changed
- Remove `BlockchainMarker`, `OfflineClient` and `OfflineWallet` in favor of just using the unit
type to mark for a missing client.
### CLI
#### Changed
- Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository
## [v0.2.0] - [0.1.0-beta.1]
### Project
#### Added
- Add CONTRIBUTING.md
@@ -209,5 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `MemoryDatabase` in the compiler example
- Make the REPL return JSON
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...HEAD
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...HEAD
[0.1.0-beta.1]: https://github.com/bitcoindevkit/bdk/compare/96c87ea5...0.1.0-beta.1
[v0.2.0]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...v0.2.0
[v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk"
version = "0.2.0"
version = "0.3.1-dev"
edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org"
@@ -9,7 +9,7 @@ documentation = "https://docs.rs/bdk"
description = "A modern, lightweight, descriptor-based wallet library"
keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
readme = "README.md"
license-file = "LICENSE"
license = "MIT"
[dependencies]
bdk-macros = "0.2"
@@ -22,11 +22,9 @@ rand = "^0.7"
# Optional dependencies
sled = { version = "0.34", optional = true }
electrum-client = { version = "0.4.0-beta.1", optional = true }
electrum-client = { version = "0.5.0-beta.1", optional = true }
reqwest = { version = "0.10", optional = true, features = ["json"] }
futures = { version = "0.3", optional = true }
clap = { version = "2.33", optional = true }
base64 = { version = "^0.11", optional = true }
async-trait = { version = "0.1", optional = true }
rocksdb = { version = "0.14", optional = true }
# pin cc version to 1.0.62 because 1.0.63 break rocksdb build
@@ -34,7 +32,6 @@ cc = { version = "=1.0.62", optional = true }
socks = { version = "0.3", optional = true }
lazy_static = { version = "1.4", optional = true }
tiny-bip39 = { version = "^0.8", optional = true }
structopt = { version = "^0.3", optional = true }
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@@ -47,13 +44,12 @@ rand = { version = "^0.7", features = ["wasm-bindgen"] }
[features]
minimal = []
compiler = ["clap", "miniscript/compiler"]
compiler = ["miniscript/compiler"]
default = ["key-value-db", "electrum"]
electrum = ["electrum-client"]
esplora = ["reqwest", "futures"]
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
key-value-db = ["sled"]
cli-utils = ["clap", "base64", "structopt"]
async-interface = ["async-trait"]
all-keys = ["keys-bip39"]
keys-bip39 = ["tiny-bip39"]
@@ -61,20 +57,17 @@ keys-bip39 = ["tiny-bip39"]
# Debug/Test features
debug-proc-macros = ["bdk-macros/debug", "bdk-testutils-macros/debug"]
test-electrum = ["electrum"]
test-md-docs = ["base64", "electrum"]
test-md-docs = ["electrum"]
[dev-dependencies]
bdk-testutils = "0.2"
bdk-testutils-macros = "0.2"
serial_test = "0.4"
lazy_static = "1.4"
rustyline = "6.0"
dirs-next = "2.0"
env_logger = "0.7"
base64 = "^0.11"
clap = "2.33"
[[example]]
name = "repl"
required-features = ["cli-utils"]
[[example]]
name = "parse_descriptor"
[[example]]

View File

@@ -35,12 +35,14 @@ Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordin
5. If a bug is found:
- If it's a minor issue you can just fix it in the release branch, since it will be merged back to `master` eventually
- For bigger issues you can fix them on `master` and then *cherry-pick* the commit to the release branch
6. On release day, make a commit on the release branch to bump the version to `x.y.z`. The message should be "Bump version to x.y.z".
7. Add a tag to this commit. The tag name should be `vx.y.z` (for example `v0.5.0`), and the message "Release x.y.z". Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
8. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
9. Publish **all** the updated crates to crates.io.
10. 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".
11. Merge the release branch back into `master`.
12. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
13. Announce the release on Twitter, Discord and Telegram.
14. Celebrate :tada:
6. Update the changelog with the new release version
7. On release day, make a commit on the release branch to bump the version to `x.y.z`. The message should be "Bump version to x.y.z".
8. Add a tag to this commit. The tag name should be `vx.y.z` (for example `v0.5.0`), and the message "Release x.y.z". Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
9. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
10. Publish **all** the updated crates to crates.io.
11. 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. Merge the release branch back into `master`.
13. 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. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
15. Announce the release on Twitter, Discord and Telegram.
16. Celebrate :tada:

View File

@@ -8,10 +8,11 @@
</p>
<p>
<!-- <a href="https://crates.io/crates/magical"><img alt="Crate Info" src="https://img.shields.io/crates/v/magical.svg"/></a> -->
<a href="https://crates.io/crates/bdk"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk.svg"/></a>
<a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT Licensed" src="https://img.shields.io/badge/license-MIT-blue.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://bitcoindevkit.org/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://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
</p>
@@ -19,7 +20,7 @@
<h4>
<a href="https://bitcoindevkit.org">Project Homepage</a>
<span> | </span>
<a href="https://bitcoindevkit.org/docs-rs/bdk">Documentation</a>
<a href="https://docs.rs/bdk">Documentation</a>
</h4>
</div>
@@ -65,11 +66,10 @@ fn main() -> Result<(), bdk::Error> {
### Generate a few addresses
```rust
use bdk::{Wallet, OfflineWallet};
use bdk::database::MemoryDatabase;
use bdk::{Wallet, database::MemoryDatabase};
fn main() -> Result<(), bdk::Error> {
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
@@ -125,13 +125,12 @@ fn main() -> Result<(), bdk::Error> {
### Sign a transaction
```rust,no_run
use bdk::{Wallet, OfflineWallet};
use bdk::database::MemoryDatabase;
use bdk::{Wallet, database::MemoryDatabase};
use bitcoin::consensus::deserialize;
fn main() -> Result<(), bdk::Error> {
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
bitcoin::Network::Testnet,

View File

@@ -29,7 +29,7 @@ use bdk::database::MemoryDatabase;
use bdk::descriptor::HDKeyPaths;
use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
use bdk::KeychainKind;
use bdk::{OfflineWallet, Wallet};
use bdk::Wallet;
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::bip32::Fingerprint;
@@ -59,7 +59,7 @@ impl AddressValidator for DummyValidator {
fn main() -> Result<(), bdk::Error> {
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
let mut wallet: OfflineWallet<_> =
let mut wallet =
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
wallet.add_address_validator(Arc::new(DummyValidator));

View File

@@ -40,7 +40,7 @@ use miniscript::policy::Concrete;
use miniscript::Descriptor;
use bdk::database::memory::MemoryDatabase;
use bdk::{KeychainKind, OfflineWallet, Wallet};
use bdk::{KeychainKind, Wallet};
fn main() {
env_logger::init_from_env(
@@ -98,8 +98,7 @@ fn main() {
Some("regtest") => Network::Regtest,
Some("testnet") | _ => Network::Testnet,
};
let wallet: OfflineWallet<_> =
Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap();
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap();
info!("... First address: {}", wallet.get_new_address().unwrap());

View File

@@ -1,174 +0,0 @@
// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020 Magical Bitcoin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use bitcoin::Network;
use clap::AppSettings;
use log::{debug, info, warn, LevelFilter};
use rustyline::error::ReadlineError;
use rustyline::Editor;
use structopt::StructOpt;
use bdk::bitcoin;
#[cfg(feature = "esplora")]
use bdk::blockchain::esplora::EsploraBlockchainConfig;
use bdk::blockchain::{
AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain, ElectrumBlockchainConfig,
};
use bdk::cli::{self, WalletOpt, WalletSubCommand};
use bdk::sled;
use bdk::Wallet;
#[derive(Debug, StructOpt, Clone, PartialEq)]
#[structopt(name = "BDK Wallet", setting = AppSettings::NoBinaryName,
version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
struct ReplOpt {
/// Wallet sub-command
#[structopt(subcommand)]
pub subcommand: WalletSubCommand,
}
fn prepare_home_dir() -> PathBuf {
let mut dir = PathBuf::new();
dir.push(&dirs_next::home_dir().unwrap());
dir.push(".bdk-bitcoin");
if !dir.exists() {
info!("Creating home directory {}", dir.as_path().display());
fs::create_dir(&dir).unwrap();
}
dir.push("database.sled");
dir
}
fn main() {
let cli_opt: WalletOpt = WalletOpt::from_args();
let level = LevelFilter::from_str(cli_opt.log_level.as_str()).unwrap_or(LevelFilter::Info);
env_logger::builder().filter_level(level).init();
let network = Network::from_str(cli_opt.network.as_str()).unwrap_or(Network::Testnet);
debug!("network: {:?}", network);
if network == Network::Bitcoin {
warn!("This is experimental software and not currently recommended for use on Bitcoin mainnet, proceed with caution.")
}
let descriptor = cli_opt.descriptor.as_str();
let change_descriptor = cli_opt.change_descriptor.as_deref();
debug!("descriptors: {:?} {:?}", descriptor, change_descriptor);
let database = sled::open(prepare_home_dir().to_str().unwrap()).unwrap();
let tree = database.open_tree(cli_opt.wallet).unwrap();
debug!("database opened successfully");
// Try to use Esplora config if "esplora" feature is enabled
#[cfg(feature = "esplora")]
let config_esplora: Option<AnyBlockchainConfig> = {
let esplora_concurrency = cli_opt.esplora_concurrency;
cli_opt.esplora.map(|base_url| {
AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
base_url: base_url.to_string(),
concurrency: Some(esplora_concurrency),
})
})
};
#[cfg(not(feature = "esplora"))]
let config_esplora = None;
// Fall back to Electrum config if Esplora config isn't provided
let config =
config_esplora.unwrap_or(AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
url: cli_opt.electrum,
socks5: cli_opt.proxy,
retry: 10,
timeout: 10,
}));
let wallet = Wallet::new(
descriptor,
change_descriptor,
network,
tree,
AnyBlockchain::from_config(&config).unwrap(),
)
.unwrap();
let wallet = Arc::new(wallet);
match cli_opt.subcommand {
WalletSubCommand::Other(external) if external.contains(&"repl".to_string()) => {
let mut rl = Editor::<()>::new();
// if rl.load_history("history.txt").is_err() {
// println!("No previous history.");
// }
loop {
let readline = rl.readline(">> ");
match readline {
Ok(line) => {
if line.trim() == "" {
continue;
}
rl.add_history_entry(line.as_str());
let split_line: Vec<&str> = line.split(" ").collect();
let repl_subcommand: Result<ReplOpt, clap::Error> =
ReplOpt::from_iter_safe(split_line);
debug!("repl_subcommand = {:?}", repl_subcommand);
if let Err(err) = repl_subcommand {
println!("{}", err.message);
continue;
}
let result = cli::handle_wallet_subcommand(
&Arc::clone(&wallet),
repl_subcommand.unwrap().subcommand,
)
.unwrap();
println!("{}", serde_json::to_string_pretty(&result).unwrap());
}
Err(ReadlineError::Interrupted) => continue,
Err(ReadlineError::Eof) => break,
Err(err) => {
println!("{:?}", err);
break;
}
}
}
// rl.save_history("history.txt").unwrap();
}
_ => {
let result = cli::handle_wallet_subcommand(&wallet, cli_opt.subcommand).unwrap();
println!("{}", serde_json::to_string_pretty(&result).unwrap());
}
}
}

View File

@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk-macros"
description = "Supporting macros for `bdk`"
keywords = ["bdk"]
license-file = "../LICENSE"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -131,7 +131,7 @@ impl CompactFiltersBlockchain {
let network = peers[0].get_network();
let cfs = DB::list_cf(&opts, &storage_dir).unwrap_or(vec!["default".to_string()]);
let cfs = DB::list_cf(&opts, &storage_dir).unwrap_or_else(|_| vec!["default".to_string()]);
let db = DB::open_cf(&opts, &storage_dir, &cfs)?;
let headers = Arc::new(ChainStore::new(db, network)?);
@@ -221,7 +221,7 @@ impl CompactFiltersBlockchain {
sent: outgoing,
height,
timestamp,
fees: inputs_sum.checked_sub(outputs_sum).unwrap_or(0),
fees: inputs_sum.saturating_sub(outputs_sum),
};
info!("Saving tx {}", tx.txid);
@@ -257,13 +257,10 @@ impl Blockchain for CompactFiltersBlockchain {
.map(|x| x / 1000)
.unwrap_or(0)
+ 1;
let expected_bundles_to_sync = total_bundles
.checked_sub(cf_sync.pruned_bundles()?)
.unwrap_or(0);
let expected_bundles_to_sync = total_bundles.saturating_sub(cf_sync.pruned_bundles()?);
let headers_cost = (first_peer.get_version().start_height as usize)
.checked_sub(initial_height)
.unwrap_or(0) as f32
.saturating_sub(initial_height) as f32
* SYNC_HEADERS_COST;
let filters_cost = expected_bundles_to_sync as f32 * SYNC_FILTERS_COST;
@@ -274,7 +271,7 @@ impl Blockchain for CompactFiltersBlockchain {
Arc::clone(&self.headers),
|new_height| {
let local_headers_cost =
new_height.checked_sub(initial_height).unwrap_or(0) as f32 * SYNC_HEADERS_COST;
new_height.saturating_sub(initial_height) as f32 * SYNC_HEADERS_COST;
progress_update.update(
local_headers_cost / total_cost * 100.0,
Some(format!("Synced headers to {}", new_height)),
@@ -288,9 +285,7 @@ impl Blockchain for CompactFiltersBlockchain {
}
let synced_height = self.headers.get_height()?;
let buried_height = synced_height
.checked_sub(sync::BURIED_CONFIRMATIONS)
.unwrap_or(0);
let buried_height = synced_height.saturating_sub(sync::BURIED_CONFIRMATIONS);
info!("Synced headers to height: {}", synced_height);
cf_sync.prepare_sync(Arc::clone(&first_peer))?;
@@ -303,7 +298,9 @@ impl Blockchain for CompactFiltersBlockchain {
.collect::<Vec<_>>(),
);
#[allow(clippy::mutex_atomic)]
let last_synced_block = Arc::new(Mutex::new(synced_height));
let synced_bundles = Arc::new(AtomicUsize::new(0));
let progress_update = Arc::new(Mutex::new(progress_update));
@@ -328,10 +325,7 @@ impl Blockchain for CompactFiltersBlockchain {
}
let block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
let saved_correct_block = match headers.get_full_block(block_height)? {
Some(block) if &block.block_hash() == block_hash => true,
_ => false,
};
let saved_correct_block = matches!(headers.get_full_block(block_height)?, Some(block) if &block.block_hash() == block_hash);
if saved_correct_block {
Ok(false)

View File

@@ -333,7 +333,7 @@ impl Peer {
NetworkMessage::Alert(_) => continue,
NetworkMessage::GetData(ref inv) => {
let (found, not_found): (Vec<_>, Vec<_>) = inv
.into_iter()
.iter()
.map(|item| (*item, reader_thread_mempool.get_tx(item)))
.partition(|(_, d)| d.is_some());
for (_, found_tx) in found {
@@ -518,10 +518,9 @@ impl InvPeer for Peer {
let getdata = inv
.iter()
.cloned()
.filter(|item| match item {
Inventory::Transaction(txid) if !self.mempool.has_tx(txid) => true,
_ => false,
})
.filter(
|item| matches!(item, Inventory::Transaction(txid) if !self.mempool.has_tx(txid)),
)
.collect::<Vec<_>>();
let num_txs = getdata.len();
self.send(NetworkMessage::GetData(getdata))?;

View File

@@ -375,7 +375,7 @@ impl ChainStore<Full> {
let min_height = match iterator
.next()
.and_then(|(k, _)| k[1..].try_into().ok())
.map(|bytes| usize::from_be_bytes(bytes))
.map(usize::from_be_bytes)
{
None => {
std::mem::drop(iterator);
@@ -444,9 +444,6 @@ impl ChainStore<Full> {
}
read_store.write(batch)?;
std::mem::drop(snapshot_cf_handle);
std::mem::drop(cf_handle);
std::mem::drop(read_store);
self.store.write().unwrap().drop_cf(&snaphost.cf_name)?;
@@ -461,7 +458,7 @@ impl ChainStore<Full> {
let read_store = self.store.read().unwrap();
let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
let key = StoreEntry::BlockHeaderIndex(Some(block_hash.clone())).get_key();
let key = StoreEntry::BlockHeaderIndex(Some(*block_hash)).get_key();
let data = read_store.get_pinned_cf(cf_handle, key)?;
Ok(data
.map(|data| {
@@ -642,7 +639,6 @@ impl<T: StoreType> ChainStore<T> {
);
}
std::mem::drop(cf_handle);
std::mem::drop(read_store);
self.store.write().unwrap().write(batch)?;

View File

@@ -204,9 +204,8 @@ impl CFSync {
if let BundleStatus::CFilters { cf_filters } = status {
log::trace!("status: CFilters");
let last_sync_buried_height = (start_height + already_processed)
.checked_sub(BURIED_CONFIRMATIONS)
.unwrap_or(0);
let last_sync_buried_height =
(start_height + already_processed).saturating_sub(BURIED_CONFIRMATIONS);
for (filter_index, filter) in cf_filters.iter().enumerate() {
let height = filter_index + start_height;
@@ -280,10 +279,7 @@ where
match locators_map.get(&headers[0].prev_blockhash) {
None => return Err(CompactFiltersError::InvalidHeaders),
Some(from) => (
store.start_snapshot(*from)?,
headers[0].prev_blockhash.clone(),
),
Some(from) => (store.start_snapshot(*from)?, headers[0].prev_blockhash),
}
} else {
return Err(CompactFiltersError::InvalidResponse);

View File

@@ -168,7 +168,7 @@ pub struct ElectrumBlockchainConfig {
/// Request retry count
pub retry: u8,
/// Request timeout (seconds)
pub timeout: u8,
pub timeout: Option<u8>,
}
impl ConfigurableBlockchain for ElectrumBlockchain {
@@ -178,8 +178,8 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
let socks5 = config.socks5.as_ref().map(Socks5Config::new);
let electrum_config = ConfigBuilder::new()
.retry(config.retry)
.socks5(socks5)?
.timeout(config.timeout)?
.socks5(socks5)?
.build();
Ok(ElectrumBlockchain(Client::from_config(

View File

@@ -79,29 +79,9 @@ pub enum Capability {
AccurateFees,
}
/// Marker trait for a blockchain backend
///
/// This is a marker trait for blockchain types. It is automatically implemented for types that
/// implement [`Blockchain`], so as a user of the library you won't have to implement this
/// manually.
///
/// Users of the library will probably never have to implement this trait manually, but they
/// could still need to import it to define types and structs with generics;
/// Implementing only the marker trait is pointless, since [`OfflineBlockchain`]
/// already does that, and whenever [`Blockchain`] is implemented, the marker trait is also
/// automatically implemented by the library.
pub trait BlockchainMarker {}
/// The [`BlockchainMarker`] marker trait is automatically implemented for [`Blockchain`] types
impl<T: Blockchain> BlockchainMarker for T {}
/// Type that only implements [`BlockchainMarker`] and is always "offline"
pub struct OfflineBlockchain;
impl BlockchainMarker for OfflineBlockchain {}
/// Trait that defines the actions that must be supported by a blockchain backend
#[maybe_async]
pub trait Blockchain: BlockchainMarker {
pub trait Blockchain {
/// Return the set of [`Capability`] supported by this backend
fn get_capabilities(&self) -> HashSet<Capability>;
@@ -189,7 +169,7 @@ pub fn progress() -> (Sender<ProgressData>, Receiver<ProgressData>) {
impl Progress for Sender<ProgressData> {
fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
if progress < 0.0 || progress > 100.0 {
if !(0.0..=100.0).contains(&progress) {
return Err(Error::InvalidProgressValue(progress));
}

View File

@@ -1,750 +0,0 @@
// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020 Magical Bitcoin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Command line interface
//!
//! This module provides a [structopt](https://docs.rs/crate/structopt) `struct` and `enum` that
//! parse global wallet options and wallet subcommand options needed for a wallet command line
//! interface.
//!
//! See the `repl.rs` example for how to use this module to create a simple command line REPL
//! wallet application.
//!
//! See [`WalletOpt`] for global wallet options and [`WalletSubCommand`] for supported sub-commands.
//!
//! # Example
//!
//! ```
//! # use bdk::bitcoin::Network;
//! # use bdk::blockchain::esplora::EsploraBlockchainConfig;
//! # use bdk::blockchain::{AnyBlockchain, ConfigurableBlockchain};
//! # use bdk::blockchain::{AnyBlockchainConfig, ElectrumBlockchainConfig};
//! # use bdk::cli::{self, WalletOpt, WalletSubCommand};
//! # use bdk::database::MemoryDatabase;
//! # use bdk::Wallet;
//! # use bitcoin::hashes::core::str::FromStr;
//! # use std::sync::Arc;
//! # use structopt::StructOpt;
//!
//! // to get args from cli use:
//! // let cli_opt = WalletOpt::from_args();
//!
//! let cli_args = vec!["repl", "--network", "testnet", "--descriptor",
//! "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
//! "sync", "--max_addresses", "50"];
//! let cli_opt = WalletOpt::from_iter(&cli_args);
//!
//! let network = Network::from_str(cli_opt.network.as_str()).unwrap_or(Network::Testnet);
//!
//! let descriptor = cli_opt.descriptor.as_str();
//! let change_descriptor = cli_opt.change_descriptor.as_deref();
//!
//! let database = MemoryDatabase::new();
//!
//! let config = match cli_opt.esplora {
//! Some(base_url) => AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
//! base_url: base_url.to_string(),
//! concurrency: Some(cli_opt.esplora_concurrency),
//! }),
//! None => AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
//! url: cli_opt.electrum,
//! socks5: cli_opt.proxy,
//! retry: 3,
//! timeout: 5,
//! }),
//! };
//!
//! let wallet = Wallet::new(
//! descriptor,
//! change_descriptor,
//! network,
//! database,
//! AnyBlockchain::from_config(&config).unwrap(),
//! ).unwrap();
//!
//! let wallet = Arc::new(wallet);
//!
//! let result = cli::handle_wallet_subcommand(&wallet, cli_opt.subcommand).unwrap();
//! println!("{}", serde_json::to_string_pretty(&result).unwrap());
//! ```
use std::collections::BTreeMap;
use std::str::FromStr;
use structopt::StructOpt;
#[allow(unused_imports)]
use log::{debug, error, info, trace, LevelFilter};
use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::{Address, OutPoint, Script, Txid};
use crate::blockchain::log_progress;
use crate::error::Error;
use crate::types::KeychainKind;
use crate::{FeeRate, TxBuilder, Wallet};
/// Wallet global options and sub-command
///
/// A [structopt](https://docs.rs/crate/structopt) `struct` that parses wallet global options and
/// sub-command from the command line or from a `String` vector. See [`WalletSubCommand`] for details
/// on parsing sub-commands.
///
/// # Example
///
/// ```
/// # use bdk::cli::{WalletOpt, WalletSubCommand};
/// # use structopt::StructOpt;
///
/// let cli_args = vec!["repl", "--network", "testnet",
/// "--descriptor", "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/44'/1'/0'/0/*)",
/// "sync", "--max_addresses", "50"];
///
/// // to get WalletOpt from OS command line args use:
/// // let wallet_opt = WalletOpt::from_args();
///
/// let wallet_opt = WalletOpt::from_iter(&cli_args);
///
/// let expected_wallet_opt = WalletOpt {
/// network: "testnet".to_string(),
/// wallet: "main".to_string(),
/// proxy: None,
/// descriptor: "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/44'/1'/0'/0/*)".to_string(),
/// change_descriptor: None,
/// log_level: "info".to_string(),
/// #[cfg(feature = "esplora")]
/// esplora: None,
/// #[cfg(feature = "esplora")]
/// esplora_concurrency: 4,
/// electrum: "ssl://electrum.blockstream.info:60002".to_string(),
/// subcommand: WalletSubCommand::Sync {
/// max_addresses: Some(50)
/// },
/// };
///
/// assert_eq!(expected_wallet_opt, wallet_opt);
/// ```
#[derive(Debug, StructOpt, Clone, PartialEq)]
#[structopt(name = "BDK Wallet",
version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
pub struct WalletOpt {
/// Sets the network
#[structopt(
name = "NETWORK",
short = "n",
long = "network",
default_value = "testnet"
)]
pub network: String,
/// Selects the wallet to use
#[structopt(
name = "WALLET_NAME",
short = "w",
long = "wallet",
default_value = "main"
)]
pub wallet: String,
#[cfg(feature = "electrum")]
/// Sets the SOCKS5 proxy for the Electrum client
#[structopt(name = "PROXY_SERVER:PORT", short = "p", long = "proxy")]
pub proxy: Option<String>,
/// Sets the descriptor to use for the external addresses
#[structopt(name = "DESCRIPTOR", short = "d", long = "descriptor", required = true)]
pub descriptor: String,
/// Sets the descriptor to use for internal addresses
#[structopt(name = "CHANGE_DESCRIPTOR", short = "c", long = "change_descriptor")]
pub change_descriptor: Option<String>,
/// Sets the logging level filter (off, error, warn, info, debug, trace)
#[structopt(long = "log_level", short = "l", default_value = "info")]
pub log_level: String,
#[cfg(feature = "esplora")]
/// Use the esplora server if given as parameter
#[structopt(name = "ESPLORA_URL", short = "e", long = "esplora")]
pub esplora: Option<String>,
#[cfg(feature = "esplora")]
/// Concurrency of requests made to the esplora server
#[structopt(
name = "ESPLORA_CONCURRENCY",
long = "esplora_concurrency",
default_value = "4"
)]
pub esplora_concurrency: u8,
#[cfg(feature = "electrum")]
/// Sets the Electrum server to use
#[structopt(
name = "SERVER:PORT",
short = "s",
long = "server",
default_value = "ssl://electrum.blockstream.info:60002"
)]
pub electrum: String,
/// Wallet sub-command
#[structopt(subcommand)]
pub subcommand: WalletSubCommand,
}
/// Wallet sub-command
///
/// A [structopt](https://docs.rs/crate/structopt) enum that parses wallet sub-command arguments from
/// the command line or from a `String` vector, such as in the [`repl`](https://github.com/bitcoindevkit/bdk/blob/master/examples/repl.rs)
/// example app.
///
/// Additional "external" sub-commands can be captured via the [`WalletSubCommand::Other`] enum and passed to a
/// custom `structopt` or another parser. See [structopt "External subcommands"](https://docs.rs/structopt/0.3.21/structopt/index.html#external-subcommands)
/// for more information.
///
/// # Example
///
/// ```
/// # use bdk::cli::WalletSubCommand;
/// # use structopt::StructOpt;
///
/// let sync_sub_command = WalletSubCommand::from_iter(&["repl", "sync", "--max_addresses", "50"]);
/// assert!(matches!(
/// sync_sub_command,
/// WalletSubCommand::Sync {
/// max_addresses: Some(50)
/// }
/// ));
///
/// let other_sub_command = WalletSubCommand::from_iter(&["repl", "custom", "--param1", "20"]);
/// let external_args: Vec<String> = vec!["custom".to_string(), "--param1".to_string(), "20".to_string()];
/// assert!(matches!(
/// other_sub_command,
/// WalletSubCommand::Other(v) if v == external_args
/// ));
/// ```
///
/// To capture wallet sub-commands from a string vector without a preceeding binary name you can
/// create a custom struct the includes the `NoBinaryName` clap setting and wraps the WalletSubCommand
/// enum. See also the [`repl`](https://github.com/bitcoindevkit/bdk/blob/master/examples/repl.rs)
/// example app.
///
/// # Example
/// ```
/// # use bdk::cli::WalletSubCommand;
/// # use structopt::StructOpt;
/// # use clap::AppSettings;
///
/// #[derive(Debug, StructOpt, Clone, PartialEq)]
/// #[structopt(name = "BDK Wallet", setting = AppSettings::NoBinaryName,
/// version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
/// author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
/// struct ReplOpt {
/// /// Wallet sub-command
/// #[structopt(subcommand)]
/// pub subcommand: WalletSubCommand,
/// }
/// ```
#[derive(Debug, StructOpt, Clone, PartialEq)]
#[structopt(
rename_all = "snake",
long_about = "A modern, lightweight, descriptor-based wallet"
)]
pub enum WalletSubCommand {
/// Generates a new external address
GetNewAddress,
/// Syncs with the chosen blockchain server
Sync {
/// max addresses to consider
#[structopt(short = "v", long = "max_addresses")]
max_addresses: Option<u32>,
},
/// Lists the available spendable UTXOs
ListUnspent,
/// Lists all the incoming and outgoing transactions of the wallet
ListTransactions,
/// Returns the current wallet balance
GetBalance,
/// Creates a new unsigned transaction
CreateTx {
/// Adds a recipient to the transaction
#[structopt(name = "ADDRESS:SAT", long = "to", required = true, parse(try_from_str = parse_recipient))]
recipients: Vec<(Script, u64)>,
/// Sends all the funds (or all the selected utxos). Requires only one recipients of value 0
#[structopt(short = "all", long = "send_all")]
send_all: bool,
/// Enables Replace-By-Fee (BIP125)
#[structopt(short = "rbf", long = "enable_rbf")]
enable_rbf: bool,
/// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
#[structopt(long = "offline_signer")]
offline_signer: bool,
/// Selects which utxos *must* be spent
#[structopt(name = "MUST_SPEND_TXID:VOUT", long = "utxos", parse(try_from_str = parse_outpoint))]
utxos: Option<Vec<OutPoint>>,
/// Marks a utxo as unspendable
#[structopt(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", parse(try_from_str = parse_outpoint))]
unspendable: Option<Vec<OutPoint>>,
/// Fee rate to use in sat/vbyte
#[structopt(name = "SATS_VBYTE", short = "fee", long = "fee_rate")]
fee_rate: Option<f32>,
/// Selects which policy should be used to satisfy the external descriptor
#[structopt(name = "EXT_POLICY", long = "external_policy")]
external_policy: Option<String>,
/// Selects which policy should be used to satisfy the internal descriptor
#[structopt(name = "INT_POLICY", long = "internal_policy")]
internal_policy: Option<String>,
},
/// Bumps the fees of an RBF transaction
BumpFee {
/// TXID of the transaction to update
#[structopt(name = "TXID", short = "txid", long = "txid")]
txid: String,
/// Allows the wallet to reduce the amount of the only output in order to increase fees. This is generally the expected behavior for transactions originally created with `send_all`
#[structopt(short = "all", long = "send_all")]
send_all: bool,
/// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
#[structopt(long = "offline_signer")]
offline_signer: bool,
/// Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used
#[structopt(name = "MUST_SPEND_TXID:VOUT", long = "utxos", parse(try_from_str = parse_outpoint))]
utxos: Option<Vec<OutPoint>>,
/// Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees
#[structopt(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", parse(try_from_str = parse_outpoint))]
unspendable: Option<Vec<OutPoint>>,
/// The new targeted fee rate in sat/vbyte
#[structopt(name = "SATS_VBYTE", short = "fee", long = "fee_rate")]
fee_rate: f32,
},
/// Returns the available spending policies for the descriptor
Policies,
/// Returns the public version of the wallet's descriptor(s)
PublicDescriptor,
/// Signs and tries to finalize a PSBT
Sign {
/// Sets the PSBT to sign
#[structopt(name = "BASE64_PSBT", long = "psbt")]
psbt: String,
/// Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor
#[structopt(name = "HEIGHT", long = "assume_height")]
assume_height: Option<u32>,
},
/// Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract
Broadcast {
/// Sets the PSBT to sign
#[structopt(
name = "BASE64_PSBT",
long = "psbt",
required_unless = "RAWTX",
conflicts_with = "RAWTX"
)]
psbt: Option<String>,
/// Sets the raw transaction to broadcast
#[structopt(
name = "RAWTX",
long = "tx",
required_unless = "BASE64_PSBT",
conflicts_with = "BASE64_PSBT"
)]
tx: Option<String>,
},
/// Extracts a raw transaction from a PSBT
ExtractPsbt {
/// Sets the PSBT to extract
#[structopt(name = "BASE64_PSBT", long = "psbt")]
psbt: String,
},
/// Finalizes a PSBT
FinalizePsbt {
/// Sets the PSBT to finalize
#[structopt(name = "BASE64_PSBT", long = "psbt")]
psbt: String,
/// Assume the blockchain has reached a specific height
#[structopt(name = "HEIGHT", long = "assume_height")]
assume_height: Option<u32>,
},
/// Combines multiple PSBTs into one
CombinePsbt {
/// Add one PSBT to combine. This option can be repeated multiple times, one for each PSBT
#[structopt(name = "BASE64_PSBT", long = "psbt", required = true)]
psbt: Vec<String>,
},
/// Put any extra arguments into this Vec
#[structopt(external_subcommand)]
Other(Vec<String>),
}
fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
let parts: Vec<_> = s.split(':').collect();
if parts.len() != 2 {
return Err("Invalid format".to_string());
}
let addr = Address::from_str(&parts[0]);
if let Err(e) = addr {
return Err(format!("{:?}", e));
}
let val = u64::from_str(&parts[1]);
if let Err(e) = val {
return Err(format!("{:?}", e));
}
Ok((addr.unwrap().script_pubkey(), val.unwrap()))
}
fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
}
/// Execute a wallet sub-command with a given [`Wallet`].
///
/// Wallet sub-commands are described in [`WalletSubCommand`]. See [`super::cli`] for example usage.
#[maybe_async]
pub fn handle_wallet_subcommand<C, D>(
wallet: &Wallet<C, D>,
wallet_subcommand: WalletSubCommand,
) -> Result<serde_json::Value, Error>
where
C: crate::blockchain::Blockchain,
D: crate::database::BatchDatabase,
{
match wallet_subcommand {
WalletSubCommand::GetNewAddress => Ok(json!({"address": wallet.get_new_address()?})),
WalletSubCommand::Sync { max_addresses } => {
maybe_await!(wallet.sync(log_progress(), max_addresses))?;
Ok(json!({}))
}
WalletSubCommand::ListUnspent => Ok(serde_json::to_value(&wallet.list_unspent()?)?),
WalletSubCommand::ListTransactions => {
Ok(serde_json::to_value(&wallet.list_transactions(false)?)?)
}
WalletSubCommand::GetBalance => Ok(json!({"satoshi": wallet.get_balance()?})),
WalletSubCommand::CreateTx {
recipients,
send_all,
enable_rbf,
offline_signer,
utxos,
unspendable,
fee_rate,
external_policy,
internal_policy,
} => {
let mut tx_builder = TxBuilder::new();
if send_all {
tx_builder = tx_builder
.drain_wallet()
.set_single_recipient(recipients[0].0.clone());
} else {
tx_builder = tx_builder.set_recipients(recipients);
}
if enable_rbf {
tx_builder = tx_builder.enable_rbf();
}
if offline_signer {
tx_builder = tx_builder
.force_non_witness_utxo()
.include_output_redeem_witness_script();
}
if let Some(fee_rate) = fee_rate {
tx_builder = tx_builder.fee_rate(FeeRate::from_sat_per_vb(fee_rate));
}
if let Some(utxos) = utxos {
tx_builder = tx_builder.utxos(utxos).manually_selected_only();
}
if let Some(unspendable) = unspendable {
tx_builder = tx_builder.unspendable(unspendable);
}
let policies = vec![
external_policy.map(|p| (p, KeychainKind::External)),
internal_policy.map(|p| (p, KeychainKind::Internal)),
];
for (policy, keychain) in policies.into_iter().filter_map(|x| x) {
let policy = serde_json::from_str::<BTreeMap<String, Vec<usize>>>(&policy)
.map_err(|s| Error::Generic(s.to_string()))?;
tx_builder = tx_builder.policy_path(policy, keychain);
}
let (psbt, details) = wallet.create_tx(tx_builder)?;
Ok(json!({"psbt": base64::encode(&serialize(&psbt)),"details": details,}))
}
WalletSubCommand::BumpFee {
txid,
send_all,
offline_signer,
utxos,
unspendable,
fee_rate,
} => {
let txid = Txid::from_str(txid.as_str()).map_err(|s| Error::Generic(s.to_string()))?;
let mut tx_builder = TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(fee_rate));
if send_all {
tx_builder = tx_builder.maintain_single_recipient();
}
if offline_signer {
tx_builder = tx_builder
.force_non_witness_utxo()
.include_output_redeem_witness_script();
}
if let Some(utxos) = utxos {
tx_builder = tx_builder.utxos(utxos);
}
if let Some(unspendable) = unspendable {
tx_builder = tx_builder.unspendable(unspendable);
}
let (psbt, details) = wallet.bump_fee(&txid, tx_builder)?;
Ok(json!({"psbt": base64::encode(&serialize(&psbt)),"details": details,}))
}
WalletSubCommand::Policies => Ok(json!({
"external": wallet.policies(KeychainKind::External)?,
"internal": wallet.policies(KeychainKind::Internal)?,
})),
WalletSubCommand::PublicDescriptor => Ok(json!({
"external": wallet.public_descriptor(KeychainKind::External)?.map(|d| d.to_string()),
"internal": wallet.public_descriptor(KeychainKind::Internal)?.map(|d| d.to_string()),
})),
WalletSubCommand::Sign {
psbt,
assume_height,
} => {
let psbt = base64::decode(&psbt).unwrap();
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
let (psbt, finalized) = wallet.sign(psbt, assume_height)?;
Ok(json!({"psbt": base64::encode(&serialize(&psbt)),"is_finalized": finalized,}))
}
WalletSubCommand::Broadcast { psbt, tx } => {
let tx = match (psbt, tx) {
(Some(psbt), None) => {
let psbt = base64::decode(&psbt).unwrap();
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
psbt.extract_tx()
}
(None, Some(tx)) => deserialize(&Vec::<u8>::from_hex(&tx).unwrap()).unwrap(),
(Some(_), Some(_)) => panic!("Both `psbt` and `tx` options not allowed"),
(None, None) => panic!("Missing `psbt` and `tx` option"),
};
let txid = maybe_await!(wallet.broadcast(tx))?;
Ok(json!({ "txid": txid }))
}
WalletSubCommand::ExtractPsbt { psbt } => {
let psbt = base64::decode(&psbt).unwrap();
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
Ok(json!({"raw_tx": serialize_hex(&psbt.extract_tx()),}))
}
WalletSubCommand::FinalizePsbt {
psbt,
assume_height,
} => {
let psbt = base64::decode(&psbt).unwrap();
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
let (psbt, finalized) = wallet.finalize_psbt(psbt, assume_height)?;
Ok(json!({ "psbt": base64::encode(&serialize(&psbt)),"is_finalized": finalized,}))
}
WalletSubCommand::CombinePsbt { psbt } => {
let mut psbts = psbt
.iter()
.map(|s| {
let psbt = base64::decode(&s).unwrap();
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
psbt
})
.collect::<Vec<_>>();
let init_psbt = psbts.pop().unwrap();
let final_psbt = psbts
.into_iter()
.try_fold::<_, _, Result<PartiallySignedTransaction, Error>>(
init_psbt,
|mut acc, x| {
acc.merge(x)?;
Ok(acc)
},
)?;
Ok(json!({ "psbt": base64::encode(&serialize(&final_psbt)) }))
}
WalletSubCommand::Other(_) => Ok(json!({})),
}
}
#[cfg(test)]
mod test {
use super::{WalletOpt, WalletSubCommand};
use bitcoin::hashes::core::str::FromStr;
use bitcoin::{Address, OutPoint};
use structopt::StructOpt;
#[test]
fn test_get_new_address() {
let cli_args = vec!["repl", "--network", "bitcoin",
"--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
"--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
"--esplora", "https://blockstream.info/api/",
"--esplora_concurrency", "5",
"get_new_address"];
let wallet_opt = WalletOpt::from_iter(&cli_args);
let expected_wallet_opt = WalletOpt {
network: "bitcoin".to_string(),
wallet: "main".to_string(),
proxy: None,
descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
log_level: "info".to_string(),
#[cfg(feature = "esplora")]
esplora: Some("https://blockstream.info/api/".to_string()),
#[cfg(feature = "esplora")]
esplora_concurrency: 5,
electrum: "ssl://electrum.blockstream.info:60002".to_string(),
subcommand: WalletSubCommand::GetNewAddress,
};
assert_eq!(expected_wallet_opt, wallet_opt);
}
#[test]
fn test_sync() {
let cli_args = vec!["repl", "--network", "testnet",
"--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
"sync", "--max_addresses", "50"];
let wallet_opt = WalletOpt::from_iter(&cli_args);
let expected_wallet_opt = WalletOpt {
network: "testnet".to_string(),
wallet: "main".to_string(),
proxy: None,
descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
change_descriptor: None,
log_level: "info".to_string(),
#[cfg(feature = "esplora")]
esplora: None,
#[cfg(feature = "esplora")]
esplora_concurrency: 4,
electrum: "ssl://electrum.blockstream.info:60002".to_string(),
subcommand: WalletSubCommand::Sync {
max_addresses: Some(50)
},
};
assert_eq!(expected_wallet_opt, wallet_opt);
}
#[test]
fn test_create_tx() {
let cli_args = vec!["repl", "--network", "testnet", "--proxy", "127.0.0.1:9150",
"--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
"--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
"--server","ssl://electrum.blockstream.info:50002",
"create_tx", "--to", "n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ:123456","mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf:78910",
"--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
"--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2"];
let wallet_opt = WalletOpt::from_iter(&cli_args);
let script1 = Address::from_str("n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ")
.unwrap()
.script_pubkey();
let script2 = Address::from_str("mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf")
.unwrap()
.script_pubkey();
let outpoint1 = OutPoint::from_str(
"87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
)
.unwrap();
let outpoint2 = OutPoint::from_str(
"87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2",
)
.unwrap();
let expected_wallet_opt = WalletOpt {
network: "testnet".to_string(),
wallet: "main".to_string(),
proxy: Some("127.0.0.1:9150".to_string()),
descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
log_level: "info".to_string(),
#[cfg(feature = "esplora")]
esplora: None,
#[cfg(feature = "esplora")]
esplora_concurrency: 4,
electrum: "ssl://electrum.blockstream.info:50002".to_string(),
subcommand: WalletSubCommand::CreateTx {
recipients: vec![(script1, 123456), (script2, 78910)],
send_all: false,
enable_rbf: false,
offline_signer: false,
utxos: Some(vec!(outpoint1, outpoint2)),
unspendable: None,
fee_rate: None,
external_policy: None,
internal_policy: None,
},
};
assert_eq!(expected_wallet_opt, wallet_opt);
}
#[test]
fn test_broadcast() {
let cli_args = vec!["repl", "--network", "testnet",
"--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
"broadcast",
"--psbt", "cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA="];
let wallet_opt = WalletOpt::from_iter(&cli_args);
let expected_wallet_opt = WalletOpt {
network: "testnet".to_string(),
wallet: "main".to_string(),
proxy: None,
descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
change_descriptor: None,
log_level: "info".to_string(),
#[cfg(feature = "esplora")]
esplora: None,
#[cfg(feature = "esplora")]
esplora_concurrency: 4,
electrum: "ssl://electrum.blockstream.info:60002".to_string(),
subcommand: WalletSubCommand::Broadcast {
psbt: Some("cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA=".to_string()),
tx: None
},
};
assert_eq!(expected_wallet_opt, wallet_opt);
}
}

View File

@@ -29,21 +29,19 @@
//!
//! ## Example
//!
//! In this example, `wallet_memory` and `wallet_sled` have the same type of `Wallet<OfflineBlockchain, AnyDatabase>`.
//! In this example, `wallet_memory` and `wallet_sled` have the same type of `Wallet<(), AnyDatabase>`.
//!
//! ```no_run
//! # use bitcoin::Network;
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
//! # use bdk::{Wallet, OfflineWallet};
//! let memory = MemoryDatabase::default().into();
//! let wallet_memory: OfflineWallet<AnyDatabase> =
//! Wallet::new_offline("...", None, Network::Testnet, memory)?;
//! # use bdk::{Wallet};
//! let memory = MemoryDatabase::default();
//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?;
//!
//! # #[cfg(feature = "key-value-db")]
//! # {
//! let sled = sled::open("my-database")?.open_tree("default_tree")?.into();
//! let wallet_sled: OfflineWallet<AnyDatabase> =
//! Wallet::new_offline("...", None, Network::Testnet, sled)?;
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?;
//! # }
//! # Ok::<(), bdk::Error>(())
//! ```
@@ -54,10 +52,10 @@
//! ```no_run
//! # use bitcoin::Network;
//! # use bdk::database::*;
//! # use bdk::{Wallet, OfflineWallet};
//! # use bdk::{Wallet};
//! let config = serde_json::from_str("...")?;
//! let database = AnyDatabase::from_config(&config)?;
//! let wallet: OfflineWallet<_> = Wallet::new_offline("...", None, Network::Testnet, database)?;
//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?;
//! # Ok::<(), bdk::Error>(())
//! ```

View File

@@ -29,7 +29,7 @@
use std::iter::FromIterator;
use crate::descriptor::Error;
use crate::descriptor::DescriptorError;
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
@@ -57,14 +57,14 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
}
/// Compute the checksum of a descriptor
pub fn get_checksum(desc: &str) -> Result<String, Error> {
pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
let mut c = 1;
let mut cls = 0;
let mut clscount = 0;
for ch in desc.chars() {
let pos = INPUT_CHARSET
.find(ch)
.ok_or(Error::InvalidDescriptorCharacter(ch))? as u64;
.ok_or(DescriptorError::InvalidDescriptorCharacter(ch))? as u64;
c = poly_mod(c, pos & 31);
cls = cls * 3 + (pos >> 5);
clscount += 1;
@@ -120,7 +120,7 @@ mod test {
assert!(matches!(
get_checksum(&invalid_desc).err(),
Some(Error::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart
));
}
}

View File

@@ -60,6 +60,7 @@ macro_rules! impl_top_level_pk {
$key.to_descriptor_key()
.and_then(|key: DescriptorKey<$ctx>| key.extract(&secp))
.map_err($crate::descriptor::DescriptorError::Key)
.map(|(pk, key_map, valid_networks)| {
(
$crate::miniscript::Descriptor::<
@@ -75,11 +76,17 @@ macro_rules! impl_top_level_pk {
#[doc(hidden)]
#[macro_export]
macro_rules! impl_leaf_opcode {
( $terminal_variant:ident ) => {
( $terminal_variant:ident ) => {{
use $crate::descriptor::CheckMiniscript;
$crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
)
.map_err($crate::Error::Miniscript)
.map_err($crate::descriptor::DescriptorError::Miniscript)
.and_then(|minisc| {
minisc.check_minsicript()?;
Ok(minisc)
})
.map(|minisc| {
(
minisc,
@@ -87,17 +94,23 @@ macro_rules! impl_leaf_opcode {
$crate::keys::any_network(),
)
})
};
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_leaf_opcode_value {
( $terminal_variant:ident, $value:expr ) => {
( $terminal_variant:ident, $value:expr ) => {{
use $crate::descriptor::CheckMiniscript;
$crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
)
.map_err($crate::Error::Miniscript)
.map_err($crate::descriptor::DescriptorError::Miniscript)
.and_then(|minisc| {
minisc.check_minsicript()?;
Ok(minisc)
})
.map(|minisc| {
(
minisc,
@@ -105,17 +118,23 @@ macro_rules! impl_leaf_opcode_value {
$crate::keys::any_network(),
)
})
};
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_leaf_opcode_value_two {
( $terminal_variant:ident, $one:expr, $two:expr ) => {
( $terminal_variant:ident, $one:expr, $two:expr ) => {{
use $crate::descriptor::CheckMiniscript;
$crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
)
.map_err($crate::Error::Miniscript)
.map_err($crate::descriptor::DescriptorError::Miniscript)
.and_then(|minisc| {
minisc.check_minsicript()?;
Ok(minisc)
})
.map(|minisc| {
(
minisc,
@@ -123,13 +142,15 @@ macro_rules! impl_leaf_opcode_value_two {
$crate::keys::any_network(),
)
})
};
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_node_opcode_two {
( $terminal_variant:ident, $( $inner:tt )* ) => ({
use $crate::descriptor::CheckMiniscript;
let inner = $crate::fragment_internal!( @t $( $inner )* );
let (a, b) = $crate::descriptor::dsl::TupleTwo::from(inner).flattened();
@@ -139,10 +160,14 @@ macro_rules! impl_node_opcode_two {
// join key_maps
a_keymap.extend(b_keymap.into_iter());
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
std::sync::Arc::new(a_minisc),
std::sync::Arc::new(b_minisc),
))?, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
))?;
minisc.check_minsicript()?;
Ok((minisc, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
})
});
}
@@ -151,6 +176,8 @@ macro_rules! impl_node_opcode_two {
#[macro_export]
macro_rules! impl_node_opcode_three {
( $terminal_variant:ident, $( $inner:tt )* ) => {
use $crate::descriptor::CheckMiniscript;
let inner = $crate::fragment_internal!( @t $( $inner )* );
let (a, b, c) = $crate::descriptor::dsl::TupleThree::from(inner).flattened();
@@ -164,11 +191,15 @@ macro_rules! impl_node_opcode_three {
let networks = $crate::keys::merge_networks(&a_networks, &b_networks);
let networks = $crate::keys::merge_networks(&networks, &c_networks);
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
std::sync::Arc::new(a_minisc),
std::sync::Arc::new(b_minisc),
std::sync::Arc::new(c_minisc),
))?, a_keymap, networks))
))?;
minisc.check_minsicript()?;
Ok((minisc, a_keymap, networks))
})
};
}
@@ -190,6 +221,7 @@ macro_rules! impl_sortedmulti {
)*
keys.into_iter().collect::<Result<Vec<_>, _>>()
.map_err($crate::descriptor::DescriptorError::Key)
.and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp))
});
@@ -199,18 +231,20 @@ macro_rules! impl_sortedmulti {
#[macro_export]
macro_rules! apply_modifier {
( $terminal_variant:ident, $inner:expr ) => {{
use $crate::descriptor::CheckMiniscript;
$inner
.map_err(|e| -> $crate::Error { e.into() })
.map_err(|e| -> $crate::descriptor::DescriptorError { e.into() })
.and_then(|(minisc, keymap, networks)| {
Ok((
$crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
std::sync::Arc::new(minisc),
),
)?,
keymap,
networks,
))
let minisc = $crate::miniscript::Miniscript::from_ast(
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
std::sync::Arc::new(minisc),
),
)?;
minisc.check_minsicript()?;
Ok((minisc, keymap, networks))
})
}};
@@ -272,7 +306,7 @@ macro_rules! apply_modifier {
/// Macro to write full descriptors with code
///
/// This macro expands to a `Result` of
/// [`DescriptorTemplateOut`](super::template::DescriptorTemplateOut) and [`Error`](crate::Error)
/// [`DescriptorTemplateOut`](super::template::DescriptorTemplateOut) and [`DescriptorError`](crate::descriptor::DescriptorError)
///
/// The syntax is very similar to the normal descriptor syntax, with the exception that modifiers
/// cannot be grouped together. For instance, a descriptor fragment like `sdv:older(144)` has to be
@@ -305,8 +339,11 @@ macro_rules! apply_modifier {
///
/// ```
/// # use std::str::FromStr;
/// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
/// let my_key_1 = bitcoin::PublicKey::from_str(
/// "02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c",
/// )?;
/// let my_key_2 =
/// bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
/// let my_timelock = 50;
///
/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
@@ -315,12 +352,13 @@ macro_rules! apply_modifier {
/// )
/// }?;
///
/// #[rustfmt::skip]
/// let b_items = vec![
/// bdk::fragment!(pk(my_key_1))?,
/// bdk::fragment!(s:pk(my_key_2))?,
/// bdk::fragment!(s:d:v:older(my_timelock))?,
/// ];
/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!(wsh(thresh_vec(2,b_items)))?;
/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!(wsh(thresh_vec(2, b_items)))?;
///
/// assert_eq!(descriptor_a, descriptor_b);
/// assert_eq!(key_map_a.len(), key_map_b.len());
@@ -496,7 +534,7 @@ macro_rules! fragment_internal {
/// Macro to write descriptor fragments with code
///
/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), Error>`. It allows writing
/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), DescriptorError>`. It allows writing
/// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec(m, ...))`.
///
/// The syntax to write macro fragment is the same as documented for the [`descriptor`] macro.
@@ -599,6 +637,7 @@ macro_rules! fragment {
)*
keys.into_iter().collect::<Result<Vec<_>, _>>()
.map_err($crate::descriptor::DescriptorError::Key)
.and_then(|keys| $crate::keys::make_multi($thresh, keys, &secp))
});
@@ -620,8 +659,8 @@ mod test {
use std::str::FromStr;
use crate::descriptor::DescriptorMeta;
use crate::keys::{DescriptorKey, KeyError, ToDescriptorKey, ValidNetworks};
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::{DescriptorKey, ToDescriptorKey, ValidNetworks};
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet};
use bitcoin::util::bip32;
use bitcoin::util::bip32::ChildNumber;
@@ -631,7 +670,7 @@ mod test {
// verify descriptor generates expected script(s) (if bare or pk) or address(es)
fn check(
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError>,
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
is_witness: bool,
is_fixed: bool,
expected: &[&str],
@@ -978,4 +1017,15 @@ mod test {
assert_eq!(descriptor.to_string(), "wsh(thresh(2,dv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))")
}
// TODO: uncomment once https://github.com/rust-bitcoin/rust-miniscript/pull/221 is released
//
// #[test]
// #[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")]
// fn test_dsl_miniscript_checks() {
// let mut uncompressed_pk = PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
// uncompressed_pk.compressed = false;
// descriptor!(wsh(v:pk(uncompressed_pk))).unwrap();
// }
}

View File

@@ -27,26 +27,19 @@
/// Errors related to the parsing and usage of descriptors
#[derive(Debug)]
pub enum Error {
//InternalError,
//InvalidPrefix(Vec<u8>),
//HardenedDerivationOnXpub,
//MalformedInput,
/// Invalid HD Key path, such as having a wildcard but a length != 1
InvalidHDKeyPath,
/// The provided descriptor doesn't match its checksum
InvalidDescriptorChecksum,
//KeyParsingError(String),
/// Error thrown while working with [`keys`](crate::keys)
Key(crate::keys::KeyError),
/// Error while extracting and manipulating policies
Policy(crate::descriptor::policy::PolicyError),
//InputIndexDoesntExist,
//MissingPublicKey,
//MissingDetails,
/// Invalid character found in the descriptor checksum
InvalidDescriptorCharacter(char),
//CantDeriveWithMiniscript,
/// BIP32 error
BIP32(bitcoin::util::bip32::Error),
/// Error during base58 decoding

View File

@@ -49,7 +49,7 @@ pub mod policy;
pub mod template;
pub use self::checksum::get_checksum;
use self::error::Error;
pub use self::error::Error as DescriptorError;
pub use self::policy::Policy;
use self::template::DescriptorTemplateOut;
use crate::keys::{KeyError, ToDescriptorKey};
@@ -72,14 +72,14 @@ pub trait ToWalletDescriptor {
fn to_wallet_descriptor(
self,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), KeyError>;
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>;
}
impl ToWalletDescriptor for &str {
fn to_wallet_descriptor(
self,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
let descriptor = if self.contains('#') {
let parts: Vec<&str> = self.splitn(2, '#').collect();
if !get_checksum(parts[0])
@@ -87,7 +87,7 @@ impl ToWalletDescriptor for &str {
.map(|computed| computed == parts[1])
.unwrap_or(false)
{
return Err(KeyError::InvalidChecksum);
return Err(DescriptorError::InvalidDescriptorChecksum);
}
parts[0]
@@ -103,7 +103,7 @@ impl ToWalletDescriptor for &String {
fn to_wallet_descriptor(
self,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
self.as_str().to_wallet_descriptor(network)
}
}
@@ -112,7 +112,7 @@ impl ToWalletDescriptor for ExtendedDescriptor {
fn to_wallet_descriptor(
self,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
(self, KeyMap::default()).to_wallet_descriptor(network)
}
}
@@ -121,7 +121,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
fn to_wallet_descriptor(
self,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
use crate::keys::DescriptorKey;
let secp = Secp256k1::new();
@@ -140,7 +140,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
if networks.contains(&network) {
Ok(pk)
} else {
Err(KeyError::InvalidNetwork)
Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
};
@@ -155,7 +155,7 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
fn to_wallet_descriptor(
self,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
let valid_networks = &self.2;
let fix_key = |pk: &DescriptorPublicKey| {
@@ -177,7 +177,7 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
Ok(pk)
} else {
Err(KeyError::InvalidNetwork)
Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
};
@@ -188,6 +188,22 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
}
}
#[doc(hidden)]
/// Used internally mainly by the `descriptor!()` and `fragment!()` macros
pub trait CheckMiniscript<Ctx: miniscript::ScriptContext> {
fn check_minsicript(&self) -> Result<(), miniscript::Error>;
}
impl<Ctx: miniscript::ScriptContext, Pk: miniscript::MiniscriptKey> CheckMiniscript<Ctx>
for miniscript::Miniscript<Pk, Ctx>
{
fn check_minsicript(&self) -> Result<(), miniscript::Error> {
Ctx::check_global_validity(self)?;
Ok(())
}
}
/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`]
pub trait ExtractPolicy {
/// Extract the spending [`policy`]
@@ -195,7 +211,7 @@ pub trait ExtractPolicy {
&self,
signers: &SignersContainer,
secp: &SecpCtx,
) -> Result<Option<Policy>, Error>;
) -> Result<Option<Policy>, DescriptorError>;
}
pub(crate) trait XKeyUtils {
@@ -235,8 +251,8 @@ impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
pub(crate) trait DescriptorMeta: Sized {
fn is_witness(&self) -> bool;
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, Error>;
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, Error>;
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>;
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
fn is_fixed(&self) -> bool;
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self>;
fn derive_from_psbt_input(
@@ -297,11 +313,11 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
}
}
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, Error> {
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> {
let translate_key = |key: &DescriptorPublicKey,
index: u32,
paths: &mut HDKeyPaths|
-> Result<DummyKey, Error> {
-> Result<DummyKey, DescriptorError> {
match key {
DescriptorPublicKey::SinglePub(_) => {}
DescriptorPublicKey::XPub(xpub) => {
@@ -344,10 +360,10 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
Ok(answer_pk)
}
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, Error> {
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
let get_key = |key: &DescriptorPublicKey,
keys: &mut Vec<DescriptorXKey<ExtendedPubKey>>|
-> Result<DummyKey, Error> {
-> Result<DummyKey, DescriptorError> {
if let DescriptorPublicKey::XPub(xpub) = key {
keys.push(xpub.clone())
}
@@ -369,7 +385,10 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
}
fn is_fixed(&self) -> bool {
fn check_key(key: &DescriptorPublicKey, flag: &mut bool) -> Result<DummyKey, Error> {
fn check_key(
key: &DescriptorPublicKey,
flag: &mut bool,
) -> Result<DummyKey, DescriptorError> {
match key {
DescriptorPublicKey::SinglePub(_) => {}
DescriptorPublicKey::XPub(xpub) => {
@@ -398,7 +417,7 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
let try_key = |key: &DescriptorPublicKey,
index: &HashMap<Fingerprint, DerivationPath>,
found_path: &mut Option<ChildNumber>|
-> Result<DummyKey, Error> {
-> Result<DummyKey, DescriptorError> {
if found_path.is_some() {
// already found a matching path, we are done
return Ok(DummyKey::default());
@@ -432,7 +451,7 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
Some(path) if !xpub.is_wildcard && path.is_empty() => {
*found_path = Some(ChildNumber::Normal { index: 0 })
}
Some(_) => return Err(Error::InvalidHDKeyPath),
Some(_) => return Err(DescriptorError::InvalidHDKeyPath),
_ => {}
}
}
@@ -713,11 +732,17 @@ mod test {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.to_wallet_descriptor(Network::Testnet);
assert!(matches!(desc.err(), Some(KeyError::InvalidChecksum)));
assert!(matches!(
desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.to_wallet_descriptor(Network::Testnet);
assert!(matches!(desc.err(), Some(KeyError::InvalidChecksum)));
assert!(matches!(
desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
}
// test ToWalletDescriptor trait from &str with keys from right and wrong network
@@ -749,11 +774,17 @@ mod test {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
.to_wallet_descriptor(Network::Bitcoin);
assert!(matches!(desc.err(), Some(KeyError::InvalidNetwork)));
assert!(matches!(
desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork))
));
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
.to_wallet_descriptor(Network::Bitcoin);
assert!(matches!(desc.err(), Some(KeyError::InvalidNetwork)));
assert!(matches!(
desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork))
));
}
// test ToWalletDescriptor trait from the output of the descriptor!() macro

View File

@@ -165,11 +165,13 @@ pub enum SatisfiableItem {
impl SatisfiableItem {
/// Returns whether the [`SatisfiableItem`] is a leaf item
pub fn is_leaf(&self) -> bool {
!matches!(self,
SatisfiableItem::Thresh {
items: _,
threshold: _,
})
!matches!(
self,
SatisfiableItem::Thresh {
items: _,
threshold: _,
}
)
}
/// Returns a unique id for the [`SatisfiableItem`]
@@ -1143,11 +1145,12 @@ mod test {
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);
let sequence = 50;
#[rustfmt::skip]
let desc = descriptor!(wsh(thresh(
2,
pk(prvkey0),
s: pk(pubkey1),
s: d: v: older(sequence)
s:pk(pubkey1),
s:d:v:older(sequence)
)))
.unwrap();

View File

@@ -33,7 +33,8 @@ use bitcoin::Network;
use miniscript::{Legacy, Segwitv0};
use super::{ExtendedDescriptor, KeyMap, ToWalletDescriptor};
use crate::keys::{DerivableKey, KeyError, ToDescriptorKey, ValidNetworks};
use crate::descriptor::DescriptorError;
use crate::keys::{DerivableKey, ToDescriptorKey, ValidNetworks};
use crate::{descriptor, KeychainKind};
/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
@@ -47,6 +48,7 @@ pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
/// ## Example
///
/// ```
/// use bdk::descriptor::error::Error as DescriptorError;
/// use bdk::keys::{KeyError, ToDescriptorKey};
/// use bdk::miniscript::Legacy;
/// use bdk::template::{DescriptorTemplate, DescriptorTemplateOut};
@@ -54,14 +56,14 @@ pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
/// struct MyP2PKH<K: ToDescriptorKey<Legacy>>(K);
///
/// impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for MyP2PKH<K> {
/// fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
/// fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
/// Ok(bdk::descriptor!(pkh(self.0))?)
/// }
/// }
/// ```
pub trait DescriptorTemplate {
/// Build the complete descriptor
fn build(self) -> Result<DescriptorTemplateOut, KeyError>;
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError>;
}
/// Turns a [`DescriptorTemplate`] into a valid wallet descriptor by calling its
@@ -70,7 +72,7 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
fn to_wallet_descriptor(
self,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
Ok(self.build()?.to_wallet_descriptor(network)?)
}
}
@@ -81,13 +83,13 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
///
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet};
/// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2PKH;
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// P2PKH(key),
/// None,
/// Network::Testnet,
@@ -103,7 +105,7 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
pub struct P2PKH<K: ToDescriptorKey<Legacy>>(pub K);
impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(descriptor!(pkh(self.0))?)
}
}
@@ -114,13 +116,13 @@ impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
///
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet};
/// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2WPKH_P2SH;
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// P2WPKH_P2SH(key),
/// None,
/// Network::Testnet,
@@ -137,7 +139,7 @@ impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
pub struct P2WPKH_P2SH<K: ToDescriptorKey<Segwitv0>>(pub K);
impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(descriptor!(sh(wpkh(self.0)))?)
}
}
@@ -148,13 +150,13 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
///
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet};
/// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2WPKH;
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// P2WPKH(key),
/// None,
/// Network::Testnet,
@@ -170,7 +172,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
pub struct P2WPKH<K: ToDescriptorKey<Segwitv0>>(pub K);
impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(descriptor!(wpkh(self.0))?)
}
}
@@ -186,12 +188,12 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP44;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// BIP44(key.clone(), KeychainKind::External),
/// Some(BIP44(key, KeychainKind::Internal)),
/// Network::Testnet,
@@ -205,7 +207,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2PKH(legacy::make_bipxx_private(44, self.0, self.1)?).build()?)
}
}
@@ -224,13 +226,13 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP44Public;
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// BIP44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP44Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
@@ -244,7 +246,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
pub struct BIP44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2PKH(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build()?)
}
}
@@ -260,12 +262,12 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP49;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// BIP49(key.clone(), KeychainKind::External),
/// Some(BIP49(key, KeychainKind::Internal)),
/// Network::Testnet,
@@ -279,7 +281,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_private(49, self.0, self.1)?).build()?)
}
}
@@ -298,13 +300,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP49Public;
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// BIP49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP49Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
@@ -318,7 +320,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
pub struct BIP49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build()?)
}
}
@@ -334,12 +336,12 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP84;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// BIP84(key.clone(), KeychainKind::External),
/// Some(BIP84(key, KeychainKind::Internal)),
/// Network::Testnet,
@@ -353,7 +355,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH(segwit_v0::make_bipxx_private(84, self.0, self.1)?).build()?)
}
}
@@ -372,13 +374,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP84Public;
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
/// let wallet = Wallet::new_offline(
/// BIP84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP84Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
@@ -392,7 +394,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
pub struct BIP84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84Public<K> {
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build()?)
}
}
@@ -406,7 +408,7 @@ macro_rules! expand_make_bipxx {
bip: u32,
key: K,
keychain: KeychainKind,
) -> Result<impl ToDescriptorKey<$ctx>, KeyError> {
) -> Result<impl ToDescriptorKey<$ctx>, DescriptorError> {
let mut derivation_path = Vec::with_capacity(4);
derivation_path.push(bip32::ChildNumber::from_hardened_idx(bip)?);
derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
@@ -430,7 +432,7 @@ macro_rules! expand_make_bipxx {
key: K,
parent_fingerprint: bip32::Fingerprint,
keychain: KeychainKind,
) -> Result<impl ToDescriptorKey<$ctx>, KeyError> {
) -> Result<impl ToDescriptorKey<$ctx>, DescriptorError> {
let derivation_path: bip32::DerivationPath = match keychain {
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(),
@@ -456,8 +458,8 @@ mod test {
// test existing descriptor templates, make sure they are expanded to the right descriptors
use super::*;
use crate::descriptor::DescriptorMeta;
use crate::keys::{KeyError, ValidNetworks};
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks;
use bitcoin::hashes::core::str::FromStr;
use bitcoin::network::constants::Network::Regtest;
use bitcoin::secp256k1::Secp256k1;
@@ -467,7 +469,7 @@ mod test {
// verify template descriptor generates expected address(es)
fn check(
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError>,
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
is_witness: bool,
is_fixed: bool,
expected: &[&str],

View File

@@ -47,7 +47,12 @@ pub enum Error {
/// Output created is under the dust limit, 546 satoshis
OutputBelowDustLimit(usize),
/// Wallet's UTXO set is not enough to cover recipient's requested plus fee
InsufficientFunds,
InsufficientFunds {
/// Sats needed for some transaction
needed: u64,
/// Sats available for spending
available: u64,
},
/// Branch and bound coin selection possible attempts with sufficiently big UTXO set could grow
/// exponentially, thus a limit is set, and when hit, this error is thrown
BnBTotalTriesExceeded,
@@ -89,11 +94,6 @@ pub enum Error {
/// Signing error
Signer(crate::wallet::signer::SignerError),
// Blockchain interface errors
/// Thrown when trying to call a method that requires a network connection, [`Wallet::sync`](crate::Wallet::sync) and [`Wallet::broadcast`](crate::Wallet::broadcast)
/// This error is thrown when creating the Client for the first time, while recovery attempts are tried
/// during the sync
OfflineClient,
/// Progress value must be between `0.0` (included) and `100.0` (included)
InvalidProgressValue(f32),
/// Progress update error (maybe the channel has been closed)
@@ -198,7 +198,7 @@ impl From<crate::blockchain::compact_filters::CompactFiltersError> for Error {
fn from(other: crate::blockchain::compact_filters::CompactFiltersError) -> Self {
match other {
crate::blockchain::compact_filters::CompactFiltersError::Global(e) => *e,
err @ _ => Error::CompactFilters(err),
err => Error::CompactFilters(err),
}
}
}

View File

@@ -43,6 +43,7 @@ use miniscript::descriptor::{DescriptorXKey, KeyMap};
pub use miniscript::ScriptContext;
use miniscript::{Miniscript, Terminal};
use crate::descriptor::{CheckMiniscript, DescriptorError};
use crate::wallet::utils::SecpCtx;
#[cfg(feature = "keys-bip39")]
@@ -420,7 +421,7 @@ where
/// [`ToDescriptorKey`]: the generated keys can be directly used in descriptors if `Self` is also
/// [`ToDescriptorKey`].
pub trait GeneratableKey<Ctx: ScriptContext>: Sized {
/// Type specifying the amount of entropy required e.g. [u8;32]
/// Type specifying the amount of entropy required e.g. `[u8;32]`
type Entropy: AsMut<[u8]> + Default;
/// Extra options required by the `generate_with_entropy`
@@ -572,14 +573,13 @@ fn expand_multi_keys<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
pub fn make_pk<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
descriptor_key: Pk,
secp: &SecpCtx,
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
let (key, key_map, valid_networks) = descriptor_key.to_descriptor_key()?.extract(secp)?;
let minisc = Miniscript::from_ast(Terminal::PkK(key))?;
Ok((
Miniscript::from_ast(Terminal::PkK(key))?,
key_map,
valid_networks,
))
minisc.check_minsicript()?;
Ok((minisc, key_map, valid_networks))
}
// Used internally by `bdk::fragment!` to build `multi()` fragments
@@ -588,14 +588,13 @@ pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
thresh: usize,
pks: Vec<Pk>,
secp: &SecpCtx,
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
let minisc = Miniscript::from_ast(Terminal::Multi(thresh, pks))?;
Ok((
Miniscript::from_ast(Terminal::Multi(thresh, pks))?,
key_map,
valid_networks,
))
minisc.check_minsicript()?;
Ok((minisc, key_map, valid_networks))
}
// Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments
@@ -610,11 +609,14 @@ pub fn make_sortedmulti_inner<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
KeyMap,
ValidNetworks,
),
KeyError,
DescriptorError,
> {
let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
let minisc = SortedMultiVec::new(thresh, pks)?;
Ok((SortedMultiVec::new(thresh, pks)?, key_map, valid_networks))
// TODO: should we apply the checks here as well?
Ok((minisc, key_map, valid_networks))
}
/// The "identity" conversion is used internally by some `bdk::fragment`s

View File

@@ -91,11 +91,11 @@
//!
//! ### Example
//! ```
//! use bdk::{Wallet, OfflineWallet};
//! use bdk::{Wallet};
//! use bdk::database::MemoryDatabase;
//!
//! fn main() -> Result<(), bdk::Error> {
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
//! let wallet = Wallet::new_offline(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
//! bitcoin::Network::Testnet,
@@ -155,13 +155,13 @@
//! ### Example
//! ```ignore
//! use base64::decode;
//! use bdk::{Wallet, OfflineWallet};
//! use bdk::{Wallet};
//! use bdk::database::MemoryDatabase;
//!
//! use bitcoin::consensus::deserialize;
//!
//! fn main() -> Result<(), bdk::Error> {
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
//! let wallet = Wallet::new_offline(
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
//! bitcoin::Network::Testnet,
@@ -191,7 +191,6 @@
//!
//! * `all-keys`: all features for working with bitcoin keys
//! * `async-interface`: async functions in bdk traits
//! * `cli-utils`: utilities for creating a command line interface wallet
//! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys
//!
//! ## Internal features
@@ -233,9 +232,6 @@ pub extern crate reqwest;
#[cfg(feature = "key-value-db")]
pub extern crate sled;
#[cfg(feature = "cli-utils")]
pub mod cli;
#[allow(unused_imports)]
#[cfg(test)]
#[macro_use]
@@ -268,4 +264,4 @@ pub use types::*;
pub use wallet::address_validator;
pub use wallet::signer;
pub use wallet::tx_builder::TxBuilder;
pub use wallet::{OfflineWallet, Wallet};
pub use wallet::Wallet;

View File

@@ -66,7 +66,7 @@
//! }
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
//! let mut wallet: OfflineWallet<_> = 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));
//!
//! let address = wallet.get_new_address()?;

View File

@@ -71,9 +71,9 @@
//! })
//! .collect::<Vec<_>>();
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
//!
//! if (fee_amount + additional_fees).ceil() as u64 + amount_needed > selected_amount {
//! return Err(bdk::Error::InsufficientFunds);
//! let amount_needed_with_fees = (fee_amount + additional_fees).ceil() as u64 + amount_needed;
//! if amount_needed_with_fees > selected_amount {
//! return Err(bdk::Error::InsufficientFunds{ needed: amount_needed_with_fees, available: selected_amount });
//! }
//!
//! Ok(CoinSelectionResult {
@@ -84,7 +84,7 @@
//! }
//! }
//!
//! # let wallet: OfflineWallet<_> = Wallet::new_offline("", None, Network::Testnet, bdk::database::MemoryDatabase::default())?;
//! # let wallet = Wallet::new_offline("", None, Network::Testnet, bdk::database::MemoryDatabase::default())?;
//! // create wallet, sync, ...
//!
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
@@ -221,8 +221,12 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
)
.collect::<Vec<_>>();
if selected_amount < amount_needed + (fee_amount.ceil() as u64) {
return Err(Error::InsufficientFunds);
let amount_needed_with_fees = amount_needed + (fee_amount.ceil() as u64);
if selected_amount < amount_needed_with_fees {
return Err(Error::InsufficientFunds {
needed: amount_needed_with_fees,
available: selected_amount,
});
}
Ok(CoinSelectionResult {
@@ -321,7 +325,10 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb();
if curr_available_value + curr_value < actual_target {
return Err(Error::InsufficientFunds);
return Err(Error::InsufficientFunds {
needed: actual_target,
available: curr_available_value + curr_value,
});
}
Ok(self

View File

@@ -43,7 +43,7 @@
//! }"#;
//!
//! let import = WalletExport::from_str(import)?;
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
//! let wallet = Wallet::new_offline(
//! &import.descriptor(),
//! import.change_descriptor().as_ref(),
//! Network::Testnet,
@@ -58,7 +58,7 @@
//! # use bdk::database::*;
//! # use bdk::wallet::export::*;
//! # use bdk::*;
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
//! let wallet = Wallet::new_offline(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
//! Network::Testnet,
@@ -78,7 +78,6 @@ use serde::{Deserialize, Serialize};
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
use crate::blockchain::BlockchainMarker;
use crate::database::BatchDatabase;
use crate::wallet::Wallet;
@@ -120,7 +119,7 @@ impl WalletExport {
///
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
/// returned will be `0`.
pub fn export_wallet<B: BlockchainMarker, D: BatchDatabase>(
pub fn export_wallet<B, D: BatchDatabase>(
wallet: &Wallet<B, D>,
label: &str,
include_blockheight: bool,
@@ -208,7 +207,7 @@ mod test {
use super::*;
use crate::database::{memory::MemoryDatabase, BatchOperations};
use crate::types::TransactionDetails;
use crate::wallet::{OfflineWallet, Wallet};
use crate::wallet::Wallet;
fn get_test_db() -> MemoryDatabase {
let mut db = MemoryDatabase::new();
@@ -234,7 +233,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
descriptor,
Some(change_descriptor),
Network::Bitcoin,
@@ -258,7 +257,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let wallet: OfflineWallet<_> =
let wallet =
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
}
@@ -272,7 +271,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
descriptor,
Some(change_descriptor),
Network::Bitcoin,
@@ -295,7 +294,7 @@ mod test {
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
))";
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
descriptor,
Some(change_descriptor),
Network::Testnet,
@@ -315,7 +314,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
descriptor,
Some(change_descriptor),
Network::Bitcoin,

View File

@@ -59,9 +59,12 @@ pub use utils::IsDust;
use address_validator::AddressValidator;
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxBuilderContext};
use utils::{check_nlocktime, check_nsequence_rbf, descriptor_to_pk_ctx, After, Older, SecpCtx};
use utils::{
check_nlocktime, check_nsequence_rbf, descriptor_to_pk_ctx, After, Older, SecpCtx,
DUST_LIMIT_SATOSHI,
};
use crate::blockchain::{Blockchain, BlockchainMarker, OfflineBlockchain, Progress};
use crate::blockchain::{Blockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::descriptor::{
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
@@ -73,9 +76,6 @@ use crate::types::*;
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
/// Type alias for a [`Wallet`] that uses [`OfflineBlockchain`]
pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
/// A Bitcoin wallet
///
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
@@ -84,7 +84,7 @@ pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
/// [creating transactions](Wallet::create_tx), etc.
///
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
/// implements [`Blockchain`], or "offline" [`OfflineBlockchain`] is used. Offline wallets only expose
/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose
/// methods that don't need any interaction with the blockchain to work.
pub struct Wallet<B, D> {
descriptor: ExtendedDescriptor,
@@ -99,24 +99,38 @@ pub struct Wallet<B, D> {
current_height: Option<u32>,
client: Option<B>,
client: B,
database: RefCell<D>,
secp: SecpCtx,
}
// offline actions, always available
impl<B, D> Wallet<B, D>
impl<D> Wallet<(), D>
where
B: BlockchainMarker,
D: BatchDatabase,
{
/// Create a new "offline" wallet
pub fn new_offline<E: ToWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
database: D,
) -> Result<Self, Error> {
Self::_new(descriptor, change_descriptor, network, database, (), None)
}
}
impl<B, D> Wallet<B, D>
where
D: BatchDatabase,
{
fn _new<E: ToWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
mut database: D,
client: B,
current_height: Option<u32>,
) -> Result<Self, Error> {
let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?;
database.check_descriptor_checksum(
@@ -148,18 +162,20 @@ where
signers,
change_signers,
address_validators: Vec::new(),
network,
current_height: None,
client: None,
current_height,
client,
database: RefCell::new(database),
secp: Secp256k1::new(),
})
}
}
// offline actions, always available
impl<B, D> Wallet<B, D>
where
D: BatchDatabase,
{
/// Return a newly generated address using the external descriptor
pub fn get_new_address(&self) -> Result<Address, Error> {
let index = self.fetch_and_increment_index(KeychainKind::External)?;
@@ -241,7 +257,7 @@ where
/// # use bdk::*;
/// # use bdk::database::*;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// let (psbt, details) = wallet.create_tx(
/// TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
@@ -495,11 +511,15 @@ where
match change_output {
None if change_val.is_dust() => {
// single recipient, but the only output would be below dust limit
return Err(Error::InsufficientFunds); // TODO: or OutputBelowDustLimit?
// TODO: or OutputBelowDustLimit?
return Err(Error::InsufficientFunds {
needed: DUST_LIMIT_SATOSHI,
available: change_val,
});
}
Some(_) if change_val.is_dust() => {
// skip the change output because it's dust, this adds up to the fees
fee_amount += selected_amount - outgoing;
// skip the change output because it's dust -- just include it in the fee.
fee_amount += change_val;
}
Some(mut change_output) => {
change_output.value = change_val;
@@ -556,7 +576,7 @@ where
/// # use bdk::*;
/// # use bdk::database::*;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// let txid = Txid::from_str("faff0a466b70f5d5f92bd757a92c1371d4838bdd5bc53a06764e2488e51ce8f8").unwrap();
/// let (psbt, details) = wallet.bump_fee(
/// &txid,
@@ -773,7 +793,11 @@ where
}
Some(_) if change_val_after_add.is_dust() => {
// single_recipient but the only output would be below dust limit
return Err(Error::InsufficientFunds); // TODO: or OutputBelowDustLimit?
// TODO: or OutputBelowDustLimit?
return Err(Error::InsufficientFunds {
needed: DUST_LIMIT_SATOSHI,
available: change_val_after_add,
});
}
None => {
removed_updatable_output.value = change_val_after_add;
@@ -820,7 +844,7 @@ where
/// # use bdk::*;
/// # use bdk::database::*;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let (psbt, _) = wallet.create_tx(TxBuilder::new())?;
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
/// # Ok::<(), bdk::Error>(())
@@ -1319,12 +1343,15 @@ where
database: D,
client: B,
) -> Result<Self, Error> {
let mut wallet = Self::new_offline(descriptor, change_descriptor, network, database)?;
wallet.current_height = Some(maybe_await!(client.get_height())? as u32);
wallet.client = Some(client);
Ok(wallet)
let current_height = Some(maybe_await!(client.get_height())? as u32);
Self::_new(
descriptor,
change_descriptor,
network,
database,
client,
current_height,
)
}
/// Sync the internal database with the blockchain
@@ -1372,13 +1399,13 @@ where
// TODO: what if i generate an address first and cache some addresses?
// TODO: we should sync if generating an address triggers a new batch to be stored
if run_setup {
maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.setup(
maybe_await!(self.client.setup(
None,
self.database.borrow_mut().deref_mut(),
progress_update,
))
} else {
maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.sync(
maybe_await!(self.client.sync(
None,
self.database.borrow_mut().deref_mut(),
progress_update,
@@ -1387,8 +1414,8 @@ where
}
/// Return a reference to the internal blockchain client
pub fn client(&self) -> Option<&B> {
self.client.as_ref()
pub fn client(&self) -> &B {
&self.client
}
/// Get the Bitcoin network the wallet is using.
@@ -1399,11 +1426,7 @@ where
/// Broadcast a transaction to the network
#[maybe_async]
pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
maybe_await!(self
.client
.as_ref()
.ok_or(Error::OfflineClient)?
.broadcast(&tx))?;
maybe_await!(self.client.broadcast(&tx))?;
Ok(tx.txid())
}
@@ -1424,7 +1447,7 @@ mod test {
#[test]
fn test_cache_addresses_fixed() {
let db = MemoryDatabase::new();
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
None,
Network::Testnet,
@@ -1458,7 +1481,7 @@ mod test {
#[test]
fn test_cache_addresses() {
let db = MemoryDatabase::new();
let wallet: OfflineWallet<_> = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_new_address().unwrap().to_string(),
@@ -1486,7 +1509,7 @@ mod test {
#[test]
fn test_cache_addresses_refill() {
let db = MemoryDatabase::new();
let wallet: OfflineWallet<_> = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_new_address().unwrap().to_string(),
@@ -1533,12 +1556,12 @@ mod test {
pub(crate) fn get_funded_wallet(
descriptor: &str,
) -> (
OfflineWallet<MemoryDatabase>,
Wallet<(), MemoryDatabase>,
(String, Option<String>),
bitcoin::Txid,
) {
let descriptors = testutils!(@descriptors (descriptor));
let wallet: OfflineWallet<_> = Wallet::new_offline(
let wallet = Wallet::new_offline(
&descriptors.0,
None,
Network::Regtest,
@@ -1952,7 +1975,7 @@ mod test {
fn test_create_tx_skip_change_dust() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
let (psbt, details) = wallet
.create_tx(TxBuilder::with_recipients(vec![(
addr.script_pubkey(),
49_800,
@@ -1961,6 +1984,7 @@ mod test {
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
assert_eq!(details.fees, 200);
}
#[test]

View File

@@ -79,7 +79,7 @@
//! let custom_signer = CustomSigner::connect();
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
//! let mut wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_signer(
//! KeychainKind::External,
//! Fingerprint::from_str("e30f11b8").unwrap().into(),

View File

@@ -29,7 +29,7 @@ use miniscript::descriptor::DescriptorPublicKeyCtx;
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
// De-facto standard "dust limit" (even though it should change based on the output type)
const DUST_LIMIT_SATOSHI: u64 = 546;
pub const DUST_LIMIT_SATOSHI: u64 = 546;
// MSB of the nSequence. If set there's no consensus-constraint, so it must be disabled when
// spending using CSV in order to enforce CSV rules

View File

@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk-testutils-macros"
description = "Supporting testing macros for `bdk`"
keywords = ["bdk"]
license-file = "../LICENSE"
license = "MIT"
[lib]
proc-macro = true

View File

@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk-testutils"
description = "Supporting testing utilities for `bdk`"
keywords = ["bdk"]
license-file = "../LICENSE"
license = "MIT"
[lib]
name = "testutils"