Skip to content

Commit 4f05441

Browse files
keychain::ChangeSet includes the descriptor
- The KeychainTxOutIndex's internal SpkIterator now uses DescriptorId instead of K. The DescriptorId -> K translation is made at the KeychainTxOutIndex level. - The keychain::Changeset is now a struct, which includes a map for last revealed indexes, and one for newly added keychains and their descriptor. API changes in bdk: - Wallet::keychains returns a `impl Iterator` instead of `BTreeMap` - Wallet::load doesn't take descriptors anymore, since they're stored in the db - Wallet::new_or_load checks if the loaded descriptor from db is the same as the provided one API changes in bdk_chain: - `ChangeSet` is now a struct, which includes a map for last revealed indexes, and one for keychains and descriptors. - `KeychainTxOutIndex::inner` returns a `SpkIterator<(DescriptorId, u32)>` - `KeychainTxOutIndex::outpoints` returns a `impl Iterator` instead of `&BTreeSet` - `KeychainTxOutIndex::keychains` returns a `impl Iterator` instead of `&BTreeMap` - `KeychainTxOutIndex::txouts` doesn't return a ExactSizeIterator anymore - `KeychainTxOutIndex::unbounded_spk_iter` returns an `Option` - `KeychainTxOutIndex::next_index` returns an `Option` - `KeychainTxOutIndex::last_revealed_indices` returns a `BTreeMap` instead of `&BTreeMap` - `KeychainTxOutIndex::reveal_to_target` returns an `Option` - `KeychainTxOutIndex::reveal_next_spk` returns an `Option` - `KeychainTxOutIndex::next_unused_spk` returns an `Option` - `KeychainTxOutIndex::add_keychain` has been renamed to `KeychainTxOutIndex::insert_descriptor`, and now it returns a ChangeSet
1 parent 8ff99f2 commit 4f05441

File tree

15 files changed

+969
-422
lines changed

15 files changed

+969
-422
lines changed

crates/bdk/src/wallet/mod.rs

Lines changed: 136 additions & 68 deletions
Large diffs are not rendered by default.

crates/bdk/tests/wallet.rs

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::str::FromStr;
22

33
use assert_matches::assert_matches;
4-
use bdk::descriptor::calc_checksum;
4+
use bdk::descriptor::{calc_checksum, IntoWalletDescriptor};
55
use bdk::psbt::PsbtUtils;
66
use bdk::signer::{SignOptions, SignerError};
77
use bdk::wallet::coin_selection::{self, LargestFirstCoinSelection};
@@ -10,9 +10,11 @@ use bdk::wallet::tx_builder::AddForeignUtxoError;
1010
use bdk::wallet::NewError;
1111
use bdk::wallet::{AddressInfo, Balance, Wallet};
1212
use bdk::KeychainKind;
13+
use bdk_chain::collections::BTreeMap;
1314
use bdk_chain::COINBASE_MATURITY;
1415
use bdk_chain::{BlockId, ConfirmationTime};
1516
use bitcoin::hashes::Hash;
17+
use bitcoin::key::Secp256k1;
1618
use bitcoin::psbt;
1719
use bitcoin::script::PushBytesBuf;
1820
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
@@ -84,14 +86,24 @@ fn load_recovers_wallet() {
8486
// recover wallet
8587
{
8688
let db = bdk_file_store::Store::open(DB_MAGIC, &file_path).expect("must recover db");
87-
let wallet =
88-
Wallet::load(get_test_tr_single_sig_xprv(), None, db).expect("must recover wallet");
89+
let wallet = Wallet::load(db).expect("must recover wallet");
8990
assert_eq!(wallet.network(), Network::Testnet);
90-
assert_eq!(wallet.spk_index().keychains(), wallet_spk_index.keychains());
91+
assert_eq!(
92+
wallet.spk_index().keychains().collect::<Vec<_>>(),
93+
wallet_spk_index.keychains().collect::<Vec<_>>()
94+
);
9195
assert_eq!(
9296
wallet.spk_index().last_revealed_indices(),
9397
wallet_spk_index.last_revealed_indices()
9498
);
99+
let secp = Secp256k1::new();
100+
assert_eq!(
101+
*wallet.get_descriptor_for_keychain(KeychainKind::External),
102+
get_test_tr_single_sig_xprv()
103+
.into_wallet_descriptor(&secp, wallet.network())
104+
.unwrap()
105+
.0
106+
);
95107
}
96108

97109
// `new` can only be called on empty db
@@ -108,12 +120,12 @@ fn new_or_load() {
108120
let file_path = temp_dir.path().join("store.db");
109121

110122
// init wallet when non-existent
111-
let wallet_keychains = {
123+
let wallet_keychains: BTreeMap<_, _> = {
112124
let db = bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path)
113125
.expect("must create db");
114126
let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
115127
.expect("must init wallet");
116-
wallet.keychains().clone()
128+
wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
117129
};
118130

119131
// wrong network
@@ -162,14 +174,60 @@ fn new_or_load() {
162174
);
163175
}
164176

177+
// wrong external descriptor
178+
{
179+
let exp_descriptor = get_test_tr_single_sig();
180+
let got_descriptor = get_test_wpkh()
181+
.into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
182+
.unwrap()
183+
.0;
184+
185+
let db =
186+
bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
187+
let err = Wallet::new_or_load(exp_descriptor, None, db, Network::Testnet)
188+
.expect_err("wrong external descriptor");
189+
assert!(
190+
matches!(
191+
err,
192+
bdk::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
193+
if got == &Some(got_descriptor) && keychain == KeychainKind::External
194+
),
195+
"err: {}",
196+
err,
197+
);
198+
}
199+
200+
// wrong internal descriptor
201+
{
202+
let exp_descriptor = Some(get_test_tr_single_sig());
203+
let got_descriptor = None;
204+
205+
let db =
206+
bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
207+
let err = Wallet::new_or_load(get_test_wpkh(), exp_descriptor, db, Network::Testnet)
208+
.expect_err("wrong internal descriptor");
209+
assert!(
210+
matches!(
211+
err,
212+
bdk::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
213+
if got == &got_descriptor && keychain == KeychainKind::Internal
214+
),
215+
"err: {}",
216+
err,
217+
);
218+
}
219+
165220
// all parameters match
166221
{
167222
let db =
168223
bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
169224
let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
170225
.expect("must recover wallet");
171226
assert_eq!(wallet.network(), Network::Testnet);
172-
assert_eq!(wallet.keychains(), &wallet_keychains);
227+
assert!(wallet
228+
.keychains()
229+
.map(|(k, v)| (*k, v.clone()))
230+
.eq(wallet_keychains));
173231
}
174232
}
175233

