From 4fd539b6470f7f771e3b5e09e3287952fa7a1825 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Tue, 28 Nov 2023 18:08:49 +0100 Subject: [PATCH 1/4] feat(chain)!: `KeychainTxOutIndex` uses a universal lookahead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wallet is currently created without setting any lookahead value for the keychain. This implicitly makes it a lookahead of 0. As this is a high-level interface we should avoid footguns and aim for a reasonable default. Instead of simply patching it for wallet, we alter `KeychainTxOutIndex` to have a default lookahead value. Additionally, instead of a per-keychain lookahead, the constructor asks for a `lookahead` value. This avoids the footguns of having methods which allows the caller the decrease the `lookahead` (and therefore panicing). This also simplifies the API. Co-authored-by: Antoine Poisot Co-authored-by: 志宇 --- crates/chain/src/keychain/txout_index.rs | 100 ++++++------------ crates/chain/src/spk_iter.rs | 2 +- crates/chain/tests/test_indexed_tx_graph.rs | 11 +- .../chain/tests/test_keychain_txout_index.rs | 32 +++--- .../example_bitcoind_rpc_polling/src/main.rs | 14 +-- 5 files changed, 56 insertions(+), 103 deletions(-) diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index 2089673ba..ed307de77 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -5,12 +5,13 @@ use crate::{ spk_iter::BIP32_MAX_INDEX, SpkIterator, SpkTxOutIndex, }; -use alloc::vec::Vec; use bitcoin::{OutPoint, Script, TxOut}; use core::{fmt::Debug, ops::Deref}; use crate::Append; +const DEFAULT_LOOKAHEAD: u32 = 1_000; + /// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public /// [`Descriptor`]s. /// @@ -46,7 +47,7 @@ use crate::Append; /// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only(); /// # let (external_descriptor,_) = Descriptor::::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap(); /// # let (internal_descriptor,_) = Descriptor::::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap(); -/// # let descriptor_for_user_42 = external_descriptor.clone(); +/// # let (descriptor_for_user_42, _) = Descriptor::::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/2/*)").unwrap(); /// txout_index.add_keychain(MyKeychain::External, external_descriptor); /// txout_index.add_keychain(MyKeychain::Internal, internal_descriptor); /// txout_index.add_keychain(MyKeychain::MyAppUser { user_id: 42 }, descriptor_for_user_42); @@ -65,17 +66,12 @@ pub struct KeychainTxOutIndex { // last revealed indexes last_revealed: BTreeMap, // lookahead settings for each keychain - lookahead: BTreeMap, + lookahead: u32, } impl Default for KeychainTxOutIndex { fn default() -> Self { - Self { - inner: SpkTxOutIndex::default(), - keychains: BTreeMap::default(), - last_revealed: BTreeMap::default(), - lookahead: BTreeMap::default(), - } + Self::new(DEFAULT_LOOKAHEAD) } } @@ -118,6 +114,24 @@ impl Indexer for KeychainTxOutIndex { } } +impl KeychainTxOutIndex { + /// Construct a [`KeychainTxOutIndex`] with the given `lookahead`. + /// + /// The lookahead is the number of scripts to cache ahead of the last revealed script index. + /// This is useful to find outputs you own when processing block data that lie beyond the last + /// revealed index. In certain situations, such as when performing an initial scan of the + /// blockchain during wallet import, it may be uncertain or unknown what the last revealed index + /// is. + pub fn new(lookahead: u32) -> Self { + Self { + inner: SpkTxOutIndex::default(), + keychains: BTreeMap::new(), + last_revealed: BTreeMap::new(), + lookahead, + } + } +} + impl KeychainTxOutIndex { /// Return a reference to the internal [`SpkTxOutIndex`]. pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> { @@ -145,54 +159,22 @@ impl KeychainTxOutIndex { pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor) { let old_descriptor = &*self .keychains - .entry(keychain) + .entry(keychain.clone()) .or_insert_with(|| descriptor.clone()); assert_eq!( &descriptor, old_descriptor, "keychain already contains a different descriptor" ); + self.replenish_lookahead(&keychain, self.lookahead); } - /// Return the lookahead setting for each keychain. + /// Get the lookahead setting. /// - /// Refer to [`set_lookahead`] for a deeper explanation of the `lookahead`. + /// Refer to [`new`] for more information on the `lookahead`. /// - /// [`set_lookahead`]: Self::set_lookahead - pub fn lookaheads(&self) -> &BTreeMap { - &self.lookahead - } - - /// Convenience method to call [`set_lookahead`] for all keychains. - /// - /// [`set_lookahead`]: Self::set_lookahead - pub fn set_lookahead_for_all(&mut self, lookahead: u32) { - for keychain in &self.keychains.keys().cloned().collect::>() { - self.set_lookahead(keychain, lookahead); - } - } - - /// Set the lookahead count for `keychain`. - /// - /// The lookahead is the number of scripts to cache ahead of the last revealed script index. This - /// is useful to find outputs you own when processing block data that lie beyond the last revealed - /// index. In certain situations, such as when performing an initial scan of the blockchain during - /// wallet import, it may be uncertain or unknown what the last revealed index is. - /// - /// # Panics - /// - /// This will panic if the `keychain` does not exist. - pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) { - self.lookahead.insert(keychain.clone(), lookahead); - self.replenish_lookahead(keychain); - } - - /// Convenience method to call [`lookahead_to_target`] for multiple keychains. - /// - /// [`lookahead_to_target`]: Self::lookahead_to_target - pub fn lookahead_to_target_multi(&mut self, target_indexes: BTreeMap) { - for (keychain, target_index) in target_indexes { - self.lookahead_to_target(&keychain, target_index) - } + /// [`new`]: Self::new + pub fn lookahead(&self) -> u32 { + self.lookahead } /// Store lookahead scripts until `target_index`. @@ -201,22 +183,14 @@ impl KeychainTxOutIndex { pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) { let next_index = self.next_store_index(keychain); if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) { - let old_lookahead = self.lookahead.insert(keychain.clone(), temp_lookahead); - self.replenish_lookahead(keychain); - - // revert - match old_lookahead { - Some(lookahead) => self.lookahead.insert(keychain.clone(), lookahead), - None => self.lookahead.remove(keychain), - }; + self.replenish_lookahead(keychain, temp_lookahead); } } - fn replenish_lookahead(&mut self, keychain: &K) { + fn replenish_lookahead(&mut self, keychain: &K, lookahead: u32) { let descriptor = self.keychains.get(keychain).expect("keychain must exist"); let next_store_index = self.next_store_index(keychain); let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1); - let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v); for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead) @@ -388,12 +362,8 @@ impl KeychainTxOutIndex { let target_index = if has_wildcard { target_index } else { 0 }; let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1); - let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v); - debug_assert_eq!( - next_reveal_index + lookahead, - self.next_store_index(keychain) - ); + debug_assert!(next_reveal_index + self.lookahead >= self.next_store_index(keychain)); // if we need to reveal new indices, the latest revealed index goes here let mut reveal_to_index = None; @@ -401,12 +371,12 @@ impl KeychainTxOutIndex { // if the target is not yet revealed, but is already stored (due to lookahead), we need to // set the `reveal_to_index` as target here (as the `for` loop below only updates // `reveal_to_index` for indexes that are NOT stored) - if next_reveal_index <= target_index && target_index < next_reveal_index + lookahead { + if next_reveal_index <= target_index && target_index < next_reveal_index + self.lookahead { reveal_to_index = Some(target_index); } // we range over indexes that are not stored - let range = next_reveal_index + lookahead..=target_index + lookahead; + let range = next_reveal_index + self.lookahead..=target_index + self.lookahead; for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) { let _inserted = self .inner diff --git a/crates/chain/src/spk_iter.rs b/crates/chain/src/spk_iter.rs index a80cd6082..a28944f1e 100644 --- a/crates/chain/src/spk_iter.rs +++ b/crates/chain/src/spk_iter.rs @@ -148,7 +148,7 @@ mod test { Descriptor, Descriptor, ) { - let mut txout_index = KeychainTxOutIndex::::default(); + let mut txout_index = KeychainTxOutIndex::::new(0); let secp = Secp256k1::signing_only(); let (external_descriptor,_) = Descriptor::::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap(); diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index ec250c95c..41b1d4d3e 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -27,9 +27,10 @@ fn insert_relevant_txs() { let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey(); let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey(); - let mut graph = IndexedTxGraph::>::default(); + let mut graph = IndexedTxGraph::>::new( + KeychainTxOutIndex::new(10), + ); graph.index.add_keychain((), descriptor); - graph.index.set_lookahead(&(), 10); let tx_a = Transaction { output: vec![ @@ -118,12 +119,12 @@ fn test_list_owned_txouts() { let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap(); let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap(); - let mut graph = - IndexedTxGraph::>::default(); + let mut graph = IndexedTxGraph::>::new( + KeychainTxOutIndex::new(10), + ); graph.index.add_keychain("keychain_1".into(), desc_1); graph.index.add_keychain("keychain_2".into(), desc_2); - graph.index.set_lookahead_for_all(10); // Get trusted and untrusted addresses diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index 161aad06d..e1f55dc31 100644 --- a/crates/chain/tests/test_keychain_txout_index.rs +++ b/crates/chain/tests/test_keychain_txout_index.rs @@ -18,12 +18,14 @@ enum TestKeychain { Internal, } -fn init_txout_index() -> ( +fn init_txout_index( + lookahead: u32, +) -> ( bdk_chain::keychain::KeychainTxOutIndex, Descriptor, Descriptor, ) { - let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::::default(); + let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::::new(lookahead); let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only(); let (external_descriptor,_) = Descriptor::::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap(); @@ -46,7 +48,7 @@ fn spk_at_index(descriptor: &Descriptor, index: u32) -> Scr fn test_set_all_derivation_indices() { use bdk_chain::indexed_tx_graph::Indexer; - let (mut txout_index, _, _) = init_txout_index(); + let (mut txout_index, _, _) = init_txout_index(0); let derive_to: BTreeMap<_, _> = [(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into(); assert_eq!( @@ -64,19 +66,10 @@ fn test_set_all_derivation_indices() { #[test] fn test_lookahead() { - let (mut txout_index, external_desc, internal_desc) = init_txout_index(); - - // ensure it does not break anything if lookahead is set multiple times - (0..=10).for_each(|lookahead| txout_index.set_lookahead(&TestKeychain::External, lookahead)); - (0..=20) - .filter(|v| v % 2 == 0) - .for_each(|lookahead| txout_index.set_lookahead(&TestKeychain::Internal, lookahead)); - - assert_eq!(txout_index.inner().all_spks().len(), 30); + let (mut txout_index, external_desc, internal_desc) = init_txout_index(10); // given: // - external lookahead set to 10 - // - internal lookahead set to 20 // when: // - set external derivation index to value higher than last, but within the lookahead value // expect: @@ -97,7 +90,7 @@ fn test_lookahead() { assert_eq!( txout_index.inner().all_spks().len(), 10 /* external lookahead */ + - 20 /* internal lookahead */ + + 10 /* internal lookahead */ + index as usize + 1 /* `derived` count */ ); assert_eq!( @@ -127,7 +120,7 @@ fn test_lookahead() { } // given: - // - internal lookahead is 20 + // - internal lookahead is 10 // - internal derivation index is `None` // when: // - derivation index is set ahead of current derivation index + lookahead @@ -148,7 +141,7 @@ fn test_lookahead() { assert_eq!( txout_index.inner().all_spks().len(), 10 /* external lookahead */ + - 20 /* internal lookahead */ + + 10 /* internal lookahead */ + 20 /* external stored index count */ + 25 /* internal stored index count */ ); @@ -226,8 +219,7 @@ fn test_lookahead() { // - last used index should change as expected #[test] fn test_scan_with_lookahead() { - let (mut txout_index, external_desc, _) = init_txout_index(); - txout_index.set_lookahead_for_all(10); + let (mut txout_index, external_desc, _) = init_txout_index(10); let spks: BTreeMap = [0, 10, 20, 30] .into_iter() @@ -281,7 +273,7 @@ fn test_scan_with_lookahead() { #[test] #[rustfmt::skip] fn test_wildcard_derivations() { - let (mut txout_index, external_desc, _) = init_txout_index(); + let (mut txout_index, external_desc, _) = init_txout_index(0); let external_spk_0 = external_desc.at_derivation_index(0).unwrap().script_pubkey(); let external_spk_16 = external_desc.at_derivation_index(16).unwrap().script_pubkey(); let external_spk_26 = external_desc.at_derivation_index(26).unwrap().script_pubkey(); @@ -339,7 +331,7 @@ fn test_wildcard_derivations() { #[test] fn test_non_wildcard_derivations() { - let mut txout_index = KeychainTxOutIndex::::default(); + let mut txout_index = KeychainTxOutIndex::::new(0); let secp = bitcoin::secp256k1::Secp256k1::signing_only(); let (no_wildcard_descriptor, _) = Descriptor::::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap(); diff --git a/example-crates/example_bitcoind_rpc_polling/src/main.rs b/example-crates/example_bitcoind_rpc_polling/src/main.rs index 35aa76907..93ef53f2b 100644 --- a/example-crates/example_bitcoind_rpc_polling/src/main.rs +++ b/example-crates/example_bitcoind_rpc_polling/src/main.rs @@ -64,9 +64,6 @@ struct RpcArgs { /// Starting block height to fallback to if no point of agreement if found #[clap(env = "FALLBACK_HEIGHT", long, default_value = "0")] fallback_height: u32, - /// The unused-scripts lookahead will be kept at this size - #[clap(long, default_value = "10")] - lookahead: u32, } impl From for Auth { @@ -161,13 +158,9 @@ fn main() -> anyhow::Result<()> { match rpc_cmd { RpcCommands::Sync { rpc_args } => { let RpcArgs { - fallback_height, - lookahead, - .. + fallback_height, .. } = rpc_args; - graph.lock().unwrap().index.set_lookahead_for_all(lookahead); - let chain_tip = chain.lock().unwrap().tip(); let rpc_client = rpc_args.new_client()?; let mut emitter = Emitter::new(&rpc_client, chain_tip, fallback_height); @@ -233,13 +226,10 @@ fn main() -> anyhow::Result<()> { } RpcCommands::Live { rpc_args } => { let RpcArgs { - fallback_height, - lookahead, - .. + fallback_height, .. } = rpc_args; let sigterm_flag = start_ctrlc_handler(); - graph.lock().unwrap().index.set_lookahead_for_all(lookahead); let last_cp = chain.lock().unwrap().tip(); println!( From bc796f412acdc3d2cd96f7aec0f24fa47c1fe889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Thu, 28 Dec 2023 12:49:04 +0800 Subject: [PATCH 2/4] fix(example): bitcoind_rpc_polling now initializes local_chain properly Previously, the genesis block is not initialized properly. Thank you @notmandatory for identifying this bug. --- .../example_bitcoind_rpc_polling/src/main.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/example-crates/example_bitcoind_rpc_polling/src/main.rs b/example-crates/example_bitcoind_rpc_polling/src/main.rs index 93ef53f2b..449242e41 100644 --- a/example-crates/example_bitcoind_rpc_polling/src/main.rs +++ b/example-crates/example_bitcoind_rpc_polling/src/main.rs @@ -12,7 +12,7 @@ use bdk_bitcoind_rpc::{ Emitter, }; use bdk_chain::{ - bitcoin::{Block, Transaction}, + bitcoin::{constants::genesis_block, Block, Transaction}, indexed_tx_graph, keychain, local_chain::{self, CheckPoint, LocalChain}, ConfirmationTimeHeightAnchor, IndexedTxGraph, @@ -117,10 +117,11 @@ fn main() -> anyhow::Result<()> { "[{:>10}s] loaded initial changeset from db", start.elapsed().as_secs_f32() ); + let (init_chain_changeset, init_graph_changeset) = init_changeset; let graph = Mutex::new({ let mut graph = IndexedTxGraph::new(index); - graph.apply_changeset(init_changeset.1); + graph.apply_changeset(init_graph_changeset); graph }); println!( @@ -128,7 +129,16 @@ fn main() -> anyhow::Result<()> { start.elapsed().as_secs_f32() ); - let chain = Mutex::new(LocalChain::from_changeset(init_changeset.0)?); + let chain = Mutex::new(if init_chain_changeset.is_empty() { + let genesis_hash = genesis_block(args.network).block_hash(); + let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); + let mut db = db.lock().unwrap(); + db.stage((chain_changeset, Default::default())); + db.commit()?; + chain + } else { + LocalChain::from_changeset(init_chain_changeset)? + }); println!( "[{:>10}s] loaded local chain from changeset", start.elapsed().as_secs_f32() From 78dff4addc775945b909f6d1cae9c09840ad2e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 26 Dec 2023 19:28:17 +0800 Subject: [PATCH 3/4] feat(wallet)!: use builder pattern to initialize wallet Wallet error types `NewError` and `NewOrLoadError` have been renamed to `InitError` and `InitOrLoadError`. --- crates/bdk/README.md | 5 +- crates/bdk/examples/compiler.rs | 4 +- crates/bdk/src/descriptor/template.rs | 88 +++---- crates/bdk/src/wallet/builder.rs | 98 ++++++++ crates/bdk/src/wallet/export.rs | 27 +- crates/bdk/src/wallet/hardwaresigner.rs | 9 +- crates/bdk/src/wallet/mod.rs | 238 ++++++------------ crates/bdk/src/wallet/signer.rs | 4 +- crates/bdk/tests/common.rs | 7 +- crates/bdk/tests/wallet.rs | 108 +++++--- example-crates/wallet_electrum/src/main.rs | 10 +- .../wallet_esplora_async/src/main.rs | 10 +- .../wallet_esplora_blocking/src/main.rs | 10 +- 13 files changed, 337 insertions(+), 281 deletions(-) create mode 100644 crates/bdk/src/wallet/builder.rs diff --git a/crates/bdk/README.md b/crates/bdk/README.md index fa23f20a2..bfd987706 100644 --- a/crates/bdk/README.md +++ b/crates/bdk/README.md @@ -72,7 +72,10 @@ fn main() { let db = (); let descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)"; - let mut wallet = Wallet::new(descriptor, None, db, Network::Testnet).expect("should create"); + let mut wallet = Wallet::builder(descriptor) + .with_network(Network::Testnet) + .init(db) + .expect("should create"); // get a new address (this increments revealed derivation index) println!("revealed address: {}", wallet.get_address(AddressIndex::New)); diff --git a/crates/bdk/examples/compiler.rs b/crates/bdk/examples/compiler.rs index e22ccadc7..a01291065 100644 --- a/crates/bdk/examples/compiler.rs +++ b/crates/bdk/examples/compiler.rs @@ -47,7 +47,9 @@ fn main() -> Result<(), Box> { println!("Compiled into following Descriptor: \n{}", descriptor); // Create a new wallet from this descriptor - let mut wallet = Wallet::new_no_persist(&format!("{}", descriptor), None, Network::Regtest)?; + let mut wallet = Wallet::builder(&format!("{}", descriptor)) + .with_network(Network::Regtest) + .init_without_persistence()?; println!( "First derived address from the descriptor: \n{}", diff --git a/crates/bdk/src/descriptor/template.rs b/crates/bdk/src/descriptor/template.rs index c5e8b31c5..19b55ca60 100644 --- a/crates/bdk/src/descriptor/template.rs +++ b/crates/bdk/src/descriptor/template.rs @@ -79,7 +79,9 @@ impl IntoWalletDescriptor for T { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?; +/// let mut wallet = Wallet::builder(P2Pkh(key)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!( /// wallet.get_address(New).to_string(), @@ -107,7 +109,9 @@ impl> DescriptorTemplate for P2Pkh { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?; +/// let mut wallet = Wallet::builder(P2Wpkh_P2Sh(key)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!( /// wallet.get_address(AddressIndex::New).to_string(), @@ -136,7 +140,9 @@ impl> DescriptorTemplate for P2Wpkh_P2Sh { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?; +/// let mut wallet = Wallet::builder(P2Wpkh(key)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!( /// wallet.get_address(New).to_string(), @@ -164,7 +170,9 @@ impl> DescriptorTemplate for P2Wpkh { /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let mut wallet = Wallet::new_no_persist(P2TR(key), None, Network::Testnet)?; +/// let mut wallet = Wallet::builder(P2TR(key)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!( /// wallet.get_address(New).to_string(), @@ -196,11 +204,10 @@ impl> DescriptorTemplate for P2TR { /// use bdk::template::Bip44; /// /// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip44(key.clone(), KeychainKind::External), -/// Some(Bip44(key, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip44(key.clone(), KeychainKind::External)) +/// .with_change_descriptor(Bip44(key, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt"); @@ -234,11 +241,10 @@ impl> DescriptorTemplate for Bip44 { /// /// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip44Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip44Public(key.clone(), fingerprint, KeychainKind::External)) +/// .with_change_descriptor(Bip44Public(key, fingerprint, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz"); @@ -271,11 +277,10 @@ impl> DescriptorTemplate for Bip44Public { /// use bdk::template::Bip49; /// /// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip49(key.clone(), KeychainKind::External), -/// Some(Bip49(key, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip49(key.clone(), KeychainKind::External)) +/// .with_change_descriptor(Bip49(key, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e"); @@ -309,11 +314,10 @@ impl> DescriptorTemplate for Bip49 { /// /// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip49Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip49Public(key.clone(), fingerprint, KeychainKind::External)) +/// .with_change_descriptor(Bip49Public(key, fingerprint, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q"); @@ -346,11 +350,10 @@ impl> DescriptorTemplate for Bip49Public { /// use bdk::template::Bip84; /// /// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip84(key.clone(), KeychainKind::External), -/// Some(Bip84(key, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip84(key.clone(), KeychainKind::External)) +/// .with_change_descriptor(Bip84(key, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr"); @@ -384,11 +387,10 @@ impl> DescriptorTemplate for Bip84 { /// /// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip84Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip84Public(key.clone(), fingerprint, KeychainKind::External)) +/// .with_change_descriptor(Bip84Public(key, fingerprint, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv"); @@ -421,11 +423,10 @@ impl> DescriptorTemplate for Bip84Public { /// use bdk::template::Bip86; /// /// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip86(key.clone(), KeychainKind::External), -/// Some(Bip86(key, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip86(key.clone(), KeychainKind::External)) +/// .with_change_descriptor(Bip86(key, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm"); @@ -459,11 +460,10 @@ impl> DescriptorTemplate for Bip86 { /// /// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::new_no_persist( -/// Bip86Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)), -/// Network::Testnet, -/// )?; +/// let mut wallet = Wallet::builder(Bip86Public(key.clone(), fingerprint, KeychainKind::External)) +/// .with_change_descriptor(Bip86Public(key, fingerprint, KeychainKind::Internal)) +/// .with_network(Network::Testnet) +/// .init_without_persistence()?; /// /// assert_eq!(wallet.get_address(New).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku"); diff --git a/crates/bdk/src/wallet/builder.rs b/crates/bdk/src/wallet/builder.rs new file mode 100644 index 000000000..8363212fc --- /dev/null +++ b/crates/bdk/src/wallet/builder.rs @@ -0,0 +1,98 @@ +use bdk_chain::PersistBackend; +use bitcoin::constants::genesis_block; +use bitcoin::{BlockHash, Network}; + +use crate::descriptor::{DescriptorError, IntoWalletDescriptor}; +use crate::wallet::{ChangeSet, Wallet}; + +use super::{InitError, InitOrLoadError}; + +/// Helper structure to initialize a fresh [`Wallet`] instance. +pub struct WalletBuilder { + pub(super) descriptor: E, + pub(super) change_descriptor: Option, + pub(super) network: Network, + pub(super) custom_genesis_hash: Option, +} + +impl WalletBuilder { + /// Start building a fresh [`Wallet`] instance. + pub(super) fn new(descriptor: E) -> Self { + Self { + descriptor, + change_descriptor: None, + network: Network::Bitcoin, + custom_genesis_hash: None, + } + } + + /// Set the internal (change) keychain. + /// + /// The internal keychain will be used to derive change addresses for change outputs. + /// + /// If no internal keychain is set, the wallet will use the external keychain for deriving + /// change addresses. + pub fn with_change_descriptor(mut self, change_descriptor: E) -> Self { + self.change_descriptor = Some(change_descriptor); + self + } + + /// Set the [`Network`] type for the wallet. + /// + /// This changes the format of the derived wallet, as well as the internal genesis block hash. + /// The internal genesis block hash can be overriden by [`with_genesis_hash`]. + /// + /// By default, [`Network::Bitcoin`] is used. + /// + /// [`with_genesis_hash`]: Self::with_genesis_hash + pub fn with_network(mut self, network: Network) -> Self { + self.network = network; + self + } + + /// Overrides the genesis hash implied by [`with_network`]. + /// + /// [`with_network`]: Self::with_network + pub fn with_genesis_hash(mut self, genesis_hash: BlockHash) -> Self { + self.custom_genesis_hash = Some(genesis_hash); + self + } + + pub(super) fn determine_genesis_hash(&self) -> BlockHash { + self.custom_genesis_hash + .unwrap_or_else(|| genesis_block(self.network).block_hash()) + } +} + +impl WalletBuilder { + /// Initializes a fresh wallet and persists it in `db`. + pub fn init(self, db: D) -> Result, InitError> + where + D: PersistBackend, + { + Wallet::init(self, db) + } + + /// Either loads [`Wallet`] from persistence, or initializes a fresh wallet if it does not + /// exist. + /// + /// This method will fail if the persistence is non-empty with parameters that are different to + /// those specified by [`WalletBuilder`]. + pub fn init_or_load( + self, + db: D, + ) -> Result, InitOrLoadError> + where + D: PersistBackend, + { + Wallet::init_or_load(self, db) + } + + /// Initializes a fresh wallet with no persistence. + pub fn init_without_persistence(self) -> Result, DescriptorError> { + Wallet::init(self, ()).map_err(|err| match err { + InitError::Descriptor(err) => err, + InitError::Write(_) => panic!("there is no db to write to"), + }) + } +} diff --git a/crates/bdk/src/wallet/export.rs b/crates/bdk/src/wallet/export.rs index f2d656891..673598020 100644 --- a/crates/bdk/src/wallet/export.rs +++ b/crates/bdk/src/wallet/export.rs @@ -29,11 +29,13 @@ //! }"#; //! //! let import = FullyNodedExport::from_str(import)?; -//! let wallet = Wallet::new_no_persist( -//! &import.descriptor(), -//! import.change_descriptor().as_ref(), -//! Network::Testnet, -//! )?; +//! let descriptor = import.descriptor(); +//! let change_descriptor = import.change_descriptor(); +//! let mut builder = Wallet::builder(&descriptor).with_network(Network::Testnet); +//! if let Some(change_descriptor) = &change_descriptor { +//! builder = builder.with_change_descriptor(change_descriptor); +//! } +//! let wallet = builder.init_without_persistence()?; //! # Ok::<_, Box>(()) //! ``` //! @@ -42,11 +44,10 @@ //! # use bitcoin::*; //! # use bdk::wallet::export::*; //! # use bdk::*; -//! let wallet = Wallet::new_no_persist( -//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", -//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"), -//! Network::Testnet, -//! )?; +//! let wallet = Wallet::builder("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)") +//! .with_change_descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)") +//! .with_network(Network::Testnet) +//! .init_without_persistence()?; //! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap(); //! //! println!("Exported: {}", export.to_string()); @@ -226,7 +227,11 @@ mod test { change_descriptor: Option<&str>, network: Network, ) -> Wallet<()> { - let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap(); + let mut builder = Wallet::builder(descriptor).with_network(network); + if let Some(change_descriptor) = change_descriptor { + builder = builder.with_change_descriptor(change_descriptor); + } + let mut wallet = builder.init_without_persistence().unwrap(); let transaction = Transaction { input: vec![], output: vec![], diff --git a/crates/bdk/src/wallet/hardwaresigner.rs b/crates/bdk/src/wallet/hardwaresigner.rs index aec49297c..092530d9e 100644 --- a/crates/bdk/src/wallet/hardwaresigner.rs +++ b/crates/bdk/src/wallet/hardwaresigner.rs @@ -30,12 +30,9 @@ //! let first_device = devices.remove(0)?; //! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?; //! -//! # let mut wallet = Wallet::new_no_persist( -//! # "", -//! # None, -//! # Network::Testnet, -//! # )?; -//! # +//! # let mut wallet = Wallet::builder("") +//! # .with_network(Network::Testnet) +//! # .init_without_persistence()?; //! // Adding the hardware signer to the BDK wallet //! wallet.add_signer( //! KeychainKind::External, diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 873af72b2..c1aa7eb79 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -28,6 +28,7 @@ use bdk_chain::{ Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut, IndexedTxGraph, Persist, PersistBackend, }; +use bitcoin::psbt; use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::{ @@ -35,7 +36,6 @@ use bitcoin::{ Weight, Witness, }; use bitcoin::{consensus::encode::serialize, BlockHash}; -use bitcoin::{constants::genesis_block, psbt}; use core::fmt; use core::ops::Deref; use descriptor::error::Error as DescriptorError; @@ -43,6 +43,8 @@ use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier}; use bdk_chain::tx_graph::CalculateFeeError; +mod builder; +pub use builder::*; pub mod coin_selection; pub mod export; pub mod signer; @@ -229,34 +231,6 @@ impl fmt::Display for AddressInfo { } } -impl Wallet { - /// Creates a wallet that does not persist data. - pub fn new_no_persist( - descriptor: E, - change_descriptor: Option, - network: Network, - ) -> Result { - Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e { - NewError::Descriptor(e) => e, - NewError::Write(_) => unreachable!("mock-write must always succeed"), - }) - } - - /// Creates a wallet that does not persist data, with a custom genesis hash. - pub fn new_no_persist_with_genesis_hash( - descriptor: E, - change_descriptor: Option, - network: Network, - genesis_hash: BlockHash, - ) -> Result { - Self::new_with_genesis_hash(descriptor, change_descriptor, (), network, genesis_hash) - .map_err(|e| match e { - NewError::Descriptor(e) => e, - NewError::Write(_) => unreachable!("mock-write must always succeed"), - }) - } -} - impl Wallet where D: PersistBackend, @@ -280,34 +254,31 @@ where } } -/// The error type when constructing a fresh [`Wallet`]. +/// The error type when initializing a fresh [`Wallet`]. /// -/// Methods [`new`] and [`new_with_genesis_hash`] may return this error. -/// -/// [`new`]: Wallet::new -/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash +/// [`WalletBuilder::init`] may return this error. #[derive(Debug)] -pub enum NewError { +pub enum InitError { /// There was problem with the passed-in descriptor(s). Descriptor(crate::descriptor::DescriptorError), /// We were unable to write the wallet's data to the persistence backend. Write(W), } -impl fmt::Display for NewError +impl fmt::Display for InitError where W: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - NewError::Descriptor(e) => e.fmt(f), - NewError::Write(e) => e.fmt(f), + InitError::Descriptor(e) => e.fmt(f), + InitError::Write(e) => e.fmt(f), } } } #[cfg(feature = "std")] -impl std::error::Error for NewError where W: core::fmt::Display + core::fmt::Debug {} +impl std::error::Error for InitError where W: core::fmt::Display + core::fmt::Debug {} /// The error type when loading a [`Wallet`] from persistence. /// @@ -350,12 +321,9 @@ impl std::error::Error for LoadError where L: core::fmt::Display + core::f /// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existant. /// -/// Methods [`new_or_load`] and [`new_or_load_with_genesis_hash`] may return this error. -/// -/// [`new_or_load`]: Wallet::new_or_load -/// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash +/// Method [`WalletBuilder::init_or_load`] may return this error. #[derive(Debug)] -pub enum NewOrLoadError { +pub enum InitOrLoadError { /// There is a problem with the passed-in descriptor. Descriptor(crate::descriptor::DescriptorError), /// Writing to the persistence backend failed. @@ -380,23 +348,23 @@ pub enum NewOrLoadError { }, } -impl fmt::Display for NewOrLoadError +impl fmt::Display for InitOrLoadError where W: fmt::Display, L: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - NewOrLoadError::Descriptor(e) => e.fmt(f), - NewOrLoadError::Write(e) => write!(f, "failed to write to persistence: {}", e), - NewOrLoadError::Load(e) => write!(f, "failed to load from persistence: {}", e), - NewOrLoadError::NotInitialized => { + InitOrLoadError::Descriptor(e) => e.fmt(f), + InitOrLoadError::Write(e) => write!(f, "failed to write to persistence: {}", e), + InitOrLoadError::Load(e) => write!(f, "failed to load from persistence: {}", e), + InitOrLoadError::NotInitialized => { write!(f, "wallet is not initialized, persistence backend is empty") } - NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => { + InitOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => { write!(f, "loaded genesis hash is not {}, got {:?}", expected, got) } - NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => { + InitOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => { write!(f, "loaded network type is not {}, got {:?}", expected, got) } } @@ -404,7 +372,7 @@ where } #[cfg(feature = "std")] -impl std::error::Error for NewOrLoadError +impl std::error::Error for InitOrLoadError where W: core::fmt::Display + core::fmt::Debug, L: core::fmt::Display + core::fmt::Debug, @@ -424,54 +392,43 @@ pub enum InsertTxError { }, } -impl Wallet { - /// Initialize an empty [`Wallet`]. - pub fn new( - descriptor: E, - change_descriptor: Option, - db: D, - network: Network, - ) -> Result> - where - D: PersistBackend, - { - let genesis_hash = genesis_block(network).block_hash(); - Self::new_with_genesis_hash(descriptor, change_descriptor, db, network, genesis_hash) +impl Wallet { + /// Start building a new [`Wallet`]. + pub fn builder(descriptor: E) -> WalletBuilder { + WalletBuilder::new(descriptor) } +} - /// Initialize an empty [`Wallet`] with a custom genesis hash. - /// - /// This is like [`Wallet::new`] with an additional `genesis_hash` parameter. This is useful - /// for syncing from alternative networks. - pub fn new_with_genesis_hash( - descriptor: E, - change_descriptor: Option, +impl Wallet { + pub(crate) fn init( + builder: WalletBuilder, db: D, - network: Network, - genesis_hash: BlockHash, - ) -> Result> + ) -> Result> where D: PersistBackend, { let secp = Secp256k1::new(); + let network = builder.network; + let genesis_hash = builder.determine_genesis_hash(); let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); let mut index = KeychainTxOutIndex::::default(); - - let (signers, change_signers) = - create_signers(&mut index, &secp, descriptor, change_descriptor, network) - .map_err(NewError::Descriptor)?; - + let (signers, change_signers) = create_signers( + &mut index, + &secp, + builder.descriptor, + builder.change_descriptor, + builder.network, + ) + .map_err(InitError::Descriptor)?; let indexed_graph = IndexedTxGraph::new(index); - let mut persist = Persist::new(db); persist.stage(ChangeSet { chain: chain_changeset, indexed_tx_graph: indexed_graph.initial_changeset(), network: Some(network), }); - persist.commit().map_err(NewError::Write)?; - - Ok(Wallet { + persist.commit().map_err(InitError::Write)?; + Ok(Self { signers, change_signers, network, @@ -533,90 +490,54 @@ impl Wallet { }) } - /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist. - /// - /// This method will fail if the loaded [`Wallet`] has different parameters to those provided. - pub fn new_or_load( - descriptor: E, - change_descriptor: Option, - db: D, - network: Network, - ) -> Result> - where - D: PersistBackend, - { - let genesis_hash = genesis_block(network).block_hash(); - Self::new_or_load_with_genesis_hash( - descriptor, - change_descriptor, - db, - network, - genesis_hash, - ) - } - - /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist (with a - /// custom genesis hash). - /// - /// This method will fail if the loaded [`Wallet`] has different parameters to those provided. - /// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is - /// useful for syncing from alternative networks. - pub fn new_or_load_with_genesis_hash( - descriptor: E, - change_descriptor: Option, + pub(crate) fn init_or_load( + builder: WalletBuilder, mut db: D, - network: Network, - genesis_hash: BlockHash, - ) -> Result> + ) -> Result> where D: PersistBackend, { - let changeset = db.load_from_persistence().map_err(NewOrLoadError::Load)?; + let network = builder.network; + let genesis_hash = builder.determine_genesis_hash(); + let changeset = db.load_from_persistence().map_err(InitOrLoadError::Load)?; match changeset { Some(changeset) => { - let wallet = - Self::load_from_changeset(descriptor, change_descriptor, db, changeset) - .map_err(|e| match e { - LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e), - LoadError::Load(e) => NewOrLoadError::Load(e), - LoadError::NotInitialized => NewOrLoadError::NotInitialized, - LoadError::MissingNetwork => { - NewOrLoadError::LoadedNetworkDoesNotMatch { - expected: network, - got: None, - } - } - LoadError::MissingGenesis => { - NewOrLoadError::LoadedGenesisDoesNotMatch { - expected: genesis_hash, - got: None, - } - } - })?; - if wallet.network != network { - return Err(NewOrLoadError::LoadedNetworkDoesNotMatch { + let wallet = Self::load_from_changeset( + builder.descriptor, + builder.change_descriptor, + db, + changeset, + ) + .map_err(|err| match err { + LoadError::Descriptor(err) => InitOrLoadError::Descriptor(err), + LoadError::Load(err) => InitOrLoadError::Load(err), + LoadError::NotInitialized => InitOrLoadError::NotInitialized, + LoadError::MissingNetwork => InitOrLoadError::LoadedNetworkDoesNotMatch { + expected: network, + got: None, + }, + LoadError::MissingGenesis => InitOrLoadError::LoadedGenesisDoesNotMatch { + expected: genesis_hash, + got: None, + }, + })?; + if wallet.network != builder.network { + return Err(InitOrLoadError::LoadedNetworkDoesNotMatch { expected: network, got: Some(wallet.network), }); } if wallet.chain.genesis_hash() != genesis_hash { - return Err(NewOrLoadError::LoadedGenesisDoesNotMatch { + return Err(InitOrLoadError::LoadedGenesisDoesNotMatch { expected: genesis_hash, got: Some(wallet.chain.genesis_hash()), }); } Ok(wallet) } - None => Self::new_with_genesis_hash( - descriptor, - change_descriptor, - db, - network, - genesis_hash, - ) - .map_err(|e| match e { - NewError::Descriptor(e) => NewOrLoadError::Descriptor(e), - NewError::Write(e) => NewOrLoadError::Write(e), + None => builder.init(db).map_err(|err| match err { + InitError::Descriptor(err) => InitOrLoadError::Descriptor(err), + InitError::Write(err) => InitOrLoadError::Write(err), }), } } @@ -1121,7 +1042,9 @@ impl Wallet { /// ``` /// # use bdk::{Wallet, KeychainKind}; /// # use bdk::bitcoin::Network; - /// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet)?; + /// let wallet = Wallet::builder("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)") + /// .with_network(Network::Testnet) + /// .init_without_persistence()?; /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) { /// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/* /// println!("secret_key: {}", secret_key); @@ -2403,12 +2326,11 @@ macro_rules! doctest_wallet { let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)"; - let mut wallet = Wallet::new_no_persist( - descriptor, - Some(change_descriptor), - Network::Regtest, - ) - .unwrap(); + let mut wallet = Wallet::builder(descriptor) + .with_change_descriptor(change_descriptor) + .with_network(Network::Regtest) + .init_without_persistence() + .unwrap(); let address = wallet.get_address(AddressIndex::New).address; let tx = Transaction { version: 1, diff --git a/crates/bdk/src/wallet/signer.rs b/crates/bdk/src/wallet/signer.rs index da4940bf9..9dec9683f 100644 --- a/crates/bdk/src/wallet/signer.rs +++ b/crates/bdk/src/wallet/signer.rs @@ -69,7 +69,9 @@ //! let custom_signer = CustomSigner::connect(); //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; -//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?; +//! let mut wallet = Wallet::builder(descriptor) +//! .with_network(Network::Testnet) +//! .init_without_persistence()?; //! wallet.add_signer( //! KeychainKind::External, //! SignerOrdering(200), diff --git a/crates/bdk/tests/common.rs b/crates/bdk/tests/common.rs index 3e0292a29..98372acc4 100644 --- a/crates/bdk/tests/common.rs +++ b/crates/bdk/tests/common.rs @@ -16,7 +16,12 @@ pub fn get_funded_wallet_with_change( descriptor: &str, change: Option<&str>, ) -> (Wallet, bitcoin::Txid) { - let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap(); + let mut builder = Wallet::builder(descriptor).with_network(Network::Regtest); + if let Some(change_descriptor) = change { + builder = builder.with_change_descriptor(change_descriptor); + } + let mut wallet = builder.init_without_persistence().unwrap(); + let change_address = wallet.get_address(AddressIndex::New).address; let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5") .expect("address") diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index 6efb0d791..bdb863f2f 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -73,9 +73,10 @@ fn load_recovers_wallet() { // create new wallet let wallet_spk_index = { let db = bdk_file_store::Store::create_new(DB_MAGIC, &file_path).expect("must create db"); - let mut wallet = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet) - .expect("must init wallet"); - + let mut wallet = Wallet::builder(get_test_tr_single_sig_xprv()) + .with_network(Network::Testnet) + .init(db) + .expect("must initialize wallet"); wallet.try_get_address(New).unwrap(); wallet.spk_index().clone() }; @@ -103,7 +104,9 @@ fn new_or_load() { let wallet_keychains = { let db = bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path) .expect("must create db"); - let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet) + let wallet = Wallet::builder(get_test_wpkh()) + .with_network(Network::Testnet) + .init_or_load(db) .expect("must init wallet"); wallet.keychains().clone() }; @@ -112,12 +115,14 @@ fn new_or_load() { { let db = bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db"); - let err = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Bitcoin) + let err = Wallet::builder(get_test_wpkh()) + .with_network(Network::Bitcoin) + .init_or_load(db) .expect_err("wrong network"); assert!( matches!( err, - bdk::wallet::NewOrLoadError::LoadedNetworkDoesNotMatch { + bdk::wallet::InitOrLoadError::LoadedNetworkDoesNotMatch { got: Some(Network::Testnet), expected: Network::Bitcoin } @@ -135,18 +140,15 @@ fn new_or_load() { let db = bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db"); - let err = Wallet::new_or_load_with_genesis_hash( - get_test_wpkh(), - None, - db, - Network::Testnet, - exp_blockhash, - ) - .expect_err("wrong genesis hash"); + let err = Wallet::builder(get_test_wpkh()) + .with_network(Network::Testnet) + .with_genesis_hash(exp_blockhash) + .init_or_load(db) + .expect_err("wrong genesis hash"); assert!( matches!( err, - bdk::wallet::NewOrLoadError::LoadedGenesisDoesNotMatch { got, expected } + bdk::wallet::InitOrLoadError::LoadedGenesisDoesNotMatch { got, expected } if got == Some(got_blockhash) && expected == exp_blockhash ), "err: {}", @@ -158,7 +160,9 @@ fn new_or_load() { { let db = bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db"); - let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet) + let wallet = Wallet::builder(get_test_wpkh()) + .with_network(Network::Testnet) + .init_or_load(db) .expect("must recover wallet"); assert_eq!(wallet.network(), Network::Testnet); assert_eq!(wallet.keychains(), &wallet_keychains); @@ -1053,7 +1057,10 @@ fn test_create_tx_policy_path_required() { #[test] fn test_create_tx_policy_path_no_csv() { let descriptors = get_test_wpkh(); - let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap(); + let mut wallet = Wallet::builder(descriptors) + .with_network(Network::Regtest) + .init_without_persistence() + .unwrap(); let tx = Transaction { version: 0, @@ -2579,9 +2586,10 @@ fn test_sign_nonstandard_sighash() { #[test] fn test_unused_address() { - let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", - None, Network::Testnet).unwrap(); - + let mut wallet = Wallet::builder("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)") + .with_network(Network::Testnet) + .init_without_persistence() + .unwrap(); assert_eq!( wallet.get_address(LastUnused).to_string(), "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" @@ -2595,7 +2603,10 @@ fn test_unused_address() { #[test] fn test_next_unused_address() { let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; - let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet).unwrap(); + let mut wallet = Wallet::builder(descriptor) + .with_network(Network::Testnet) + .init_without_persistence() + .unwrap(); assert_eq!(wallet.derivation_index(KeychainKind::External), None); assert_eq!( @@ -2621,8 +2632,11 @@ fn test_next_unused_address() { #[test] fn test_peek_address_at_index() { - let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", - None, Network::Testnet).unwrap(); + let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; + let mut wallet = Wallet::builder(descriptor) + .with_network(Network::Testnet) + .init_without_persistence() + .unwrap(); assert_eq!( wallet.get_address(Peek(1)).to_string(), @@ -2653,8 +2667,11 @@ fn test_peek_address_at_index() { #[test] fn test_peek_address_at_index_not_derivable() { - let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", - None, Network::Testnet).unwrap(); + let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)"; + let mut wallet = Wallet::builder(descriptor) + .with_network(Network::Testnet) + .init_without_persistence() + .unwrap(); assert_eq!( wallet.get_address(Peek(1)).to_string(), @@ -2674,8 +2691,11 @@ fn test_peek_address_at_index_not_derivable() { #[test] fn test_returns_index_and_address() { - let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", - None, Network::Testnet).unwrap(); + let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; + let mut wallet = Wallet::builder(descriptor) + .with_network(Network::Testnet) + .init_without_persistence() + .unwrap(); // new index 0 assert_eq!( @@ -2741,12 +2761,11 @@ fn test_sending_to_bip350_bech32m_address() { fn test_get_address() { use bdk::descriptor::template::Bip84; let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - let mut wallet = Wallet::new_no_persist( - Bip84(key, KeychainKind::External), - Some(Bip84(key, KeychainKind::Internal)), - Network::Regtest, - ) - .unwrap(); + let mut wallet = Wallet::builder(Bip84(key, KeychainKind::External)) + .with_change_descriptor(Bip84(key, KeychainKind::Internal)) + .with_network(Network::Regtest) + .init_without_persistence() + .unwrap(); assert_eq!( wallet.get_address(AddressIndex::New), @@ -2770,8 +2789,10 @@ fn test_get_address() { } ); - let mut wallet = - Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap(); + let mut wallet = Wallet::builder(Bip84(key, KeychainKind::External)) + .with_network(Network::Regtest) + .init_without_persistence() + .unwrap(); assert_eq!( wallet.get_internal_address(AddressIndex::New), @@ -2792,8 +2813,10 @@ fn test_get_address_no_reuse_single_descriptor() { use std::collections::HashSet; let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - let mut wallet = - Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap(); + let mut wallet = Wallet::builder(Bip84(key, KeychainKind::External)) + .with_network(Network::Regtest) + .init_without_persistence() + .unwrap(); let mut used_set = HashSet::new(); @@ -3258,8 +3281,10 @@ fn test_taproot_sign_derive_index_from_psbt() { let mut psbt = builder.finish().unwrap(); // re-create the wallet with an empty db - let wallet_empty = - Wallet::new_no_persist(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap(); + let wallet_empty = Wallet::builder(get_test_tr_single_sig_xprv()) + .with_network(Network::Regtest) + .init_without_persistence() + .unwrap(); // signing with an empty db means that we will only look at the psbt to infer the // derivation index @@ -3359,7 +3384,10 @@ fn test_taproot_sign_non_default_sighash() { #[test] fn test_spend_coinbase() { let descriptor = get_test_wpkh(); - let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Regtest).unwrap(); + let mut wallet = Wallet::builder(descriptor) + .with_network(Network::Regtest) + .init_without_persistence() + .unwrap(); let confirmation_height = 5; wallet diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 9d4c6c5a4..2c9975338 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -22,12 +22,10 @@ fn main() -> Result<(), anyhow::Error> { let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; - let mut wallet = Wallet::new_or_load( - external_descriptor, - Some(internal_descriptor), - db, - Network::Testnet, - )?; + let mut wallet = Wallet::builder(external_descriptor) + .with_change_descriptor(internal_descriptor) + .with_network(Network::Testnet) + .init_or_load(db)?; let address = wallet.try_get_address(bdk::wallet::AddressIndex::New)?; println!("Generated Address: {}", address); diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index fb8f7b510..fe55ea091 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -20,12 +20,10 @@ async fn main() -> Result<(), anyhow::Error> { let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; - let mut wallet = Wallet::new_or_load( - external_descriptor, - Some(internal_descriptor), - db, - Network::Testnet, - )?; + let mut wallet = Wallet::builder(external_descriptor) + .with_change_descriptor(internal_descriptor) + .with_network(Network::Testnet) + .init_or_load(db)?; let address = wallet.try_get_address(AddressIndex::New)?; println!("Generated Address: {}", address); diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 09e7c3ad4..8902f8c78 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -19,12 +19,10 @@ fn main() -> Result<(), anyhow::Error> { let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; - let mut wallet = Wallet::new_or_load( - external_descriptor, - Some(internal_descriptor), - db, - Network::Testnet, - )?; + let mut wallet = Wallet::builder(external_descriptor) + .with_change_descriptor(internal_descriptor) + .with_network(Network::Testnet) + .init_or_load(db)?; let address = wallet.try_get_address(AddressIndex::New)?; println!("Generated Address: {}", address); From 1662340682951ea9046924f72bd50f016ea6f550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Wed, 27 Dec 2023 13:43:21 +0800 Subject: [PATCH 4/4] refactor(chain): make clippy happy --- crates/chain/src/tx_graph.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 10cf10446..ef15d7e7d 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -581,10 +581,7 @@ impl TxGraph { } for (outpoint, txout) in changeset.txouts { - let tx_entry = self - .txs - .entry(outpoint.txid) - .or_insert_with(Default::default); + let tx_entry = self.txs.entry(outpoint.txid).or_default(); match tx_entry { (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */ @@ -597,13 +594,13 @@ impl TxGraph { for (anchor, txid) in changeset.anchors { if self.anchors.insert((anchor.clone(), txid)) { - let (_, anchors, _) = self.txs.entry(txid).or_insert_with(Default::default); + let (_, anchors, _) = self.txs.entry(txid).or_default(); anchors.insert(anchor); } } for (txid, new_last_seen) in changeset.last_seen { - let (_, _, last_seen) = self.txs.entry(txid).or_insert_with(Default::default); + let (_, _, last_seen) = self.txs.entry(txid).or_default(); if new_last_seen > *last_seen { *last_seen = new_last_seen; }