Compare commits

...

36 Commits

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

View File

@@ -10,8 +10,8 @@ jobs:
strategy:
matrix:
rust:
- 1.50.0 # STABLE
- 1.45.0 # MSRV
- 1.51.0 # STABLE
- 1.46.0 # MSRV
features:
- default
- minimal
@@ -98,7 +98,7 @@ jobs:
- name: Install rustup
run: curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Set default toolchain
run: $HOME/.cargo/bin/rustup default 1.50.0 # STABLE
run: $HOME/.cargo/bin/rustup default 1.51.0 # STABLE
- name: Set profile
run: $HOME/.cargo/bin/rustup set profile minimal
- name: Update toolchain
@@ -131,7 +131,7 @@ jobs:
- run: sudo apt-get update || exit 1
- run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
- name: Set default toolchain
run: rustup default 1.50.0 # STABLE
run: rustup default 1.51.0 # STABLE
- name: Set profile
run: rustup set profile minimal
- name: Add target wasm32
@@ -148,7 +148,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Set default toolchain
run: rustup default 1.50.0 # STABLE
run: rustup default 1.51.0 # STABLE
- name: Set profile
run: rustup set profile minimal
- name: Add clippy

View File

@@ -17,17 +17,14 @@ jobs:
~/.cargo/git
target
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- name: Set default toolchain
run: rustup default nightly-2021-03-23
- name: Set profile
run: rustup set profile minimal
- name: Update toolchain
run: rustup update
- name: Build docs
uses: actions-rs/cargo@v1
with:
command: rustdoc
args: --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
run: cargo rustdoc --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,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Misc
#### Changed
- New minimum supported rust version is 1.46.0
- Changed `AnyBlockchainConfig` to use serde tagged representation.
### Descriptor
#### Added
- Added ability to analyze a `PSBT` to check which and how many signatures are already available
### Wallet
#### Changed
- `get_new_address()` refactored to `get_address(AddressIndex::New)` to support different `get_address()` index selection strategies
#### Added
- Added `get_address(AddressIndex::LastUnused)` which returns the last derived address if it has not been used or if used in a received transaction returns a new address
- Added `get_address(AddressIndex::Peek(u32))` which returns a derived address for a specified descriptor index but does not change the current index
- Added `get_address(AddressIndex::Reset(u32))` which returns a derived address for a specified descriptor index and resets current index to the given value
- Added `get_psbt_input` to create the corresponding psbt input for a local utxo.
#### Fixed
- Fixed `coin_select` calculation for UTXOs where `value < fee` that caused over-/underflow errors.
## [v0.5.1] - [v0.5.0]
### Misc

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk"
version = "0.5.2-dev"
version = "0.6.0"
edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org"
@@ -35,8 +35,6 @@ tiny-bip39 = { version = "^0.8", optional = true }
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = ["rt"] }
# pin hyper version to prevent update to socket2 0.4.0 which isn't supported for MSRV 1.45.0
hyper = { version = "=0.14.4" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
async-trait = "0.1"
@@ -62,7 +60,7 @@ test-md-docs = ["electrum"]
[dev-dependencies]
bdk-testutils = "0.4"
bdk-testutils-macros = "0.4"
bdk-testutils-macros = "0.5"
serial_test = "0.4"
lazy_static = "1.4"
env_logger = "0.7"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -873,13 +873,13 @@ pub enum KeyError {
Message(String),
/// BIP32 error
BIP32(bitcoin::util::bip32::Error),
Bip32(bitcoin::util::bip32::Error),
/// Miniscript error
Miniscript(miniscript::Error),
}
impl_error!(miniscript::Error, Miniscript, KeyError);
impl_error!(bitcoin::util::bip32::Error, BIP32, KeyError);
impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError);
impl std::fmt::Display for KeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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