@@ -181,7 +239,6 @@ fn test_descriptor_checksum() {
181239

182240
let raw_descriptor = wallet
183241
.keychains()
184-
.iter()
185242
.next()
186243
.unwrap()
187244
.1

crates/chain/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ proptest = "1.2.0"
2727

2828
[features]
2929
default = ["std"]
30-
std = ["bitcoin/std", "miniscript/std"]
31-
serde = ["serde_crate", "bitcoin/serde"]
30+
std = ["bitcoin/std", "miniscript?/std"]
31+
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]

crates/chain/src/descriptor_ext.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1-
use crate::miniscript::{Descriptor, DescriptorPublicKey};
1+
use crate::{
2+
alloc::{string::ToString, vec::Vec},
3+
miniscript::{Descriptor, DescriptorPublicKey},
4+
};
5+
use bitcoin::hashes::{hash_newtype, sha256, Hash};
6+
7+
hash_newtype! {
8+
/// Represents the ID of a descriptor, defined as the sha256 hash of
9+
/// the descriptor string, checksum excluded.
10+
///
11+
/// This is useful for having a fixed-length unique representation of a descriptor,
12+
/// in particular, we use it to persist application state changes related to the
13+
/// descriptor without having to re-write the whole descriptor each time.
14+
///
15+
pub struct DescriptorId(pub sha256::Hash);
16+
}
217

318
/// A trait to extend the functionality of a miniscript descriptor.
419
pub trait DescriptorExt {
520
/// Returns the minimum value (in satoshis) at which an output is broadcastable.
621
/// Panics if the descriptor wildcard is hardened.
722
fn dust_value(&self) -> u64;
23+
24+
/// Returns the descriptor id, calculated as the sha256 of the descriptor, checksum not
25+
/// included.
26+
fn descriptor_id(&self) -> DescriptorId;
827
}
928

1029
impl DescriptorExt for Descriptor<DescriptorPublicKey> {
@@ -15,4 +34,11 @@ impl DescriptorExt for Descriptor<DescriptorPublicKey> {
1534
.dust_value()
1635
.to_sat()
1736
}
37+
38+
fn descriptor_id(&self) -> DescriptorId {
39+
let desc = self.to_string();
40+
let desc_without_checksum = desc.split('#').next().expect("Must be here");
41+
let descriptor_bytes = <Vec<u8>>::from(desc_without_checksum.as_bytes());
42+
DescriptorId(sha256::Hash::hash(&descriptor_bytes))
43+
}
1844
}

0 commit comments

Comments
 (0)