sync::Arc,
vec::Vec,
};
-use bdk_chain::{chain_graph, keychain::KeychainTracker, sparse_chain, BlockId, ConfirmationTime};
+use bdk_chain::{
+ chain_graph,
+ keychain::{KeychainChangeSet, KeychainScan, KeychainTracker},
+ sparse_chain, BlockId, ConfirmationTime, IntoOwned,
+};
use bitcoin::secp256k1::Secp256k1;
-use core::convert::TryInto;
use core::fmt;
use core::ops::Deref;
#[cfg(feature = "hardware-signer")]
#[cfg_attr(docsrs, doc(cfg(feature = "hardware-signer")))]
pub mod hardwaresigner;
+pub mod persist;
pub use utils::IsDust;
use crate::descriptor::policy::BuildSatisfaction;
use crate::descriptor::{
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
- ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, SpkIter, XKeyUtils,
+ ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
};
use crate::error::{Error, MiniscriptPsbtError};
use crate::psbt::PsbtUtils;
/// [`Database`]: crate::database::Database
/// [`signer`]: crate::signer
#[derive(Debug)]
-pub struct Wallet {
+pub struct Wallet<D = ()> {
signers: Arc<SignersContainer>,
change_signers: Arc<SignersContainer>,
keychain_tracker: KeychainTracker<KeychainKind, ConfirmationTime>,
-
+ persist: persist::Persist<D>,
network: Network,
-
secp: SecpCtx,
}
+/// The update to a [`Wallet`] used in [Wallet::apply_update]. This is usually returned from blockchain data sources.
+/// The type parameter `T` indicates the kind of transaction contained in the update. It's usually a [`bitcoin::Transaction`].
+pub type Update<T> = KeychainScan<KeychainKind, ConfirmationTime, T>;
+/// Error indicating that something was wrong with an [`Update<T>`].
+pub type UpdateError = chain_graph::UpdateError<ConfirmationTime>;
+/// The changeset produced internally by applying an update
+pub(crate) type ChangeSet = KeychainChangeSet<KeychainKind, ConfirmationTime, Transaction>;
+
/// 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)]
}
impl Wallet {
- /// Create a wallet.
- ///
- /// The only way this can fail is if the descriptors passed in do not match the checksums in `database`.
+ /// Creates a wallet that does not persist data.
+ pub fn new_no_persist<E: IntoWalletDescriptor>(
+ descriptor: E,
+ change_descriptor: Option<E>,
+ network: Network,
+ ) -> Result<Self, crate::descriptor::DescriptorError> {
+ Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
+ NewError::Descriptor(e) => e,
+ NewError::Persist(_) => unreachable!("no persistence so it can't fail"),
+ })
+ }
+}
+
+#[derive(Debug)]
+/// Error returned from [`Wallet::new`]
+pub enum NewError<P> {
+ /// There was problem with the descriptors passed in
+ Descriptor(crate::descriptor::DescriptorError),
+ /// We were unable to load the wallet's data from the persistance backend
+ Persist(P),
+}
+
+impl<P> core::fmt::Display for NewError<P>
+where
+ P: core::fmt::Display,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ NewError::Descriptor(e) => e.fmt(f),
+ NewError::Persist(e) => {
+ write!(f, "failed to load wallet from persistance backend: {}", e)
+ }
+ }
+ }
+}
+
+#[cfg(feautre = "std")]
+impl<P: core::fmt::Display> std::error::Error for NewError<P> {}
+
+impl<D> Wallet<D> {
+ /// Create a wallet from a `descriptor` (and an optional `change_descriptor`) and load related
+ /// transaction data from `db`.
pub fn new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
+ mut db: D,
network: Network,
- ) -> Result<Self, Error> {
+ ) -> Result<Self, NewError<D::LoadError>>
+ where
+ D: persist::Backend,
+ {
let secp = Secp256k1::new();
let mut keychain_tracker = KeychainTracker::default();
- let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
+ let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)
+ .map_err(NewError::Descriptor)?;
keychain_tracker
.txout_index
.add_keychain(KeychainKind::External, descriptor.clone());
let change_signers = match change_descriptor {
Some(desc) => {
let (change_descriptor, change_keymap) =
- into_wallet_descriptor_checked(desc, &secp, network)?;
+ into_wallet_descriptor_checked(desc, &secp, network)
+ .map_err(NewError::Descriptor)?;
let change_signers = Arc::new(SignersContainer::build(
change_keymap,
None => Arc::new(SignersContainer::new()),
};
+ db.load_into_keychain_tracker(&mut keychain_tracker)
+ .map_err(NewError::Persist)?;
+
+ let persist = persist::Persist::new(db);
+
Ok(Wallet {
signers,
change_signers,
network,
+ persist,
secp,
keychain_tracker,
})
self.keychain_tracker.txout_index.keychains()
}
- // Return a newly derived address for the specified `keychain`.
- fn get_new_address(&mut self, keychain: KeychainKind) -> AddressInfo {
- let ((index, spk), _) = self.keychain_tracker.txout_index.reveal_next_spk(&keychain);
- let address =
- Address::from_script(&spk, self.network).expect("descriptor must have address form");
-
- AddressInfo {
- address,
- index,
- keychain,
- }
- }
-
- // Return the the last previously derived address for `keychain` if it has not been used in a
- // received transaction. Otherwise return a new address using [`Wallet::get_new_address`].
- fn get_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo {
- let index = self.derivation_index(KeychainKind::External);
-
- match index {
- Some(index)
- if !self
- .keychain_tracker
- .txout_index
- .is_used(&(keychain, index)) =>
- {
- self.peek_address(index, keychain)
- }
- _ => self.get_new_address(keychain),
- }
- }
-
- // Return derived address for the descriptor of given [`KeychainKind`] at a specific index
- fn peek_address(&self, index: u32, keychain: KeychainKind) -> AddressInfo {
- let address = self
- .get_descriptor_for_keychain(keychain)
- .at_derivation_index(index)
- .address(self.network)
- .expect("descriptor must have address form");
- AddressInfo {
- index,
- address,
- keychain,
- }
- }
-
/// 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
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
- pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
+ pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo
+ where
+ D: persist::Backend,
+ {
self._get_address(address_index, KeychainKind::External)
}
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
/// be returned for any [`AddressIndex`].
- pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo {
+ pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
+ where
+ D: persist::Backend,
+ {
self._get_address(address_index, KeychainKind::Internal)
}
- fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo {
- // TODO: Fix this mess!
- let _keychain = self.map_keychain(keychain);
- let mut info = match address_index {
- AddressIndex::New => self.get_new_address(_keychain),
- AddressIndex::LastUnused => self.get_unused_address(_keychain),
- AddressIndex::Peek(index) => self.peek_address(index, _keychain),
+ fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
+ where
+ D: persist::Backend,
+ {
+ let keychain = self.map_keychain(keychain);
+ let txout_index = &mut self.keychain_tracker.txout_index;
+ let (index, spk) = match address_index {
+ AddressIndex::New => {
+ let ((index, spk), changeset) = txout_index.reveal_next_spk(&keychain);
+ let spk = spk.clone();
+
+ self.persist.stage(changeset.into());
+ self.persist.commit().expect("TODO");
+ (index, spk)
+ }
+ AddressIndex::LastUnused => {
+ let index = txout_index.last_revealed_index(&keychain);
+ match index {
+ Some(index) if !txout_index.is_used(&(keychain, index)) => (
+ index,
+ txout_index
+ .spk_at_index(&(keychain, index))
+ .expect("must exist")
+ .clone(),
+ ),
+ _ => return self._get_address(AddressIndex::New, keychain),
+ }
+ }
+ AddressIndex::Peek(index) => txout_index
+ .spks_of_keychain(&keychain)
+ .take(index as usize + 1)
+ .last()
+ .unwrap(),
+ };
+ let info = AddressInfo {
+ index,
+ address: Address::from_script(&spk, self.network)
+ .expect("descriptor must have address form"),
+ keychain,
};
- info.keychain = keychain;
info
}
.collect()
}
- /// Iterate over all checkpoints.
+ /// Get all the checkpoints the wallet is currently storing indexed by height.
pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
self.keychain_tracker.chain().checkpoints()
}
self.keychain_tracker.chain().latest_checkpoint()
}
- /// Create an iterator over all the script pubkeys starting at index 0 for a particular
- /// keychain.
- pub fn iter_all_script_pubkeys(&self, keychain: KeychainKind) -> SpkIter {
- SpkIter::new(self.get_descriptor_for_keychain(keychain).clone())
+ /// Returns a iterators of all the script pubkeys for the `Internal` and External` variants in `KeychainKind`.
+ ///
+ /// This is inteded to be used when doing a full scan of your addresses (e.g. after restoring
+ /// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g.
+ /// electrum server) which will go through each address until it reaches a *stop grap*.
+ ///
+ /// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
+ /// script pubkeys the wallet is storing internally).
+ pub fn spks_of_all_keychains(
+ &self,
+ ) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, Script)> + Clone> {
+ self.keychain_tracker.txout_index.spks_of_all_keychains()
}
- /// Returns the `UTXO` owned by this wallet corresponding to `outpoint` if it exists in the
+ /// Gets an iterator over all the script pubkeys in a single keychain.
+ ///
+ /// See [`spks_of_all_keychains`] for more documentation
+ ///
+ /// [`spks_of_all_keychains`]: Self::spks_of_all_keychains
+ pub fn spks_of_keychain(
+ &self,
+ keychain: KeychainKind,
+ ) -> impl Iterator<Item = (u32, Script)> + Clone {
+ self.keychain_tracker
+ .txout_index
+ .spks_of_keychain(&keychain)
+ }
+
+ /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
/// wallet's database.
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalUtxo> {
self.keychain_tracker
})
}
- /// Add a new checkpoint to the wallet
+ /// Add a new checkpoint to the wallet's internal view of the chain.
+ /// This stages but does not [`commit`] the change.
+ ///
+ /// Returns whether anything changed with the insertion (e.g. `false` if checkpoint was already
+ /// there).
+ ///
+ /// [`commit`]: Self::commit
pub fn insert_checkpoint(
&mut self,
block_id: BlockId,
) -> Result<bool, sparse_chain::InsertCheckpointError> {
- Ok(!self
- .keychain_tracker
- .insert_checkpoint(block_id)?
- .is_empty())
+ let changeset = self.keychain_tracker.insert_checkpoint(block_id)?;
+ let changed = changeset.is_empty();
+ self.persist.stage(changeset);
+ Ok(changed)
}
- /// Add a transaction to the wallet. Will only work if height <= latest checkpoint
+ /// Add a transaction to the wallet's internal view of the chain.
+ /// This stages but does not [`commit`] the change.
+ ///
+ /// There are a number reasons `tx` could be rejected with an `Err(_)`. The most important one
+ /// is that the transaction is at a height that is greater than [`latest_checkpoint`]. Therefore
+ /// you should use [`insert_checkpoint`] to insert new checkpoints before manually inserting new
+ /// transactions.
+ ///
+ /// Returns whether anything changed with the transaction insertion (e.g. `false` if the
+ /// transaction was already inserted at the same position).
+ ///
+ /// [`commit`]: Self::commit
+ /// [`latest_checkpoint`]: Self::latest_checkpoint
+ /// [`insert_checkpoint`]: Self::insert_checkpoint
pub fn insert_tx(
&mut self,
tx: Transaction,
position: ConfirmationTime,
) -> Result<bool, chain_graph::InsertTxError<ConfirmationTime>> {
- Ok(!self.keychain_tracker.insert_tx(tx, position)?.is_empty())
+ let changeset = self.keychain_tracker.insert_tx(tx, position)?;
+ let changed = changeset.is_empty();
+ self.persist.stage(changeset);
+ Ok(changed)
}
#[deprecated(note = "use Wallet::transactions instead")]
&self,
) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
self.keychain_tracker
- .chain()
- .txids()
- .map(move |&(pos, txid)| {
- (
- pos,
- self.keychain_tracker
- .graph()
- .get_tx(txid)
- .expect("must exist"),
- )
- })
+ .chain_graph()
+ .transactions_in_chain()
+ .map(|(pos, tx)| (*pos, tx))
}
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::bitcoin::Network;
/// # use bdk::database::MemoryDatabase;
- /// let wallet = Wallet::new("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet, MemoryDatabase::new())?;
+ /// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet, MemoryDatabase::new())?;
/// 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);
/// ```
///
/// [`TxBuilder`]: crate::TxBuilder
- pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm, CreateTx> {
+ pub fn build_tx(&mut self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
TxBuilder {
wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
params: TxParams::default(),
&mut self,
coin_selection: Cs,
params: TxParams,
- ) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error> {
+ ) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
+ where
+ D: persist::Backend,
+ {
let external_descriptor = self
.keychain_tracker
.txout_index
// get drain script
let drain_script = match params.drain_to {
Some(ref drain_recipient) => drain_recipient.clone(),
- None => self
- .get_internal_address(AddressIndex::New)
- .address
- .script_pubkey(),
+ None => {
+ let change_keychain = self.map_keychain(KeychainKind::Internal);
+ let ((index, spk), changeset) = self
+ .keychain_tracker
+ .txout_index
+ .next_unused_spk(&change_keychain);
+ let spk = spk.clone();
+ self.keychain_tracker
+ .txout_index
+ .mark_used(&change_keychain, index);
+ self.persist.stage(changeset.into());
+ self.persist.commit().expect("TODO");
+ spk
+ }
};
let coin_selection = coin_selection.coin_select(
pub fn build_fee_bump(
&mut self,
txid: Txid,
- ) -> Result<TxBuilder<'_, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
+ ) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
let graph = self.keychain_tracker.graph();
let txout_index = &self.keychain_tracker.txout_index;
let tx_and_height = self.keychain_tracker.chain_graph().get_tx_in_chain(txid);
return Err(Error::IrreplaceableTransaction);
}
- let fee = graph
- .calculate_fee(&tx)
- .ok_or(Error::FeeRateUnavailable)?
- .try_into()
- .map_err(|_| Error::FeeRateUnavailable)?;
+ let fee = graph.calculate_fee(&tx).ok_or(Error::FeeRateUnavailable)?;
+ if fee < 0 {
+ // It's available but it's wrong so let's say it's unavailable
+ return Err(Error::FeeRateUnavailable)?;
+ }
+ let fee = fee as u64;
let feerate = FeeRate::from_wu(fee, tx.weight());
// remove the inputs from the tx and process them
.1
.to_string()
}
+
+ /// Applies an update to the wallet and stages the changes (but does not [`commit`] them).
+ ///
+ /// Usually you create an `update` by interacting with some blockchain data source and inserting
+ /// transactions related to your wallet into it.
+ ///
+ /// [`commit`]: Self::commit
+ pub fn apply_udpate<Tx>(&mut self, update: Update<Tx>) -> Result<(), UpdateError>
+ where
+ D: persist::Backend,
+ Tx: IntoOwned<Transaction> + Clone,
+ {
+ let changeset = self.keychain_tracker.apply_update(update)?;
+ self.persist.stage(changeset);
+ Ok(())
+ }
+
+ /// Commits all curently [`staged`] changed to the persistence backend returning and error when this fails.
+ ///
+ /// [`staged`]: Self::staged
+ pub fn commit(&mut self) -> Result<(), D::WriteError>
+ where
+ D: persist::Backend,
+ {
+ self.persist.commit()
+ }
+
+ /// Returns the changes that will be staged with the next call to [`commit`].
+ ///
+ /// [`commit`]: Self::commit
+ pub fn staged(&self) -> &ChangeSet {
+ self.persist.staged()
+ }
}
/// Deterministically generate a unique name given the descriptors defining the wallet
--- /dev/null
+//! Persistence for changes made to a [`Wallet`].
+//!
+//! BDK's [`Wallet`] needs somewhere to persist changes it makes during operation.
+//! Operations like giving out a new address are crucial to persist so that next time the
+//! application is loaded it can find transactions related to that address.
+//!
+//! Note that `Wallet` does not read this persisted data during operation since it always has a copy
+//! in memory
+use crate::KeychainKind;
+use bdk_chain::{keychain::KeychainTracker, ConfirmationTime};
+
+/// `Persist` wraps a [`Backend`] to create a convienient staging area for changes before they are
+/// persisted. Not all changes made to the [`Wallet`] need to be written to disk right away so you
+/// can use [`Persist::stage`] to *stage* it first and then [`Persist::commit`] to finally write it
+/// to disk.
+#[derive(Debug)]
+pub struct Persist<P> {
+ backend: P,
+ stage: ChangeSet,
+}
+
+impl<P> Persist<P> {
+ /// Create a new `Persist` from a [`Backend`]
+ pub fn new(backend: P) -> Self {
+ Self {
+ backend,
+ stage: Default::default(),
+ }
+ }
+
+ /// Stage a `changeset` to later persistence with [`commit`].
+ ///
+ /// [`commit`]: Self::commit
+ pub fn stage(&mut self, changeset: ChangeSet) {
+ self.stage.append(changeset)
+ }
+
+ /// Get the changes that haven't been commited yet
+ pub fn staged(&self) -> &ChangeSet {
+ &self.stage
+ }
+
+ /// Commit the staged changes to the underlying persistence backend.
+ ///
+ /// Retuns a backend defined error if this fails
+ pub fn commit(&mut self) -> Result<(), P::WriteError>
+ where
+ P: Backend,
+ {
+ self.backend.append_changeset(&self.stage)?;
+ self.stage = Default::default();
+ Ok(())
+ }
+}
+
+/// A persistence backend for [`Wallet`]
+///
+/// [`Wallet`]: crate::Wallet
+pub trait Backend {
+ /// The error the backend returns when it fails to write
+ type WriteError: core::fmt::Debug;
+ /// The error the backend returns when it fails to load
+ type LoadError: core::fmt::Debug;
+ /// Appends a new changeset to the persistance backend.
+ ///
+ /// It is up to the backend what it does with this. It could store every changeset in a list or
+ /// it insert the actual changes to a more structured database. All it needs to guarantee is
+ /// that [`load_into_keychain_tracker`] restores a keychain tracker to what it should be if all
+ /// changesets had been applied sequentially.
+ ///
+ /// [`load_into_keychain_tracker`]: Self::load_into_keychain_tracker
+ fn append_changeset(&mut self, changeset: &ChangeSet) -> Result<(), Self::WriteError>;
+
+ /// Applies all the changesets the backend has received to `tracker`.
+ fn load_into_keychain_tracker(
+ &mut self,
+ tracker: &mut KeychainTracker<KeychainKind, ConfirmationTime>,
+ ) -> Result<(), Self::LoadError>;
+}
+
+#[cfg(feature = "file-store")]
+mod file_store {
+ use super::*;
+ use bdk_chain::file_store::{IterError, KeychainStore};
+
+ type FileStore = KeychainStore<KeychainKind, ConfirmationTime>;
+
+ impl Backend for FileStore {
+ type WriteError = std::io::Error;
+ type LoadError = IterError;
+ fn append_changeset(&mut self, changeset: &ChangeSet) -> Result<(), Self::WriteError> {
+ self.append_changeset(changeset)
+ }
+ fn load_into_keychain_tracker(
+ &mut self,
+ tracker: &mut KeychainTracker<KeychainKind, ConfirmationTime>,
+ ) -> Result<(), Self::LoadError> {
+ self.load_into_keychain_tracker(tracker)
+ }
+ }
+}
+
+impl Backend for () {
+ type WriteError = ();
+ type LoadError = ();
+ fn append_changeset(&mut self, _changeset: &ChangeSet) -> Result<(), Self::WriteError> {
+ Ok(())
+ }
+ fn load_into_keychain_tracker(
+ &mut self,
+ _tracker: &mut KeychainTracker<KeychainKind, ConfirmationTime>,
+ ) -> Result<(), Self::LoadError> {
+ Ok(())
+ }
+}
+
+#[cfg(feature = "file-store")]
+pub use file_store::*;
+
+use super::ChangeSet;
use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
+use super::persist;
use crate::{
types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo},
TransactionDetails,
/// [`finish`]: Self::finish
/// [`coin_selection`]: Self::coin_selection
#[derive(Debug)]
-pub struct TxBuilder<'a, Cs, Ctx> {
- pub(crate) wallet: Rc<RefCell<&'a mut Wallet>>,
+pub struct TxBuilder<'a, D, Cs, Ctx> {
+ pub(crate) wallet: Rc<RefCell<&'a mut Wallet<D>>>,
pub(crate) params: TxParams,
pub(crate) coin_selection: Cs,
pub(crate) phantom: PhantomData<Ctx>,
}
}
-impl<'a, Cs: Clone, Ctx> Clone for TxBuilder<'a, Cs, Ctx> {
+impl<'a, D, Cs: Clone, Ctx> Clone for TxBuilder<'a, D, Cs, Ctx> {
fn clone(&self) -> Self {
TxBuilder {
wallet: self.wallet.clone(),
}
// methods supported by both contexts, for any CoinSelectionAlgorithm
-impl<'a, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, Cs, Ctx> {
+impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, Cs, Ctx> {
/// Set a custom fee rate
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate));
pub fn coin_selection<P: CoinSelectionAlgorithm>(
self,
coin_selection: P,
- ) -> TxBuilder<'a, P, Ctx> {
+ ) -> TxBuilder<'a, D, P, Ctx> {
TxBuilder {
wallet: self.wallet,
params: self.params,
/// Returns the [`BIP174`] "PSBT" and summary details about the transaction.
///
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
- pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error> {
+ pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
+ where
+ D: persist::Backend,
+ {
self.wallet
.borrow_mut()
.create_tx(self.coin_selection, self.params)
}
}
-impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs, CreateTx> {
+impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> {
/// Replace the recipients already added with a new list
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
self.params.recipients = recipients;
}
// methods supported only by bump_fee
-impl<'a> TxBuilder<'a, DefaultCoinSelectionAlgorithm, BumpFee> {
+impl<'a, D> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> {
/// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
/// will attempt to find a change output to shrink instead.
TxOrdering::Untouched => {}
TxOrdering::Shuffle => {
use rand::seq::SliceRandom;
- #[cfg(test)]
- use rand::SeedableRng;
-
- #[cfg(not(test))]
let mut rng = rand::thread_rng();
- #[cfg(test)]
- let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
-
+ tx.input.shuffle(&mut rng);
tx.output.shuffle(&mut rng);
}
TxOrdering::Bip69Lexicographic => {
let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone();
- TxOrdering::Shuffle.sort_tx(&mut tx);
+ (0..40)
+ .find(|_| {
+ TxOrdering::Shuffle.sort_tx(&mut tx);
+ original_tx.input != tx.input
+ })
+ .expect("it should have moved the inputs at least once");
- assert_eq!(original_tx.input, tx.input);
- assert_ne!(original_tx.output, tx.output);
+ let mut tx = original_tx.clone();
+ (0..40)
+ .find(|_| {
+ TxOrdering::Shuffle.sort_tx(&mut tx);
+ original_tx.output != tx.output
+ })
+ .expect("it should have moved the outputs at least once");
}
#[test]
#[test]
fn test_create_tx_policy_path_no_csv() {
let descriptors = get_test_wpkh();
- let mut wallet = Wallet::new(descriptors, None, Network::Regtest).unwrap();
+ let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap();
let tx = Transaction {
version: 0,
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
receive_output_in_latest_block(&mut wallet, 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
- let mut builder = wallet.build_tx();
+ let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
builder
.add_recipient(addr.script_pubkey(), 45_000)
.enable_rbf();
let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
- let mut builder = wallet.build_tx();
+ let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
builder
.add_recipient(addr.script_pubkey(), 45_000)
.enable_rbf();
#[test]
fn test_unused_address() {
- let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet).unwrap();
assert_eq!(
#[test]
fn test_next_unused_address() {
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
- let mut wallet = Wallet::new(descriptor, None, Network::Testnet).unwrap();
+ let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet).unwrap();
assert_eq!(wallet.derivation_index(KeychainKind::External), None);
assert_eq!(
#[test]
fn test_peek_address_at_index() {
- let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet).unwrap();
assert_eq!(
#[test]
fn test_peek_address_at_index_not_derivable() {
- let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
+ let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
None, Network::Testnet).unwrap();
assert_eq!(
#[test]
fn test_returns_index_and_address() {
- let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet).unwrap();
// new index 0
fn test_get_address() {
use bdk::descriptor::template::Bip84;
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
- let mut wallet = Wallet::new(
+ let mut wallet = Wallet::new_no_persist(
Bip84(key, KeychainKind::External),
Some(Bip84(key, KeychainKind::Internal)),
Network::Regtest,
);
let mut wallet =
- Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
+ Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
assert_eq!(
wallet.get_internal_address(AddressIndex::New),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
- keychain: KeychainKind::Internal,
+ keychain: KeychainKind::External,
},
"when there's no internal descriptor it should just use external"
);
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let mut wallet =
- Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
+ Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
let mut used_set = HashSet::new();
let (mut psbt, _) = builder.finish().unwrap();
// re-create the wallet with an empty db
- let wallet_empty = Wallet::new(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap();
+ let wallet_empty =
+ Wallet::new_no_persist(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap();
// signing with an empty db means that we will only look at the psbt to infer the
// derivation index
#[test]
fn test_spend_coinbase() {
let descriptor = get_test_wpkh();
- let mut wallet = Wallet::new(descriptor, None, Network::Regtest).unwrap();
+ let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Regtest).unwrap();
let confirmation_height = 5;
wallet