Also update examples and remove bdk_persist crate.
"crates/esplora",
"crates/bitcoind_rpc",
"crates/hwi",
- "crates/persist",
"crates/testenv",
"example-crates/example_cli",
"example-crates/example_electrum",
proptest = "1.2.0"
[features]
-default = ["std", "miniscript", "persist"]
+default = ["std", "miniscript"]
std = ["bitcoin/std", "miniscript?/std"]
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
-persist = ["miniscript"]
async = ["async-trait"]
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
/// Return a reference to the internal [`SpkTxOutIndex`].
///
- /// **WARNING:** The internal index will contain lookahead spks. Refer to
+ /// **WARNING**: The internal index will contain lookahead spks. Refer to
/// [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
&self.inner
mod spk_iter;
#[cfg(feature = "miniscript")]
pub use spk_iter::*;
-#[cfg(feature = "persist")]
+#[cfg(feature = "miniscript")]
pub mod persist;
pub mod spk_client;
-//! This module is home to the [`Persist`] trait which defines the behavior of a data store
+//! This module is home to the [`PersistBackend`] trait which defines the behavior of a data store
//! required to persist changes made to BDK data structures.
//!
-//! The [`StagedPersist`] type provides a convenient wrapper around implementations of [`Persist`] that
-//! allows changes to be staged before committing them.
-//!
//! The [`CombinedChangeSet`] type encapsulates a combination of [`crate`] structures that are
//! typically persisted together.
use core::convert::Infallible;
use core::default::Default;
use core::fmt::{Debug, Display};
-use core::mem;
/// A changeset containing [`crate`] structures typically persisted together.
#[derive(Debug, Clone, PartialEq)]
///
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
/// that are to be persisted, or retrieved from persistence.
-pub trait Persist<C> {
+pub trait PersistBackend<C> {
/// The error the backend returns when it fails to write.
type WriteError: Debug + Display;
fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError>;
}
-impl<C> Persist<C> for () {
+impl<C> PersistBackend<C> for () {
type WriteError = Infallible;
type LoadError = Infallible;
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
/// that are to be persisted, or retrieved from persistence.
#[async_trait]
-pub trait PersistAsync<C> {
+pub trait PersistBackendAsync<C> {
/// The error the backend returns when it fails to write.
type WriteError: Debug + Display;
#[cfg(feature = "async")]
#[async_trait]
-impl<C> PersistAsync<C> for () {
+impl<C> PersistBackendAsync<C> for () {
type WriteError = Infallible;
type LoadError = Infallible;
Ok(None)
}
}
-
-/// `StagedPersist` adds a convenient staging area for changesets before they are persisted.
-///
-/// Not all changes to the in-memory representation needs to be written to disk right away, so
-/// [`crate::persist::StagedPersist::stage`] can be used to *stage* changes first and then
-/// [`crate::persist::StagedPersist::commit`] can be used to write changes to disk.
-pub struct StagedPersist<C, P: Persist<C>> {
- inner: P,
- stage: C,
-}
-
-impl<C, P: Persist<C>> Persist<C> for StagedPersist<C, P> {
- type WriteError = P::WriteError;
- type LoadError = P::LoadError;
-
- fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
- self.inner.write_changes(changeset)
- }
-
- fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
- self.inner.load_changes()
- }
-}
-
-impl<C, P> StagedPersist<C, P>
-where
- C: Default + Append,
- P: Persist<C>,
-{
- /// Create a new [`StagedPersist`] adding staging to an inner data store that implements
- /// [`Persist`].
- pub fn new(persist: P) -> Self {
- Self {
- inner: persist,
- stage: Default::default(),
- }
- }
-
- /// Stage a `changeset` to be committed later with [`commit`].
- ///
- /// [`commit`]: Self::commit
- pub fn stage(&mut self, changeset: C) {
- self.stage.append(changeset)
- }
-
- /// Get the changes that have not been committed yet.
- pub fn staged(&self) -> &C {
- &self.stage
- }
-
- /// Take the changes that have not been committed yet.
- ///
- /// New staged is set to default;
- pub fn take_staged(&mut self) -> C {
- mem::take(&mut self.stage)
- }
-
- /// Commit the staged changes to the underlying persistence backend.
- ///
- /// Changes that are committed (if any) are returned.
- ///
- /// # Error
- ///
- /// Returns a backend-defined error if this fails.
- pub fn commit(&mut self) -> Result<Option<C>, P::WriteError> {
- if self.staged().is_empty() {
- return Ok(None);
- }
- let staged = self.take_staged();
- self.write_changes(&staged)
- // if written successfully, take and return `self.stage`
- .map(|_| Some(staged))
- }
-
- /// Stages a new changeset and commits it (along with any other previously staged changes) to
- /// the persistence backend
- ///
- /// Convenience method for calling [`stage`] and then [`commit`].
- ///
- /// [`stage`]: Self::stage
- /// [`commit`]: Self::commit
- pub fn stage_and_commit(&mut self, changeset: C) -> Result<Option<C>, P::WriteError> {
- self.stage(changeset);
- self.commit()
- }
-}
-
-#[cfg(feature = "async")]
-/// `StagedPersistAsync` adds a convenient async staging area for changesets before they are persisted.
-///
-/// Not all changes to the in-memory representation needs to be written to disk right away, so
-/// [`StagedPersistAsync::stage`] can be used to *stage* changes first and then
-/// [`StagedPersistAsync::commit`] can be used to write changes to disk.
-pub struct StagedPersistAsync<C, P: PersistAsync<C>> {
- inner: P,
- staged: C,
-}
-
-#[cfg(feature = "async")]
-#[async_trait]
-impl<C: Send + Sync, P: PersistAsync<C> + Send> PersistAsync<C> for StagedPersistAsync<C, P> {
- type WriteError = P::WriteError;
- type LoadError = P::LoadError;
-
- async fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
- self.inner.write_changes(changeset).await
- }
-
- async fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
- self.inner.load_changes().await
- }
-}
-
-#[cfg(feature = "async")]
-impl<C, P> StagedPersistAsync<C, P>
-where
- C: Default + Append + Send + Sync,
- P: PersistAsync<C> + Send,
-{
- /// Create a new [`StagedPersistAsync`] adding staging to an inner data store that implements
- /// [`PersistAsync`].
- pub fn new(persist: P) -> Self {
- Self {
- inner: persist,
- staged: Default::default(),
- }
- }
-
- /// Stage a `changeset` to be committed later with [`commit`].
- ///
- /// [`commit`]: Self::commit
- pub fn stage(&mut self, changeset: C) {
- self.staged.append(changeset)
- }
-
- /// Get the changes that have not been committed yet.
- pub fn staged(&self) -> &C {
- &self.staged
- }
-
- /// Take the changes that have not been committed yet.
- ///
- /// New staged is set to default;
- pub fn take_staged(&mut self) -> C {
- mem::take(&mut self.staged)
- }
-
- /// Commit the staged changes to the underlying persistence backend.
- ///
- /// Changes that are committed (if any) are returned.
- ///
- /// # Error
- ///
- /// Returns a backend-defined error if this fails.
- pub async fn commit(&mut self) -> Result<Option<C>, P::WriteError> {
- if self.staged().is_empty() {
- return Ok(None);
- }
- let staged = self.take_staged();
- self.write_changes(&staged)
- .await
- // if written successfully, take and return `self.stage`
- .map(|_| Some(staged))
- }
-
- /// Stages a new changeset and commits it (along with any other previously staged changes) to
- /// the persistence backend
- ///
- /// Convenience method for calling [`stage`] and then [`commit`].
- ///
- /// [`stage`]: Self::stage
- /// [`commit`]: Self::commit
- pub async fn stage_and_commit(&mut self, changeset: C) -> Result<Option<C>, P::WriteError> {
- self.stage(changeset);
- self.commit().await
- }
-}
-
-#[cfg(test)]
-mod test {
- extern crate core;
-
- use crate::persist::{Persist, StagedPersist};
- use crate::Append;
- use std::error::Error;
- use std::fmt::{self, Display, Formatter};
- use std::prelude::rust_2015::{String, ToString};
- use TestError::FailedWrite;
-
- struct TestBackend<C: Default + Append + Clone + ToString> {
- changeset: C,
- }
-
- #[derive(Debug, Eq, PartialEq)]
- enum TestError {
- FailedWrite,
- FailedLoad,
- }
-
- impl Display for TestError {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- write!(f, "{:?}", self)
- }
- }
-
- impl Error for TestError {}
-
- #[derive(Clone, Default)]
- struct TestChangeSet(Option<String>);
-
- impl fmt::Display for TestChangeSet {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.clone().0.unwrap_or_default())
- }
- }
-
- impl Append for TestChangeSet {
- fn append(&mut self, other: Self) {
- if other.0.is_some() {
- self.0 = other.0
- }
- }
-
- fn is_empty(&self) -> bool {
- self.0.is_none()
- }
- }
-
- impl<C> Persist<C> for TestBackend<C>
- where
- C: Default + Append + Clone + ToString,
- {
- type WriteError = TestError;
- type LoadError = TestError;
-
- fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
- if changeset.to_string() == "ERROR" {
- Err(FailedWrite)
- } else {
- self.changeset = changeset.clone();
- Ok(())
- }
- }
-
- fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
- if self.changeset.to_string() == "ERROR" {
- Err(Self::LoadError::FailedLoad)
- } else {
- Ok(Some(self.changeset.clone()))
- }
- }
- }
-
- #[test]
- fn test_persist_stage_commit() {
- let backend = TestBackend {
- changeset: TestChangeSet(None),
- };
-
- let mut staged_backend = StagedPersist::new(backend);
- staged_backend.stage(TestChangeSet(Some("ONE".to_string())));
- staged_backend.stage(TestChangeSet(None));
- staged_backend.stage(TestChangeSet(Some("TWO".to_string())));
- let result = staged_backend.commit();
- assert!(matches!(result, Ok(Some(TestChangeSet(Some(v)))) if v == *"TWO".to_string()));
-
- let result = staged_backend.commit();
- assert!(matches!(result, Ok(None)));
-
- staged_backend.stage(TestChangeSet(Some("TWO".to_string())));
- let result = staged_backend.stage_and_commit(TestChangeSet(Some("ONE".to_string())));
- assert!(matches!(result, Ok(Some(TestChangeSet(Some(v)))) if v == *"ONE".to_string()));
- }
-
- #[test]
- fn test_persist_commit_error() {
- let backend = TestBackend {
- changeset: TestChangeSet(None),
- };
- let mut staged_backend = StagedPersist::new(backend);
- staged_backend.stage(TestChangeSet(Some("ERROR".to_string())));
- let result = staged_backend.commit();
- assert!(matches!(result, Err(e) if e == FailedWrite));
- }
-}
license = "MIT OR Apache-2.0"
repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk_file_store"
-description = "A simple append-only flat file implementation of Persist for Bitcoin Dev Kit."
+description = "A simple append-only flat file implementation of PersistBackend for Bitcoin Dev Kit."
keywords = ["bitcoin", "persist", "persistence", "bdk", "file"]
authors = ["Bitcoin Dev Kit Developers"]
readme = "README.md"
[dependencies]
-anyhow = { version = "1", default-features = false }
bdk_chain = { path = "../chain", version = "0.15.0", features = [ "serde", "miniscript" ] }
-bdk_persist = { path = "../persist", version = "0.3.0"}
bincode = { version = "1" }
serde = { version = "1", features = ["derive"] }
# BDK File Store
-This is a simple append-only flat file implementation of [`PersistBackend`](bdk_persist::PersistBackend).
+This is a simple append-only flat file implementation of [`PersistBackend`](bdk_chain::persist::PersistBackend).
The main structure is [`Store`] which works with any [`bdk_chain`] based changesets to persist data into a flat file.
use crate::{bincode_options, EntryIter, FileError, IterError};
-use anyhow::anyhow;
+use bdk_chain::persist::PersistBackend;
use bdk_chain::Append;
-use bdk_persist::PersistBackend;
use bincode::Options;
use std::{
fmt::{self, Debug},
impl<C> PersistBackend<C> for Store<C>
where
C: Append
+ + Debug
+ serde::Serialize
+ serde::de::DeserializeOwned
+ core::marker::Send
+ core::marker::Sync,
{
- fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()> {
+ type WriteError = io::Error;
+ type LoadError = AggregateChangesetsError<C>;
+
+ fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
self.append_changeset(changeset)
- .map_err(|e| anyhow!(e).context("failed to write changes to persistence backend"))
}
- fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>> {
+ fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
self.aggregate_changesets()
- .map_err(|e| anyhow!(e.iter_error).context("error loading from persistence backend"))
}
}
//! let first_device = devices.remove(0)?;
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
//!
-//! # let mut wallet = Wallet::new_no_persist(
+//! # let mut wallet = Wallet::new(
//! # "",
//! # "",
//! # Network::Testnet,
+++ /dev/null
-[package]
-name = "bdk_persist"
-homepage = "https://bitcoindevkit.org"
-version = "0.3.0"
-repository = "https://github.com/bitcoindevkit/bdk"
-documentation = "https://docs.rs/bdk_persist"
-description = "Types that define data persistence of a BDK wallet"
-keywords = ["bitcoin", "wallet", "persistence", "database"]
-readme = "README.md"
-license = "MIT OR Apache-2.0"
-authors = ["Bitcoin Dev Kit Developers"]
-edition = "2021"
-rust-version = "1.63"
-
-[dependencies]
-anyhow = { version = "1", default-features = false }
-bdk_chain = { path = "../chain", version = "0.15.0", default-features = false }
-
-[features]
-default = ["bdk_chain/std", "miniscript"]
-serde = ["bdk_chain/serde"]
-miniscript = ["bdk_chain/miniscript"]
+++ /dev/null
-# BDK Persist
-
-This crate is home to the [`PersistBackend`] trait which defines the behavior of a database to perform the task of persisting changes made to BDK data structures.
-
-The [`Persist`] type provides a convenient wrapper around a [`PersistBackend`] that allows staging changes before committing them.
+++ /dev/null
-#![cfg(feature = "miniscript")]
-
-use bdk_chain::{bitcoin::Network, indexed_tx_graph, keychain, local_chain, Anchor, Append};
-
-/// Changes from a combination of [`bdk_chain`] structures.
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(
- feature = "serde",
- derive(bdk_chain::serde::Deserialize, bdk_chain::serde::Serialize),
- serde(
- crate = "bdk_chain::serde",
- bound(
- deserialize = "A: Ord + bdk_chain::serde::Deserialize<'de>, K: Ord + bdk_chain::serde::Deserialize<'de>",
- serialize = "A: Ord + bdk_chain::serde::Serialize, K: Ord + bdk_chain::serde::Serialize",
- ),
- )
-)]
-pub struct CombinedChangeSet<K, A> {
- /// Changes to the [`LocalChain`](local_chain::LocalChain).
- pub chain: local_chain::ChangeSet,
- /// Changes to [`IndexedTxGraph`](indexed_tx_graph::IndexedTxGraph).
- pub indexed_tx_graph: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
- /// Stores the network type of the transaction data.
- pub network: Option<Network>,
-}
-
-impl<K, A> Default for CombinedChangeSet<K, A> {
- fn default() -> Self {
- Self {
- chain: Default::default(),
- indexed_tx_graph: Default::default(),
- network: None,
- }
- }
-}
-
-impl<K: Ord, A: Anchor> Append for CombinedChangeSet<K, A> {
- fn append(&mut self, other: Self) {
- Append::append(&mut self.chain, other.chain);
- Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph);
- if other.network.is_some() {
- debug_assert!(
- self.network.is_none() || self.network == other.network,
- "network type must either be just introduced or remain the same"
- );
- self.network = other.network;
- }
- }
-
- fn is_empty(&self) -> bool {
- self.chain.is_empty() && self.indexed_tx_graph.is_empty() && self.network.is_none()
- }
-}
-
-impl<K, A> From<local_chain::ChangeSet> for CombinedChangeSet<K, A> {
- fn from(chain: local_chain::ChangeSet) -> Self {
- Self {
- chain,
- ..Default::default()
- }
- }
-}
-
-impl<K, A> From<indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>>
- for CombinedChangeSet<K, A>
-{
- fn from(indexed_tx_graph: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>) -> Self {
- Self {
- indexed_tx_graph,
- ..Default::default()
- }
- }
-}
+++ /dev/null
-#![doc = include_str!("../README.md")]
-#![no_std]
-#![warn(missing_docs)]
-
-mod changeset;
-mod persist;
-pub use changeset::*;
-pub use persist::*;
+++ /dev/null
-extern crate alloc;
-use alloc::boxed::Box;
-use bdk_chain::Append;
-use core::fmt;
-
-/// `Persist` wraps a [`PersistBackend`] to create a convenient staging area for changes (`C`)
-/// before they are persisted.
-///
-/// Not all changes to the in-memory representation needs to be written to disk right away, so
-/// [`Persist::stage`] can be used to *stage* changes first and then [`Persist::commit`] can be used
-/// to write changes to disk.
-pub struct Persist<C> {
- backend: Box<dyn PersistBackend<C> + Send + Sync>,
- stage: C,
-}
-
-impl<C: fmt::Debug> fmt::Debug for Persist<C> {
- fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
- write!(fmt, "{:?}", self.stage)?;
- Ok(())
- }
-}
-
-impl<C> Persist<C>
-where
- C: Default + Append,
-{
- /// Create a new [`Persist`] from [`PersistBackend`].
- pub fn new(backend: impl PersistBackend<C> + Send + Sync + 'static) -> Self {
- let backend = Box::new(backend);
- Self {
- backend,
- stage: Default::default(),
- }
- }
-
- /// Stage a `changeset` to be committed later with [`commit`].
- ///
- /// [`commit`]: Self::commit
- pub fn stage(&mut self, changeset: C) {
- self.stage.append(changeset)
- }
-
- /// Get the changes that have not been committed yet.
- pub fn staged(&self) -> &C {
- &self.stage
- }
-
- /// Commit the staged changes to the underlying persistence backend.
- ///
- /// Changes that are committed (if any) are returned.
- ///
- /// # Error
- ///
- /// Returns a backend-defined error if this fails.
- pub fn commit(&mut self) -> anyhow::Result<Option<C>> {
- if self.stage.is_empty() {
- return Ok(None);
- }
- self.backend
- .write_changes(&self.stage)
- // if written successfully, take and return `self.stage`
- .map(|_| Some(core::mem::take(&mut self.stage)))
- }
-
- /// Stages a new changeset and commits it (along with any other previously staged changes) to
- /// the persistence backend
- ///
- /// Convenience method for calling [`stage`] and then [`commit`].
- ///
- /// [`stage`]: Self::stage
- /// [`commit`]: Self::commit
- pub fn stage_and_commit(&mut self, changeset: C) -> anyhow::Result<Option<C>> {
- self.stage(changeset);
- self.commit()
- }
-}
-
-/// A persistence backend for [`Persist`].
-///
-/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
-/// that are to be persisted, or retrieved from persistence.
-pub trait PersistBackend<C> {
- /// Writes a changeset to the persistence backend.
- ///
- /// It is up to the backend what it does with this. It could store every changeset in a list or
- /// it inserts the actual changes into a more structured database. All it needs to guarantee is
- /// that [`load_from_persistence`] restores a keychain tracker to what it should be if all
- /// changesets had been applied sequentially.
- ///
- /// [`load_from_persistence`]: Self::load_from_persistence
- fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()>;
-
- /// Return the aggregate changeset `C` from persistence.
- fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>>;
-}
-
-impl<C> PersistBackend<C> for () {
- fn write_changes(&mut self, _changeset: &C) -> anyhow::Result<()> {
- Ok(())
- }
-
- fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>> {
- Ok(None)
- }
-}
license = "MIT OR Apache-2.0"
repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk_sqlite"
-description = "A simple SQLite based implementation of Persist for Bitcoin Dev Kit."
+description = "A simple SQLite based implementation of PersistBackend for Bitcoin Dev Kit."
keywords = ["bitcoin", "persist", "persistence", "bdk", "sqlite"]
authors = ["Bitcoin Dev Kit Developers"]
readme = "README.md"
[dependencies]
-anyhow = { version = "1", default-features = false }
bdk_chain = { path = "../chain", version = "0.15.0", features = ["serde", "miniscript"] }
-bdk_persist = { path = "../persist", version = "0.3.0", features = ["serde"] }
rusqlite = { version = "0.31.0", features = ["bundled"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
\ No newline at end of file
# BDK SQLite
-This is a simple [SQLite] relational database schema backed implementation of [`PersistBackend`](bdk_persist::PersistBackend).
+This is a simple [SQLite] relational database schema backed implementation of `PersistBackend`.
-The main structure is `Store` which persists [`bdk_persist`] `CombinedChangeSet` data into a SQLite database file.
+The main structure is `Store` which persists `CombinedChangeSet` data into a SQLite database file.
-[`bdk_persist`]:https://docs.rs/bdk_persist/latest/bdk_persist/
+<!-- [`PersistBackend`]: bdk_chain::persist::PersistBackend -->
[SQLite]: https://www.sqlite.org/index.html
use std::sync::{Arc, Mutex};
use crate::Error;
+use bdk_chain::persist::{CombinedChangeSet, PersistBackend};
use bdk_chain::{
indexed_tx_graph, keychain, local_chain, tx_graph, Anchor, Append, DescriptorExt, DescriptorId,
};
-use bdk_persist::CombinedChangeSet;
/// Persists data in to a relational schema based [SQLite] database file.
///
}
}
-impl<K, A, C> bdk_persist::PersistBackend<C> for Store<K, A>
+impl<K, A> PersistBackend<CombinedChangeSet<K, A>> for Store<K, A>
where
K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
A: Anchor + for<'de> Deserialize<'de> + Serialize + Send,
- C: Clone + From<CombinedChangeSet<K, A>> + Into<CombinedChangeSet<K, A>>,
{
- fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()> {
- self.write(&changeset.clone().into())
- .map_err(|e| anyhow::anyhow!(e).context("unable to write changes to sqlite database"))
+ type WriteError = Error;
+ type LoadError = Error;
+
+ fn write_changes(
+ &mut self,
+ changeset: &CombinedChangeSet<K, A>,
+ ) -> Result<(), Self::WriteError> {
+ self.write(changeset)
}
- fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>> {
+ fn load_changes(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Self::LoadError> {
self.read()
- .map(|c| c.map(Into::into))
- .map_err(|e| anyhow::anyhow!(e).context("unable to read changes from sqlite database"))
}
}
use bdk_chain::bitcoin::Network::Testnet;
use bdk_chain::bitcoin::{secp256k1, BlockHash, OutPoint};
use bdk_chain::miniscript::Descriptor;
+ use bdk_chain::persist::{CombinedChangeSet, PersistBackend};
use bdk_chain::{
indexed_tx_graph, keychain, tx_graph, BlockId, ConfirmationHeightAnchor,
ConfirmationTimeHeightAnchor, DescriptorExt,
};
- use bdk_persist::PersistBackend;
use std::str::FromStr;
use std::sync::Arc;
}
#[test]
- fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor(
- ) -> anyhow::Result<()> {
+ fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor() {
let (test_changesets, agg_test_changesets) =
create_test_changesets(&|height, time, hash| ConfirmationTimeHeightAnchor {
confirmation_height: height,
store.write_changes(changeset).expect("write changeset");
});
- let agg_changeset = store.load_from_persistence().expect("aggregated changeset");
+ let agg_changeset = store.load_changes().expect("aggregated changeset");
assert_eq!(agg_changeset, Some(agg_test_changesets));
- Ok(())
}
#[test]
- fn insert_and_load_aggregate_changesets_with_confirmation_height_anchor() -> anyhow::Result<()>
- {
+ fn insert_and_load_aggregate_changesets_with_confirmation_height_anchor() {
let (test_changesets, agg_test_changesets) =
create_test_changesets(&|height, _time, hash| ConfirmationHeightAnchor {
confirmation_height: height,
store.write_changes(changeset).expect("write changeset");
});
- let agg_changeset = store.load_from_persistence().expect("aggregated changeset");
+ let agg_changeset = store.load_changes().expect("aggregated changeset");
assert_eq!(agg_changeset, Some(agg_test_changesets));
- Ok(())
}
#[test]
- fn insert_and_load_aggregate_changesets_with_blockid_anchor() -> anyhow::Result<()> {
+ fn insert_and_load_aggregate_changesets_with_blockid_anchor() {
let (test_changesets, agg_test_changesets) =
create_test_changesets(&|height, _time, hash| BlockId { height, hash });
store.write_changes(changeset).expect("write changeset");
});
- let agg_changeset = store.load_from_persistence().expect("aggregated changeset");
+ let agg_changeset = store.load_changes().expect("aggregated changeset");
assert_eq!(agg_changeset, Some(agg_test_changesets));
- Ok(())
}
fn create_test_changesets<A: Anchor + Copy>(
rust-version = "1.63"
[dependencies]
-anyhow = { version = "1", default-features = false }
rand = "^0.8"
miniscript = { version = "12.0.0", features = ["serde"], default-features = false }
bitcoin = { version = "0.32.0", features = ["serde", "base64", "rand-std"], default-features = false }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
bdk_chain = { path = "../chain", version = "0.15.0", features = ["miniscript", "serde"], default-features = false }
-bdk_persist = { path = "../persist", version = "0.3.0", features = ["miniscript", "serde"], default-features = false }
# Optional dependencies
bip39 = { version = "2.0", optional = true }
## Persistence
-To persist the `Wallet` on disk, it must be constructed with a [`PersistBackend`] implementation.
+To persist `Wallet` state data on disk use an implementation of the [`PersistBackend`] trait.
**Implementations**
* [`bdk_file_store`]: A simple flat-file implementation of [`PersistBackend`].
+* [`bdk_sqlite`]: A simple sqlite implementation of [`PersistBackend`].
**Example**
fn main() {
// Create a new file `Store`.
- let db = bdk_file_store::Store::<ChangeSet>::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store");
+ let mut db = bdk_file_store::Store::<ChangeSet>::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store");
let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)";
- let mut wallet = Wallet::new_or_load(descriptor, change_descriptor, db, Network::Testnet).expect("create or load wallet");
+ let changeset = db.load_changes().expect("changeset loaded");
+ let mut wallet = Wallet::new_or_load(descriptor, change_descriptor, changeset, Network::Testnet).expect("create or load wallet");
// Insert a single `TxOut` at `OutPoint` into the wallet.
let _ = wallet.insert_txout(outpoint, txout);
- wallet.commit().expect("must write to database");
+ wallet.commit_to(&mut db).expect("must commit changes to database");
}
```
<!-- use bdk_wallet::bitcoin::Network; -->
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
-<!-- let wallet = Wallet::new_no_persist( -->
+<!-- let wallet = Wallet::new( -->
<!-- "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
<!-- Network::Testnet, -->
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
<!-- let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); -->
-<!-- let wallet = Wallet::new_no_persist( -->
+<!-- let wallet = Wallet::new( -->
<!-- "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
<!-- Network::Testnet, -->
<!-- use bdk_wallet::bitcoin::Network; -->
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
-<!-- let wallet = Wallet::new_no_persist( -->
+<!-- let wallet = Wallet::new( -->
<!-- "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", -->
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), -->
<!-- Network::Testnet, -->
conditions.
[`Wallet`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/wallet/struct.Wallet.html
-[`PersistBackend`]: https://docs.rs/bdk_chain/latest/bdk_chain/trait.PersistBackend.html
+[`PersistBackend`]: https://docs.rs/bdk_chain/latest/bdk_chain/persist/trait.PersistBackend.html
[`bdk_chain`]: https://docs.rs/bdk_chain/latest
[`bdk_file_store`]: https://docs.rs/bdk_file_store/latest
+[`bdk_sqlite`]: https://docs.rs/bdk_sqlite/latest
[`bdk_electrum`]: https://docs.rs/bdk_electrum/latest
[`bdk_esplora`]: https://docs.rs/bdk_esplora/latest
[`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest
);
// Create a new wallet from descriptors
- let mut wallet = Wallet::new_no_persist(&descriptor, &internal_descriptor, Network::Regtest)?;
+ let mut wallet = Wallet::new(&descriptor, &internal_descriptor, Network::Regtest)?;
println!(
"First derived address from the descriptor: \n{}",
- wallet.next_unused_address(KeychainKind::External)?,
+ wallet.next_unused_address(KeychainKind::External),
);
// BDK also has it's own `Policy` structure to represent the spending condition in a more
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
-/// let mut wallet =
-/// Wallet::new_no_persist(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
+/// let mut wallet = Wallet::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
///
/// assert_eq!(
/// wallet
-/// .next_unused_address(KeychainKind::External)?
+/// .next_unused_address(KeychainKind::External)
/// .to_string(),
/// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
/// );
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// P2Wpkh_P2Sh(key_external),
/// P2Wpkh_P2Sh(key_internal),
/// Network::Testnet,
///
/// assert_eq!(
/// wallet
-/// .next_unused_address(KeychainKind::External)?
+/// .next_unused_address(KeychainKind::External)
/// .to_string(),
/// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
/// );
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
-/// let mut wallet =
-/// Wallet::new_no_persist(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
+/// let mut wallet = Wallet::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
///
/// assert_eq!(
/// wallet
-/// .next_unused_address(KeychainKind::External)?
+/// .next_unused_address(KeychainKind::External)
/// .to_string(),
/// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
/// );
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
-/// let mut wallet =
-/// Wallet::new_no_persist(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
+/// let mut wallet = Wallet::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
///
/// assert_eq!(
/// wallet
-/// .next_unused_address(KeychainKind::External)?
+/// .next_unused_address(KeychainKind::External)
/// .to_string(),
/// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
/// );
/// use bdk_wallet::template::Bip44;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip44(key.clone(), KeychainKind::External),
/// Bip44(key, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Bip44Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
/// use bdk_wallet::template::Bip49;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip49(key.clone(), KeychainKind::External),
/// Bip49(key, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Bip49Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
/// use bdk_wallet::template::Bip84;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip84(key.clone(), KeychainKind::External),
/// Bip84(key, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Bip84Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
/// use bdk_wallet::template::Bip86;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip86(key.clone(), KeychainKind::External),
/// Bip86(key, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
+/// let mut wallet = Wallet::new(
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
/// Bip86Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet,
/// )?;
///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
//! # use bitcoin::*;
//! # use bdk_wallet::wallet::{self, ChangeSet, coin_selection::*, coin_selection};
//! # use bdk_wallet::wallet::error::CreateTxError;
-//! # use bdk_persist::PersistBackend;
//! # use bdk_wallet::*;
//! # use bdk_wallet::wallet::coin_selection::decide_change;
//! # use anyhow::Error;
pub enum CreateTxError {
/// There was a problem with the descriptors passed in
Descriptor(DescriptorError),
- /// We were unable to load wallet data from or write wallet data to the persistence backend
- Persist(anyhow::Error),
/// There was a problem while extracting and manipulating policies
Policy(PolicyError),
/// Spending policy is not compatible with this [`KeychainKind`]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Descriptor(e) => e.fmt(f),
- Self::Persist(e) => {
- write!(
- f,
- "failed to load wallet data from or write wallet data to persistence backend: {}",
- e
- )
- }
Self::Policy(e) => e.fmt(f),
CreateTxError::SpendingPolicyRequired(keychain_kind) => {
write!(f, "Spending policy required: {:?}", keychain_kind)
//! }"#;
//!
//! let import = FullyNodedExport::from_str(import)?;
-//! let wallet = Wallet::new_no_persist(
+//! let wallet = Wallet::new(
//! &import.descriptor(),
//! &import.change_descriptor().expect("change descriptor"),
//! Network::Testnet,
//! # use bitcoin::*;
//! # use bdk_wallet::wallet::export::*;
//! # use bdk_wallet::*;
-//! let wallet = Wallet::new_no_persist(
+//! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
//! Network::Testnet,
use crate::wallet::Wallet;
fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
- let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap();
+ let mut wallet = Wallet::new(descriptor, change_descriptor, network).unwrap();
let transaction = Transaction {
input: vec![],
output: vec![],
//! let first_device = devices.remove(0)?;
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
//!
-//! # let mut wallet = Wallet::new_no_persist(
+//! # let mut wallet = Wallet::new(
//! # "",
//! # None,
//! # Network::Testnet,
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
Indexed, IndexedTxGraph,
};
-use bdk_persist::{Persist, PersistBackend};
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
use bitcoin::{
use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
use bitcoin::{constants::genesis_block, Amount};
use core::fmt;
+use core::mem;
use core::ops::Deref;
use descriptor::error::Error as DescriptorError;
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
/// 1. output *descriptors* from which it can derive addresses.
/// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors.
///
+/// The user is responsible for loading and writing wallet changes using an implementation of
+/// [`PersistBackend`]. See individual functions and example for instructions on when [`Wallet`]
+/// state needs to be persisted.
+///
+/// [`PersistBackend`]: bdk_chain::persist::PersistBackend
/// [`signer`]: crate::signer
#[derive(Debug)]
pub struct Wallet {
change_signers: Arc<SignersContainer>,
chain: LocalChain,
indexed_graph: IndexedTxGraph<ConfirmationTimeHeightAnchor, KeychainTxOutIndex<KeychainKind>>,
- persist: Persist<ChangeSet>,
+ stage: ChangeSet,
network: Network,
secp: SecpCtx,
}
}
/// The changes made to a wallet by applying an [`Update`].
-pub type ChangeSet = bdk_persist::CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>;
+pub type ChangeSet =
+ bdk_chain::persist::CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>;
/// A derived address and the index it was found at.
/// For convenience this automatically derefs to `Address`
}
}
-impl Wallet {
- /// Creates a wallet that does not persist data.
- pub fn new_no_persist<E: IntoWalletDescriptor>(
- descriptor: E,
- change_descriptor: E,
- network: Network,
- ) -> Result<Self, DescriptorError> {
- Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
- NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
- NewError::Descriptor(e) => e,
- NewError::Persist(_) => 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<E: IntoWalletDescriptor>(
- descriptor: E,
- change_descriptor: E,
- network: Network,
- genesis_hash: BlockHash,
- ) -> Result<Self, crate::descriptor::DescriptorError> {
- Self::new_with_genesis_hash(descriptor, change_descriptor, (), network, genesis_hash)
- .map_err(|e| match e {
- NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
- NewError::Descriptor(e) => e,
- NewError::Persist(_) => unreachable!("mock-write must always succeed"),
- })
- }
-}
-
/// The error type when constructing a fresh [`Wallet`].
///
/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
#[derive(Debug)]
pub enum NewError {
- /// Database already has data.
- NonEmptyDatabase,
/// 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.
- Persist(anyhow::Error),
}
impl fmt::Display for NewError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- NewError::NonEmptyDatabase => write!(
- f,
- "database already has data - use `load` or `new_or_load` methods instead"
- ),
NewError::Descriptor(e) => e.fmt(f),
- NewError::Persist(e) => e.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for NewError {}
-/// The error type when loading a [`Wallet`] from persistence.
+/// The error type when loading a [`Wallet`] from a [`ChangeSet`].
///
-/// Method [`load`] may return this error.
+/// Method [`load_from_changeset`] may return this error.
///
-/// [`load`]: Wallet::load
+/// [`load_from_changeset`]: Wallet::load_from_changeset
#[derive(Debug)]
pub enum LoadError {
/// There was a problem with the passed-in descriptor(s).
Descriptor(crate::descriptor::DescriptorError),
- /// Loading data from the persistence backend failed.
- Persist(anyhow::Error),
- /// Wallet not initialized, persistence backend is empty.
- NotInitialized,
/// Data loaded from persistence is missing network type.
MissingNetwork,
/// Data loaded from persistence is missing genesis hash.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LoadError::Descriptor(e) => e.fmt(f),
- LoadError::Persist(e) => e.fmt(f),
- LoadError::NotInitialized => {
- write!(f, "wallet is not initialized, persistence backend is empty")
- }
LoadError::MissingNetwork => write!(f, "loaded data is missing network type"),
LoadError::MissingGenesis => write!(f, "loaded data is missing genesis hash"),
LoadError::MissingDescriptor(k) => {
pub enum NewOrLoadError {
/// There is a problem with the passed-in descriptor.
Descriptor(crate::descriptor::DescriptorError),
- /// Either writing to or loading from the persistence backend failed.
- Persist(anyhow::Error),
- /// Wallet is not initialized, persistence backend is empty.
- NotInitialized,
/// The loaded genesis hash does not match what was provided.
LoadedGenesisDoesNotMatch {
/// The expected genesis block hash.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NewOrLoadError::Descriptor(e) => e.fmt(f),
- NewOrLoadError::Persist(e) => write!(
- f,
- "failed to either write to or load from persistence, {}",
- e
- ),
- NewOrLoadError::NotInitialized => {
- write!(f, "wallet is not initialized, persistence backend is empty")
- }
NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
}
pub fn new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: E,
- db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network,
) -> Result<Self, NewError> {
let genesis_hash = genesis_block(network).block_hash();
- Self::new_with_genesis_hash(descriptor, change_descriptor, db, network, genesis_hash)
+ Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
}
/// Initialize an empty [`Wallet`] with a custom genesis hash.
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: E,
- mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network,
genesis_hash: BlockHash,
) -> Result<Self, NewError> {
- if let Ok(changeset) = db.load_from_persistence() {
- if changeset.is_some() {
- return Err(NewError::NonEmptyDatabase);
- }
- }
let secp = Secp256k1::new();
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
let mut index = KeychainTxOutIndex::<KeychainKind>::default();
let indexed_graph = IndexedTxGraph::new(index);
- let mut persist = Persist::new(db);
- persist.stage(ChangeSet {
+ let staged = ChangeSet {
chain: chain_changeset,
indexed_tx_graph: indexed_graph.initial_changeset(),
network: Some(network),
- });
- persist.commit().map_err(NewError::Persist)?;
+ };
Ok(Wallet {
signers,
network,
chain,
indexed_graph,
- persist,
+ stage: staged,
secp,
})
}
- /// Load [`Wallet`] from the given persistence backend.
+ /// Stage a ['ChangeSet'] to be persisted later.
+ ///
+ /// [`commit`]: Self::commit
+ fn stage(&mut self, changeset: ChangeSet) {
+ self.stage.append(changeset)
+ }
+
+ /// Take the staged [`ChangeSet`] to be persisted now.
+ pub fn take_staged(&mut self) -> ChangeSet {
+ mem::take(&mut self.stage)
+ }
+
+ /// Load [`Wallet`] from the given previously persisted [`ChangeSet`].
///
/// Note that the descriptor secret keys are not persisted to the db; this means that after
/// calling this method the [`Wallet`] **won't** know the secret keys, and as such, won't be
/// # use bdk_sqlite::{Store, rusqlite::Connection};
/// #
/// # fn main() -> Result<(), anyhow::Error> {
+ /// # use bdk_chain::persist::PersistBackend;
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
/// # let file_path = temp_dir.path().join("store.db");
/// # let conn = Connection::open(file_path).expect("must open connection");
- /// # let db = Store::new(conn).expect("must create db");
+ /// # let mut db = Store::new(conn).expect("must create db");
/// let secp = Secp256k1::new();
///
/// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
///
/// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
/// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
- ///
- /// let mut wallet = Wallet::load(db)?;
+ /// let changeset = db.load_changes()?.expect("there must be an existing changeset");
+ /// let mut wallet = Wallet::load_from_changeset(changeset)?;
///
/// external_signer_container.signers().into_iter()
/// .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
///
/// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
/// passed-in descriptors to the [`Wallet`].
- pub fn load(
- mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
- ) -> Result<Self, LoadError> {
- let changeset = db
- .load_from_persistence()
- .map_err(LoadError::Persist)?
- .ok_or(LoadError::NotInitialized)?;
- Self::load_from_changeset(db, changeset)
- }
-
- fn load_from_changeset(
- db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
- changeset: ChangeSet,
- ) -> Result<Self, LoadError> {
+ pub fn load_from_changeset(changeset: ChangeSet) -> Result<Self, LoadError> {
let secp = Secp256k1::new();
let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
let chain =
let mut indexed_graph = IndexedTxGraph::new(index);
indexed_graph.apply_changeset(changeset.indexed_tx_graph);
- let persist = Persist::new(db);
+ let stage = ChangeSet::default();
Ok(Wallet {
signers,
change_signers,
chain,
indexed_graph,
- persist,
+ stage,
network,
secp,
})
}
- /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist.
+ /// Either loads [`Wallet`] from the given [`ChangeSet`] or initializes it if one does not exist.
+ ///
+ /// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
+ ///
+ /// ```rust,no_run
+ /// # use bdk_chain::persist::PersistBackend;
+ /// # use bdk_wallet::Wallet;
+ /// # use bdk_sqlite::{Store, rusqlite::Connection};
+ /// # use bitcoin::Network::Testnet;
+ /// # let conn = Connection::open_in_memory().expect("must open connection");
+ /// let mut db = Store::new(conn).expect("must create db");
+ /// let changeset = db.load_changes()?;
+ ///
+ /// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
+ /// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
///
- /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
+ /// let mut wallet = Wallet::new_or_load(external_descriptor, internal_descriptor, changeset, Testnet)?;
+ /// # Ok::<(), anyhow::Error>(())
+ /// ```
pub fn new_or_load<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: E,
- db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+ changeset: Option<ChangeSet>,
network: Network,
) -> Result<Self, NewOrLoadError> {
let genesis_hash = genesis_block(network).block_hash();
Self::new_or_load_with_genesis_hash(
descriptor,
change_descriptor,
- db,
+ changeset,
network,
genesis_hash,
)
}
- /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist, using the
+ /// Either loads [`Wallet`] from a [`ChangeSet`] or initializes it if one does not exist, using the
/// provided descriptor, change descriptor, network, and custom genesis hash.
///
- /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
+ /// This method will fail if the loaded [`ChangeSet`] 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<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: E,
- mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+ changeset: Option<ChangeSet>,
network: Network,
genesis_hash: BlockHash,
) -> Result<Self, NewOrLoadError> {
- let changeset = db
- .load_from_persistence()
- .map_err(NewOrLoadError::Persist)?;
- match changeset {
- Some(changeset) => {
- let mut wallet = Self::load_from_changeset(db, changeset).map_err(|e| match e {
- LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
- LoadError::Persist(e) => NewOrLoadError::Persist(e),
- LoadError::NotInitialized => NewOrLoadError::NotInitialized,
- LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
- expected: network,
+ if let Some(changeset) = changeset {
+ let mut wallet = Self::load_from_changeset(changeset).map_err(|e| match e {
+ LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
+ LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
+ expected: network,
+ got: None,
+ },
+ LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
+ expected: genesis_hash,
+ got: None,
+ },
+ LoadError::MissingDescriptor(keychain) => {
+ NewOrLoadError::LoadedDescriptorDoesNotMatch {
got: None,
- },
- LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
- expected: genesis_hash,
- got: None,
- },
- LoadError::MissingDescriptor(keychain) => {
- NewOrLoadError::LoadedDescriptorDoesNotMatch {
- got: None,
- keychain,
- }
+ keychain,
}
- })?;
- if wallet.network != network {
- return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
- expected: network,
- got: Some(wallet.network),
- });
- }
- if wallet.chain.genesis_hash() != genesis_hash {
- return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
- expected: genesis_hash,
- got: Some(wallet.chain.genesis_hash()),
- });
- }
-
- let (expected_descriptor, expected_descriptor_keymap) = descriptor
- .into_wallet_descriptor(&wallet.secp, network)
- .map_err(NewOrLoadError::Descriptor)?;
- let wallet_descriptor = wallet.public_descriptor(KeychainKind::External);
- if wallet_descriptor != &expected_descriptor {
- return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
- got: Some(wallet_descriptor.clone()),
- keychain: KeychainKind::External,
- });
- }
- // if expected descriptor has private keys add them as new signers
- if !expected_descriptor_keymap.is_empty() {
- let signer_container = SignersContainer::build(
- expected_descriptor_keymap,
- &expected_descriptor,
- &wallet.secp,
- );
- signer_container.signers().into_iter().for_each(|signer| {
- wallet.add_signer(
- KeychainKind::External,
- SignerOrdering::default(),
- signer.clone(),
- )
- });
}
+ })?;
+ if wallet.network != network {
+ return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
+ expected: network,
+ got: Some(wallet.network),
+ });
+ }
+ if wallet.chain.genesis_hash() != genesis_hash {
+ return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
+ expected: genesis_hash,
+ got: Some(wallet.chain.genesis_hash()),
+ });
+ }
- let (expected_change_descriptor, expected_change_descriptor_keymap) =
- change_descriptor
- .into_wallet_descriptor(&wallet.secp, network)
- .map_err(NewOrLoadError::Descriptor)?;
- let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal);
- if wallet_change_descriptor != &expected_change_descriptor {
- return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
- got: Some(wallet_change_descriptor.clone()),
- keychain: KeychainKind::Internal,
- });
- }
- // if expected change descriptor has private keys add them as new signers
- if !expected_change_descriptor_keymap.is_empty() {
- let signer_container = SignersContainer::build(
- expected_change_descriptor_keymap,
- &expected_change_descriptor,
- &wallet.secp,
- );
- signer_container.signers().into_iter().for_each(|signer| {
- wallet.add_signer(
- KeychainKind::Internal,
- SignerOrdering::default(),
- signer.clone(),
- )
- });
- }
+ let (expected_descriptor, expected_descriptor_keymap) = descriptor
+ .into_wallet_descriptor(&wallet.secp, network)
+ .map_err(NewOrLoadError::Descriptor)?;
+ let wallet_descriptor = wallet.public_descriptor(KeychainKind::External);
+ if wallet_descriptor != &expected_descriptor {
+ return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
+ got: Some(wallet_descriptor.clone()),
+ keychain: KeychainKind::External,
+ });
+ }
+ // if expected descriptor has private keys add them as new signers
+ if !expected_descriptor_keymap.is_empty() {
+ let signer_container = SignersContainer::build(
+ expected_descriptor_keymap,
+ &expected_descriptor,
+ &wallet.secp,
+ );
+ signer_container.signers().into_iter().for_each(|signer| {
+ wallet.add_signer(
+ KeychainKind::External,
+ SignerOrdering::default(),
+ signer.clone(),
+ )
+ });
+ }
- Ok(wallet)
+ let (expected_change_descriptor, expected_change_descriptor_keymap) = change_descriptor
+ .into_wallet_descriptor(&wallet.secp, network)
+ .map_err(NewOrLoadError::Descriptor)?;
+ let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal);
+ if wallet_change_descriptor != &expected_change_descriptor {
+ return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
+ got: Some(wallet_change_descriptor.clone()),
+ keychain: KeychainKind::Internal,
+ });
}
- None => Self::new_with_genesis_hash(
- descriptor,
- change_descriptor,
- db,
- network,
- genesis_hash,
- )
- .map_err(|e| match e {
- NewError::NonEmptyDatabase => {
- unreachable!("database is already checked to have no data")
- }
- NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
- NewError::Persist(e) => NewOrLoadError::Persist(e),
- }),
+ // if expected change descriptor has private keys add them as new signers
+ if !expected_change_descriptor_keymap.is_empty() {
+ let signer_container = SignersContainer::build(
+ expected_change_descriptor_keymap,
+ &expected_change_descriptor,
+ &wallet.secp,
+ );
+ signer_container.signers().into_iter().for_each(|signer| {
+ wallet.add_signer(
+ KeychainKind::Internal,
+ SignerOrdering::default(),
+ signer.clone(),
+ )
+ });
+ }
+
+ Ok(wallet)
+ } else {
+ Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
+ .map_err(|e| match e {
+ NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
+ })
}
}
/// Attempt to reveal the next address of the given `keychain`.
///
- /// This will increment the internal derivation index. If the keychain's descriptor doesn't
+ /// This will increment the keychain's derivation index. If the keychain's descriptor doesn't
/// contain a wildcard or every address is already revealed up to the maximum derivation
/// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki),
- /// then returns the last revealed address.
+ /// then the last revealed address will be returned.
///
- /// # Errors
+ /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more
+ /// calls to this method before closing the wallet. For example:
///
- /// If writing to persistent storage fails.
- pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
- let ((index, spk), index_changeset) = self
- .indexed_graph
- .index
+ /// ```rust,no_run
+ /// # use bdk_chain::persist::PersistBackend;
+ /// # use bdk_wallet::wallet::{Wallet, ChangeSet};
+ /// # use bdk_wallet::KeychainKind;
+ /// # use bdk_sqlite::{Store, rusqlite::Connection};
+ /// # let conn = Connection::open_in_memory().expect("must open connection");
+ /// # let mut db = Store::new(conn).expect("must create store");
+ /// # let changeset = ChangeSet::default();
+ /// # let mut wallet = Wallet::load_from_changeset(changeset).expect("load wallet");
+ /// let next_address = wallet.reveal_next_address(KeychainKind::External);
+ /// db.write_changes(&wallet.take_staged())?;
+ ///
+ /// // Now it's safe to show the user their next address!
+ /// println!("Next address: {}", next_address.address);
+ /// # Ok::<(), anyhow::Error>(())
+ /// ```
+ pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> AddressInfo {
+ let index = &mut self.indexed_graph.index;
+ let stage = &mut self.stage;
+
+ let ((index, spk), index_changeset) = index
.reveal_next_spk(&keychain)
.expect("keychain must exist");
- self.persist
- .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
+ stage.append(indexed_tx_graph::ChangeSet::from(index_changeset).into());
- Ok(AddressInfo {
+ AddressInfo {
index,
address: Address::from_script(spk.as_script(), self.network)
.expect("must have address form"),
keychain,
- })
+ }
}
/// Reveal addresses up to and including the target `index` and return an iterator
/// possible index. If all addresses up to the given `index` are already revealed, then
/// no new addresses are returned.
///
- /// # Errors
- ///
- /// If writing to persistent storage fails.
+ /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more
+ /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`].
pub fn reveal_addresses_to(
&mut self,
keychain: KeychainKind,
index: u32,
- ) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
+ ) -> impl Iterator<Item = AddressInfo> + '_ {
let (spks, index_changeset) = self
.indexed_graph
.index
.reveal_to_target(&keychain, index)
.expect("keychain must exist");
- self.persist
- .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
+ self.stage(indexed_tx_graph::ChangeSet::from(index_changeset).into());
- Ok(spks.into_iter().map(move |(index, spk)| AddressInfo {
+ spks.into_iter().map(move |(index, spk)| AddressInfo {
index,
address: Address::from_script(&spk, self.network).expect("must have address form"),
keychain,
- }))
+ })
}
/// Get the next unused address for the given `keychain`, i.e. the address with the lowest
/// This will attempt to derive and reveal a new address if no newly revealed addresses
/// are available. See also [`reveal_next_address`](Self::reveal_next_address).
///
- /// # Errors
- ///
- /// If writing to persistent storage fails.
- pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
- let ((index, spk), index_changeset) = self
- .indexed_graph
- .index
+ /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more
+ /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`].
+ pub fn next_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo {
+ let index = &mut self.indexed_graph.index;
+
+ let ((index, spk), index_changeset) = index
.next_unused_spk(&keychain)
.expect("keychain must exist");
- self.persist
- .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
+ self.stage
+ .append(indexed_tx_graph::ChangeSet::from(index_changeset).into());
- Ok(AddressInfo {
+ AddressInfo {
index,
address: Address::from_script(spk.as_script(), self.network)
.expect("must have address form"),
keychain,
- })
+ }
}
/// Marks an address used of the given `keychain` at `index`.
/// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will
/// not be returned in [`list_unspent`] or [`list_output`].
///
- /// Any inserted `TxOut`s are not persisted until [`commit`] is called.
- ///
- /// **WARNING:** This should only be used to add `TxOut`s that the wallet does not own. Only
+ /// **WARNINGS:** This should only be used to add `TxOut`s that the wallet does not own. Only
/// insert `TxOut`s that you trust the values for!
///
+ /// You must persist the changes resulting from one or more calls to this method if you need
+ /// the inserted `TxOut` data to be reloaded after closing the wallet.
+ /// See [`Wallet::reveal_next_address`].
+ ///
/// [`calculate_fee`]: Self::calculate_fee
/// [`calculate_fee_rate`]: Self::calculate_fee_rate
/// [`list_unspent`]: Self::list_unspent
/// [`list_output`]: Self::list_output
- /// [`commit`]: Self::commit
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) {
let additions = self.indexed_graph.insert_txout(outpoint, txout);
- self.persist.stage(ChangeSet::from(additions));
+ self.stage(ChangeSet::from(additions));
}
/// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase transaction.
}
/// 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).
///
+ /// **WARNING**: You must persist the changes resulting from one or more calls to this method
+ /// if you need the inserted checkpoint data to be reloaded after closing the wallet.
+ /// See [`Wallet::reveal_next_address`].
+ ///
/// [`commit`]: Self::commit
pub fn insert_checkpoint(
&mut self,
) -> Result<bool, local_chain::AlterCheckPointError> {
let changeset = self.chain.insert_block(block_id)?;
let changed = !changeset.is_empty();
- self.persist.stage(changeset.into());
+ self.stage(changeset.into());
Ok(changed)
}
- /// Add a transaction to the wallet's internal view of the chain. This stages but does not
- /// [`commit`] the change.
+ /// Add a transaction to the wallet's internal view of the chain. This stages the change,
+ /// you must persist it later.
///
/// Returns whether anything changed with the transaction insertion (e.g. `false` if the
/// transaction was already inserted at the same position).
/// Therefore you should use [`insert_checkpoint`] to insert new checkpoints before manually
/// inserting new transactions.
///
- /// **WARNING:** If `position` is confirmed, we anchor the `tx` to a the lowest checkpoint that
+ /// **WARNING**: If `position` is confirmed, we anchor the `tx` to the lowest checkpoint that
/// is >= the `position`'s height. The caller is responsible for ensuring the `tx` exists in our
/// local view of the best chain's history.
///
+ /// You must persist the changes resulting from one or more calls to this method if you need
+ /// the inserted tx to be reloaded after closing the wallet.
+ ///
/// [`commit`]: Self::commit
/// [`latest_checkpoint`]: Self::latest_checkpoint
/// [`insert_checkpoint`]: Self::insert_checkpoint
}
let changed = !changeset.is_empty();
- self.persist.stage(changeset);
+ self.stage(changeset);
Ok(changed)
}
/// # use bdk_wallet::bitcoin::Network;
/// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
/// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
- /// let wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
+ /// let wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
/// 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);
/// # use bdk_wallet::*;
/// # use bdk_wallet::wallet::ChangeSet;
/// # use bdk_wallet::wallet::error::CreateTxError;
- /// # use bdk_persist::PersistBackend;
/// # use anyhow::Error;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let mut wallet = doctest_wallet!();
.next_unused_spk(&change_keychain)
.expect("keychain must exist");
self.indexed_graph.index.mark_used(change_keychain, index);
- self.persist
- .stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
- index_changeset,
- )));
- self.persist.commit().map_err(CreateTxError::Persist)?;
+ self.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
+ index_changeset,
+ )));
spk
}
};
/// # use bdk_wallet::*;
/// # use bdk_wallet::wallet::ChangeSet;
/// # use bdk_wallet::wallet::error::CreateTxError;
- /// # use bdk_persist::PersistBackend;
/// # use anyhow::Error;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let mut wallet = doctest_wallet!();
/// # use bdk_wallet::*;
/// # use bdk_wallet::wallet::ChangeSet;
/// # use bdk_wallet::wallet::error::CreateTxError;
- /// # use bdk_persist::PersistBackend;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let mut wallet = doctest_wallet!();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
.to_string()
}
- /// Applies an update to the wallet and stages the changes (but does not [`commit`] them).
+ /// Applies an update to the wallet and stages the changes (but does not persist them).
///
/// Usually you create an `update` by interacting with some blockchain data source and inserting
/// transactions related to your wallet into it.
///
+ /// After applying updates you should persist the staged wallet changes. For an example of how
+ /// to persist staged wallet changes see [`Wallet::reveal_next_address`]. `
+ ///
/// [`commit`]: Self::commit
pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
let update = update.into();
changeset.append(ChangeSet::from(
self.indexed_graph.apply_update(update.graph),
));
- self.persist.stage(changeset);
+ self.stage(changeset);
Ok(())
}
- /// Commits all currently [`staged`] changed to the persistence backend returning and error when
- /// this fails.
- ///
- /// This returns whether the `update` resulted in any changes.
- ///
- /// [`staged`]: Self::staged
- pub fn commit(&mut self) -> anyhow::Result<bool> {
- self.persist.commit().map(|c| c.is_some())
- }
-
- /// Returns the changes that will be committed with the next call to [`commit`].
- ///
- /// [`commit`]: Self::commit
- pub fn staged(&self) -> &ChangeSet {
- self.persist.staged()
- }
-
/// Get a reference to the inner [`TxGraph`].
pub fn tx_graph(&self) -> &TxGraph<ConfirmationTimeHeightAnchor> {
self.indexed_graph.graph()
/// The `connected_to` parameter informs the wallet how this block connects to the internal
/// [`LocalChain`]. Relevant transactions are filtered from the `block` and inserted into the
/// internal [`TxGraph`].
+ ///
+ /// **WARNING**: You must persist the changes resulting from one or more calls to this method
+ /// if you need the inserted block data to be reloaded after closing the wallet.
+ /// See [`Wallet::reveal_next_address`].
pub fn apply_block_connected_to(
&mut self,
block: &Block,
.apply_block_relevant(block, height)
.into(),
);
- self.persist.stage(changeset);
+ self.stage(changeset);
Ok(())
}
/// when the transaction was last seen in the mempool. This is used for conflict resolution
/// when there is conflicting unconfirmed transactions. The transaction with the later
/// `last_seen` is prioritized.
+ ///
+ /// **WARNING**: You must persist the changes resulting from one or more calls to this method
+ /// if you need the applied unconfirmed transactions to be reloaded after closing the wallet.
+ /// See [`Wallet::reveal_next_address`].
pub fn apply_unconfirmed_txs<'t>(
&mut self,
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
let indexed_graph_changeset = self
.indexed_graph
.batch_insert_relevant_unconfirmed(unconfirmed_txs);
- self.persist.stage(ChangeSet::from(indexed_graph_changeset));
+ self.stage(ChangeSet::from(indexed_graph_changeset));
}
}
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(
+ let mut wallet = Wallet::new(
descriptor,
change_descriptor,
Network::Regtest,
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
-//! let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
+//! let mut wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
//! wallet.add_signer(
//! KeychainKind::External,
//! SignerOrdering(200),
//! # use bdk_wallet::*;
//! # use bdk_wallet::wallet::ChangeSet;
//! # use bdk_wallet::wallet::error::CreateTxError;
-//! # use bdk_persist::PersistBackend;
//! # use anyhow::Error;
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
//! # let mut wallet = doctest_wallet!();
/// # use core::str::FromStr;
/// # use bdk_wallet::wallet::ChangeSet;
/// # use bdk_wallet::wallet::error::CreateTxError;
-/// # use bdk_persist::PersistBackend;
/// # use anyhow::Error;
/// # let mut wallet = doctest_wallet!();
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
/// # use bdk_wallet::*;
/// # use bdk_wallet::wallet::ChangeSet;
/// # use bdk_wallet::wallet::error::CreateTxError;
- /// # use bdk_persist::PersistBackend;
/// # use anyhow::Error;
/// # let to_address =
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
/// Returns a new [`Psbt`] per [`BIP174`].
///
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
+ ///
+ /// **WARNING**: To avoid change address reuse you must persist the changes resulting from one
+ /// or more calls to this method before closing the wallet. See [`Wallet::reveal_next_address`].
pub fn finish(self) -> Result<Psbt, CreateTxError> {
self.wallet
.borrow_mut()
/// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000
/// sats are the transaction fee.
pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) {
- let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap();
+ let mut wallet = Wallet::new(descriptor, change, Network::Regtest).unwrap();
let receive_address = wallet.peek_address(KeychainKind::External, 0).address;
let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
.expect("address")
+use anyhow::anyhow;
use std::path::Path;
use std::str::FromStr;
use assert_matches::assert_matches;
use bdk_chain::collections::BTreeMap;
use bdk_chain::COINBASE_MATURITY;
-use bdk_chain::{BlockId, ConfirmationTime};
-use bdk_persist::PersistBackend;
+use bdk_chain::{persist::PersistBackend, BlockId, ConfirmationTime};
use bdk_sqlite::rusqlite::Connection;
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
use bdk_wallet::psbt::PsbtUtils;
use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
use bdk_wallet::wallet::error::CreateTxError;
use bdk_wallet::wallet::tx_builder::AddForeignUtxoError;
-use bdk_wallet::wallet::NewError;
-use bdk_wallet::wallet::{AddressInfo, Balance, Wallet};
+use bdk_wallet::wallet::{AddressInfo, Balance, NewError, Wallet};
use bdk_wallet::KeychainKind;
use bitcoin::hashes::Hash;
use bitcoin::key::Secp256k1;
use common::*;
fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) -> OutPoint {
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let tx = Transaction {
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
// create new wallet
let wallet_spk_index = {
- let db = create_new(&file_path).expect("must create db");
let mut wallet =
- Wallet::new(desc, change_desc, db, Network::Testnet).expect("must init wallet");
+ Wallet::new(desc, change_desc, Network::Testnet).expect("must init wallet");
+
+ wallet.reveal_next_address(KeychainKind::External);
+
+ // persist new wallet changes
+ let staged_changeset = wallet.take_staged();
+ let db = &mut create_new(&file_path).expect("must create db");
+ db.write_changes(&staged_changeset)
+ .map_err(|e| anyhow!("write changes error: {}", e))?;
- wallet.reveal_next_address(KeychainKind::External).unwrap();
wallet.spk_index().clone()
};
// recover wallet
{
- let db = recover(&file_path).expect("must recover db");
- let wallet = Wallet::load(db).expect("must recover wallet");
+ // load persisted wallet changes
+ let db = &mut recover(&file_path).expect("must recover db");
+ let changeset = db
+ .load_changes()
+ .expect("must recover wallet")
+ .expect("changeset");
+
+ let wallet = Wallet::load_from_changeset(changeset).expect("must recover wallet");
assert_eq!(wallet.network(), Network::Testnet);
assert_eq!(
wallet.spk_index().keychains().collect::<Vec<_>>(),
);
}
- // `new` can only be called on empty db
- {
- let db = recover(&file_path).expect("must recover db");
- let result = Wallet::new(desc, change_desc, db, Network::Testnet);
- assert!(matches!(result, Err(NewError::NonEmptyDatabase)));
- }
-
Ok(())
}
// init wallet when non-existent
let wallet_keychains: BTreeMap<_, _> = {
- let db = new_or_load(&file_path).expect("must create db");
- let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet)
+ let wallet = &mut Wallet::new_or_load(desc, change_desc, None, Network::Testnet)
.expect("must init wallet");
+ let staged_changeset = wallet.take_staged();
+ let mut db = new_or_load(&file_path).expect("must create db");
+ db.write_changes(&staged_changeset)
+ .map_err(|e| anyhow!("write changes error: {}", e))?;
wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
};
// wrong network
{
- let db = new_or_load(&file_path).expect("must create db");
- let err = Wallet::new_or_load(desc, change_desc, db, Network::Bitcoin)
+ let mut db = new_or_load(&file_path).expect("must create db");
+ let changeset = db
+ .load_changes()
+ .map_err(|e| anyhow!("load changes error: {}", e))?;
+ let err = Wallet::new_or_load(desc, change_desc, changeset, Network::Bitcoin)
.expect_err("wrong network");
assert!(
matches!(
let got_blockhash =
bitcoin::blockdata::constants::genesis_block(Network::Testnet).block_hash();
- let db = new_or_load(&file_path).expect("must open db");
+ let db = &mut new_or_load(&file_path).expect("must open db");
+ let changeset = db
+ .load_changes()
+ .map_err(|e| anyhow!("load changes error: {}", e))?;
let err = Wallet::new_or_load_with_genesis_hash(
desc,
change_desc,
- db,
+ changeset,
Network::Testnet,
exp_blockhash,
)
// wrong external descriptor
{
- let exp_descriptor = get_test_tr_single_sig();
+ let (exp_descriptor, exp_change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
let got_descriptor = desc
.into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
.unwrap()
.0;
- let db = new_or_load(&file_path).expect("must open db");
- let err = Wallet::new_or_load(exp_descriptor, change_desc, db, Network::Testnet)
- .expect_err("wrong external descriptor");
+ let db = &mut new_or_load(&file_path).expect("must open db");
+ let changeset = db
+ .load_changes()
+ .map_err(|e| anyhow!("load changes error: {}", e))?;
+ let err =
+ Wallet::new_or_load(exp_descriptor, exp_change_desc, changeset, Network::Testnet)
+ .expect_err("wrong external descriptor");
assert!(
matches!(
err,
.unwrap()
.0;
- let db = new_or_load(&file_path).expect("must open db");
- let err = Wallet::new_or_load(desc, exp_descriptor, db, Network::Testnet)
+ let db = &mut new_or_load(&file_path).expect("must open db");
+ let changeset = db
+ .load_changes()
+ .map_err(|e| anyhow!("load changes error: {}", e))?;
+ let err = Wallet::new_or_load(desc, exp_descriptor, changeset, Network::Testnet)
.expect_err("wrong internal descriptor");
assert!(
matches!(
// all parameters match
{
- let db = new_or_load(&file_path).expect("must open db");
- let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet)
+ let db = &mut new_or_load(&file_path).expect("must open db");
+ let changeset = db
+ .load_changes()
+ .map_err(|e| anyhow!("load changes error: {}", e))?;
+ let wallet = Wallet::new_or_load(desc, change_desc, changeset, Network::Testnet)
.expect("must recover wallet");
assert_eq!(wallet.network(), Network::Testnet);
assert!(wallet
fn test_error_external_and_internal_are_the_same() {
// identical descriptors should fail to create wallet
let desc = get_test_wpkh();
- let err = Wallet::new_no_persist(desc, desc, Network::Testnet);
+ let err = Wallet::new(desc, desc, Network::Testnet);
assert!(
- matches!(&err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
+ matches!(
+ &err,
+ Err(NewError::Descriptor(
+ DescriptorError::ExternalAndInternalAreTheSame
+ ))
+ ),
"expected same descriptors error, got {:?}",
err,
);
// public + private of same descriptor should fail to create wallet
let desc = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
let change_desc = "wpkh([3c31d632/84'/1'/0']tpubDCYwFkks2cg78N7eoYbBatsFEGje8vW8arSKW4rLwD1AU1s9KJMDRHE32JkvYERuiFjArrsH7qpWSpJATed5ShZbG9KsskA5Rmi6NSYgYN2/0/*)";
- let err = Wallet::new_no_persist(desc, change_desc, Network::Testnet);
+ let err = Wallet::new(desc, change_desc, Network::Testnet);
assert!(
- matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
+ matches!(
+ err,
+ Err(NewError::Descriptor(
+ DescriptorError::ExternalAndInternalAreTheSame
+ ))
+ ),
"expected same descriptors error, got {:?}",
err,
);
#[should_panic(expected = "NoUtxosSelected")]
fn test_create_tx_manually_selected_empty_utxos() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_version_0() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_version_1_csv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_custom_version() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
fn test_create_tx_default_locktime_is_last_sync_height() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_create_tx_fee_sniping_locktime_last_sync() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
#[test]
fn test_create_tx_default_locktime_cltv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_create_tx_custom_locktime() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_custom_locktime_compatible_with_cltv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_custom_locktime_incompatible_with_cltv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_no_rbf_csv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_create_tx_with_default_rbf_csv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_with_custom_rbf_csv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_no_rbf_cltv() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_create_tx_invalid_rbf_sequence() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_custom_rbf_sequence() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_change_policy() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_default_sequence() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_create_tx_drain_wallet_and_drain_to() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
.unwrap()
.assume_checked();
- let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let drain_addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(20_000))
#[test]
fn test_create_tx_drain_to_and_utxos() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect();
let mut builder = wallet.build_tx();
builder
#[should_panic(expected = "NoRecipients")]
fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let drain_addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(drain_addr.script_pubkey());
builder.finish().unwrap();
#[test]
fn test_create_tx_default_fee_rate() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_create_tx_custom_fee_rate() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_absolute_fee() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
#[test]
fn test_create_tx_absolute_zero_fee() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
#[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_absolute_high_fee() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
use bdk_wallet::wallet::tx_builder::TxOrdering;
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[test]
fn test_create_tx_skip_change_dust() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800));
let psbt = builder.finish().unwrap();
#[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_drain_to_dust_amount() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
// very high fee rate, so that the only output would be below dust
let mut builder = wallet.build_tx();
builder
#[test]
fn test_create_tx_ordering_respected() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
#[test]
fn test_create_tx_default_sighash() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_create_tx_custom_sighash() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
use core::str::FromStr;
let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
let (mut wallet, _) =
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
let (mut wallet, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
let (mut wallet, _) =
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
fn test_create_tx_non_witness_utxo() {
let (mut wallet, _) =
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
fn test_create_tx_only_witness_utxo() {
let (mut wallet, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
fn test_create_tx_shwpkh_has_witness_utxo() {
let (mut wallet, _) =
get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() {
let (mut wallet, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
#[test]
fn test_create_tx_policy_path_no_csv() {
let (desc, change_desc) = get_test_wpkh_with_change_desc();
- let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap();
+ let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).expect("wallet");
let tx = Transaction {
version: transaction::Version::non_standard(0),
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.script_pubkey(),
value: Amount::from_sat(50_000),
}],
fn test_create_tx_global_xpubs_with_origin() {
use bitcoin::bip32;
let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
)]
fn test_create_tx_global_xpubs_origin_missing() {
let (mut wallet, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
fn test_create_tx_global_xpubs_master_without_origin() {
use bitcoin::bip32;
let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[should_panic(expected = "IrreplaceableTransaction")]
fn test_bump_fee_irreplaceable_tx() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[should_panic(expected = "TransactionConfirmed")]
fn test_bump_fee_confirmed_tx() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let psbt = builder.finish().unwrap();
#[test]
fn test_bump_fee_low_fee_rate() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[should_panic(expected = "FeeTooLow")]
fn test_bump_fee_low_abs() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
#[should_panic(expected = "FeeTooLow")]
fn test_bump_fee_zero_abs() {
let (mut wallet, _) = get_funded_wallet_wpkh();
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
#[test]
fn test_sign_single_xprv() {
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
#[test]
fn test_sign_single_xprv_with_master_fingerprint_and_path() {
let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
#[test]
fn test_sign_single_xprv_bip44_path() {
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
#[test]
fn test_sign_single_xprv_sh_wpkh() {
let (mut wallet, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
fn test_sign_single_wif() {
let (mut wallet, _) =
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
#[test]
fn test_sign_single_xprv_no_hd_keypaths() {
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
for remove_partial_sigs in &[true, false] {
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
for try_finalize in &[true, false] {
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
let sighash = EcdsaSighashType::NonePlusAnyoneCanPay;
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
fn test_unused_address() {
let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let change_desc = get_test_wpkh();
- let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap();
+ let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).expect("wallet");
// `list_unused_addresses` should be empty if we haven't revealed any
assert!(wallet
assert_eq!(
wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
fn test_next_unused_address() {
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let change = get_test_wpkh();
- let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Testnet).unwrap();
+ let mut wallet = Wallet::new(descriptor, change, Network::Testnet).expect("wallet");
assert_eq!(wallet.derivation_index(KeychainKind::External), None);
assert_eq!(
wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
// test mark used / unused
assert!(wallet.mark_used(KeychainKind::External, 0));
- let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let next_unused_addr = wallet.next_unused_address(KeychainKind::External);
assert_eq!(next_unused_addr.index, 1);
assert!(wallet.unmark_used(KeychainKind::External, 0));
- let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let next_unused_addr = wallet.next_unused_address(KeychainKind::External);
assert_eq!(next_unused_addr.index, 0);
// use the above address
assert_eq!(
wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
fn test_peek_address_at_index() {
let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let change_desc = get_test_wpkh();
- let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap();
+ let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).unwrap();
assert_eq!(
wallet.peek_address(KeychainKind::External, 1).to_string(),
assert_eq!(
wallet
.reveal_next_address(KeychainKind::External)
- .unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet
.reveal_next_address(KeychainKind::External)
- .unwrap()
.to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
#[test]
fn test_peek_address_at_index_not_derivable() {
- let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
get_test_wpkh(), Network::Testnet).unwrap();
assert_eq!(
#[test]
fn test_returns_index_and_address() {
- let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
get_test_wpkh(), Network::Testnet).unwrap();
// new index 0
assert_eq!(
- wallet.reveal_next_address(KeychainKind::External).unwrap(),
+ wallet.reveal_next_address(KeychainKind::External),
AddressInfo {
index: 0,
address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a")
// new index 1
assert_eq!(
- wallet.reveal_next_address(KeychainKind::External).unwrap(),
+ wallet.reveal_next_address(KeychainKind::External),
AddressInfo {
index: 1,
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7")
// new index 2
assert_eq!(
- wallet.reveal_next_address(KeychainKind::External).unwrap(),
+ wallet.reveal_next_address(KeychainKind::External),
AddressInfo {
index: 2,
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2")
fn test_get_address() {
use bdk_wallet::descriptor::template::Bip84;
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
- let wallet = Wallet::new_no_persist(
+ let wallet = Wallet::new(
Bip84(key, KeychainKind::External),
Bip84(key, KeychainKind::Internal),
Network::Regtest,
#[test]
fn test_reveal_addresses() {
let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
- let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Signet).unwrap();
+ let mut wallet = Wallet::new(desc, change_desc, Network::Signet).unwrap();
let keychain = KeychainKind::External;
- let last_revealed_addr = wallet
- .reveal_addresses_to(keychain, 9)
- .unwrap()
- .last()
- .unwrap();
+ let last_revealed_addr = wallet.reveal_addresses_to(keychain, 9).last().unwrap();
assert_eq!(wallet.derivation_index(keychain), Some(9));
let unused_addrs = wallet.list_unused_addresses(keychain).collect::<Vec<_>>();
assert_eq!(unused_addrs.last().unwrap(), &last_revealed_addr);
// revealing to an already revealed index returns nothing
- let mut already_revealed = wallet.reveal_addresses_to(keychain, 9).unwrap();
+ let mut already_revealed = wallet.reveal_addresses_to(keychain, 9);
assert!(already_revealed.next().is_none());
}
use std::collections::HashSet;
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
- let mut wallet = Wallet::new_no_persist(
+ let mut wallet = Wallet::new(
Bip84(key, KeychainKind::External),
Bip84(key, KeychainKind::Internal),
Network::Regtest,
let mut used_set = HashSet::new();
(0..3).for_each(|_| {
- let external_addr = wallet
- .reveal_next_address(KeychainKind::External)
- .unwrap()
- .address;
+ let external_addr = wallet.reveal_next_address(KeychainKind::External).address;
assert!(used_set.insert(external_addr));
- let internal_addr = wallet
- .reveal_next_address(KeychainKind::Internal)
- .unwrap()
- .address;
+ let internal_addr = wallet.reveal_next_address(KeychainKind::Internal).address;
assert!(used_set.insert(internal_addr));
});
}
fn test_taproot_remove_tapfields_after_finalize_sign_option() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
fn test_taproot_psbt_populate_tap_key_origins() {
let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
let (mut wallet, _) = get_funded_wallet_with_change(desc, change_desc);
- let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
+ let addr = wallet.reveal_next_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
let (mut wallet, _) =
get_funded_wallet_with_change(get_test_tr_repeated_key(), get_test_tr_single_sig());
- let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
+ let addr = wallet.reveal_next_address(KeychainKind::External);
let path = vec![("rn4nre9c".to_string(), vec![0])]
.into_iter()
use bitcoin::taproot;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
#[test]
fn test_taproot_sign_missing_witness_utxo() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
#[test]
fn test_taproot_sign_using_non_witness_utxo() {
let (mut wallet, prev_txid) = get_funded_wallet(get_test_tr_single_sig());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
}
fn test_spend_from_wallet(mut wallet: Wallet) {
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
#[test]
fn test_taproot_no_key_spend() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
fn test_taproot_script_spend_sign_all_leaves() {
use bdk_wallet::signer::TapLeavesOptions;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
use bitcoin::taproot::TapLeafHash;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
use bitcoin::taproot::TapLeafHash;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
fn test_taproot_script_spend_sign_no_leaves() {
use bdk_wallet::signer::TapLeavesOptions;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
fn test_taproot_sign_derive_index_from_psbt() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
let mut psbt = builder.finish().unwrap();
// re-create the wallet with an empty db
- let wallet_empty = Wallet::new_no_persist(
+ let wallet_empty = Wallet::new(
get_test_tr_single_sig_xprv(),
get_test_tr_single_sig(),
Network::Regtest,
#[test]
fn test_taproot_sign_explicit_sighash_all() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
let sighash = TapSighashType::NonePlusAnyoneCanPay;
let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
#[test]
fn test_spend_coinbase() {
let (desc, change_desc) = get_test_wpkh_with_change_desc();
- let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap();
+ let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).unwrap();
let confirmation_height = 5;
wallet
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
- .unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
fn test_allow_dust_limit() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let mut builder = wallet.build_tx();
// instead of 70). We then check that our fee rate and fee calculation is
// alright.
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
let mut builder = wallet.build_tx();
let mut data = PushBytesBuf::try_from(vec![0]).unwrap();
// We then check that our fee rate and fee calculation is alright and that our
// signature is 70 bytes.
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
- let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+ let addr = wallet.next_unused_address(KeychainKind::External);
let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
let mut builder = wallet.build_tx();
builder
bitcoincore_rpc::{Auth, Client, RpcApi},
Emitter,
};
+use bdk_chain::persist::PersistBackend;
use bdk_chain::{
bitcoin::{constants::genesis_block, Block, Transaction},
indexed_tx_graph, keychain,
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()?;
+ db.write_changes(&(chain_changeset, Default::default()))?;
chain
} else {
LocalChain::from_changeset(init_chain_changeset)?
.apply_update(emission.checkpoint)
.expect("must always apply as we receive blocks in order from emitter");
let graph_changeset = graph.apply_block_relevant(&emission.block, height);
- db.stage((chain_changeset, graph_changeset));
+ db.write_changes(&(chain_changeset, graph_changeset))?;
// commit staged db changes in intervals
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
last_db_commit = Instant::now();
- db.commit()?;
println!(
"[{:>10}s] committed to db (took {}s)",
start.elapsed().as_secs_f32(),
);
{
let mut db = db.lock().unwrap();
- db.stage((local_chain::ChangeSet::default(), graph_changeset));
- db.commit()?; // commit one last time
+ db.write_changes(&(local_chain::ChangeSet::default(), graph_changeset))?;
}
}
RpcCommands::Live { rpc_args } => {
}
};
- db.stage(changeset);
+ db.write_changes(&changeset)?;
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
last_db_commit = Instant::now();
- db.commit()?;
println!(
"[{:>10}s] committed to db (took {}s)",
start.elapsed().as_secs_f32(),
[dependencies]
bdk_chain = { path = "../../crates/chain", features = ["serde", "miniscript"]}
-bdk_persist = { path = "../../crates/persist" }
bdk_file_store = { path = "../../crates/file_store" }
bdk_tmp_plan = { path = "../../nursery/tmp_plan" }
bdk_coin_select = { path = "../../nursery/coin_select" }
use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue};
use bdk_file_store::Store;
use serde::{de::DeserializeOwned, Serialize};
+use std::fmt::Debug;
use std::{cmp::Reverse, collections::BTreeMap, path::PathBuf, sync::Mutex, time::Duration};
use bdk_chain::{
Anchor, Append, ChainOracle, DescriptorExt, FullTxOut,
};
pub use bdk_file_store;
-use bdk_persist::{Persist, PersistBackend};
pub use clap;
+use bdk_chain::persist::PersistBackend;
use clap::{Parser, Subcommand};
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainOracle, C>(
graph: &Mutex<KeychainTxGraph<A>>,
- db: &Mutex<Persist<C>>,
+ db: &Mutex<Store<C>>,
chain: &Mutex<O>,
keymap: &BTreeMap<DescriptorPublicKey, DescriptorSecretKey>,
network: Network,
) -> anyhow::Result<()>
where
O::Error: std::error::Error + Send + Sync + 'static,
- C: Default + Append + DeserializeOwned + Serialize + From<KeychainChangeSet<A>>,
+ C: Default
+ + Append
+ + DeserializeOwned
+ + Serialize
+ + From<KeychainChangeSet<A>>
+ + Send
+ + Sync
+ + Debug,
{
match cmd {
Commands::ChainSpecific(_) => unreachable!("example code should handle this!"),
let ((spk_i, spk), index_changeset) =
spk_chooser(index, &Keychain::External).expect("Must exist");
let db = &mut *db.lock().unwrap();
- db.stage_and_commit(C::from((
+ db.write_changes(&C::from((
local_chain::ChangeSet::default(),
indexed_tx_graph::ChangeSet::from(index_changeset),
)))?;
// If we're unable to persist this, then we don't want to broadcast.
{
let db = &mut *db.lock().unwrap();
- db.stage_and_commit(C::from((
+ db.write_changes(&C::from((
local_chain::ChangeSet::default(),
indexed_tx_graph::ChangeSet::from(index_changeset),
)))?;
// We know the tx is at least unconfirmed now. Note if persisting here fails,
// it's not a big deal since we can always find it again form
// blockchain.
- db.lock().unwrap().stage_and_commit(C::from((
+ db.lock().unwrap().write_changes(&C::from((
local_chain::ChangeSet::default(),
keychain_changeset,
)))?;
}
/// The initial state returned by [`init`].
-pub struct Init<CS: clap::Subcommand, S: clap::Args, C> {
+pub struct Init<CS: clap::Subcommand, S: clap::Args, C>
+where
+ C: Default + Append + Serialize + DeserializeOwned + Debug + Send + Sync + 'static,
+{
/// Arguments parsed by the cli.
pub args: Args<CS, S>,
/// Descriptor keymap.
/// Keychain-txout index.
pub index: KeychainTxOutIndex<Keychain>,
/// Persistence backend.
- pub db: Mutex<Persist<C>>,
+ pub db: Mutex<Store<C>>,
/// Initial changeset.
pub init_changeset: C,
}
+ Append
+ Serialize
+ DeserializeOwned
+ + Debug
+ core::marker::Send
+ core::marker::Sync
+ 'static,
Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)),
};
- let init_changeset = db_backend.load_from_persistence()?.unwrap_or_default();
+ let init_changeset = db_backend.load_changes()?.unwrap_or_default();
Ok(Init {
args,
keymap,
index,
- db: Mutex::new(Persist::new(db_backend)),
+ db: Mutex::new(db_backend),
init_changeset,
})
}
sync::Mutex,
};
+use bdk_chain::persist::PersistBackend;
use bdk_chain::{
bitcoin::{constants::genesis_block, Address, Network, Txid},
collections::BTreeSet,
};
let mut db = db.lock().unwrap();
- db.stage(db_changeset);
- db.commit()?;
+ db.write_changes(&db_changeset)?;
Ok(())
}
sync::Mutex,
};
+use bdk_chain::persist::PersistBackend;
use bdk_chain::{
bitcoin::{constants::genesis_block, Address, Network, Txid},
indexed_tx_graph::{self, IndexedTxGraph},
// We persist the changes
let mut db = db.lock().unwrap();
- db.stage((local_chain_changeset, indexed_tx_graph_changeset));
- db.commit()?;
+ db.write_changes(&(local_chain_changeset, indexed_tx_graph_changeset))?;
Ok(())
}
const STOP_GAP: usize = 50;
const BATCH_SIZE: usize = 5;
+use anyhow::anyhow;
use std::io::Write;
use std::str::FromStr;
use bdk_file_store::Store;
use bdk_wallet::bitcoin::{Address, Amount};
use bdk_wallet::chain::collections::HashSet;
+use bdk_wallet::chain::persist::PersistBackend;
use bdk_wallet::{bitcoin::Network, Wallet};
use bdk_wallet::{KeychainKind, SignOptions};
fn main() -> Result<(), anyhow::Error> {
let db_path = std::env::temp_dir().join("bdk-electrum-example");
- let db =
+ let mut db =
Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
-
+ let changeset = db
+ .load_changes()
+ .map_err(|e| anyhow!("load changes error: {}", e))?;
let mut wallet = Wallet::new_or_load(
external_descriptor,
internal_descriptor,
- db,
+ changeset,
Network::Testnet,
)?;
- let address = wallet.next_unused_address(KeychainKind::External)?;
+ let address = wallet.next_unused_address(KeychainKind::External);
+ db.write_changes(&wallet.take_staged())?;
println!("Generated Address: {}", address);
let balance = wallet.balance();
println!();
wallet.apply_update(update)?;
- wallet.commit()?;
+ db.write_changes(&wallet.take_staged())?;
let balance = wallet.balance();
println!("Wallet balance after syncing: {} sats", balance.total());
};
use bdk_sqlite::{rusqlite::Connection, Store};
+use bdk_wallet::chain::persist::PersistBackend;
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
const STOP_GAP: usize = 50;
async fn main() -> Result<(), anyhow::Error> {
let db_path = "bdk-esplora-async-example.sqlite";
let conn = Connection::open(db_path)?;
- let db = Store::new(conn)?;
+ let mut db = Store::new(conn)?;
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
+ let changeset = db.load_changes()?;
let mut wallet = Wallet::new_or_load(
external_descriptor,
internal_descriptor,
- db,
+ changeset,
Network::Signet,
)?;
- let address = wallet.next_unused_address(KeychainKind::External)?;
+ let address = wallet.next_unused_address(KeychainKind::External);
+ db.write_changes(&wallet.take_staged())?;
println!("Generated Address: {}", address);
let balance = wallet.balance();
let _ = update.graph_update.update_last_seen_unconfirmed(now);
wallet.apply_update(update)?;
- wallet.commit()?;
+ db.write_changes(&wallet.take_staged())?;
println!();
let balance = wallet.balance();
use bdk_esplora::{esplora_client, EsploraExt};
use bdk_file_store::Store;
+use bdk_wallet::chain::persist::PersistBackend;
use bdk_wallet::{
bitcoin::{Address, Amount, Network},
KeychainKind, SignOptions, Wallet,
fn main() -> Result<(), anyhow::Error> {
let db_path = std::env::temp_dir().join("bdk-esplora-example");
- let db =
+ let mut db =
Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
+ let changeset = db.load_changes()?;
let mut wallet = Wallet::new_or_load(
external_descriptor,
internal_descriptor,
- db,
+ changeset,
Network::Testnet,
)?;
- let address = wallet.next_unused_address(KeychainKind::External)?;
+ let address = wallet.next_unused_address(KeychainKind::External);
+ db.write_changes(&wallet.take_staged())?;
println!("Generated Address: {}", address);
let balance = wallet.balance();
let _ = update.graph_update.update_last_seen_unconfirmed(now);
wallet.apply_update(update)?;
- wallet.commit()?;
+ db.write_changes(&wallet.take_staged())?;
println!();
let balance = wallet.balance();
Emitter,
};
use bdk_file_store::Store;
+use bdk_wallet::chain::persist::PersistBackend;
use bdk_wallet::{
bitcoin::{Block, Network, Transaction},
wallet::Wallet,
);
let start_load_wallet = Instant::now();
+ let mut db = Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
+ DB_MAGIC.as_bytes(),
+ args.db_path,
+ )?;
+ let changeset = db.load_changes()?;
+
let mut wallet = Wallet::new_or_load(
&args.descriptor,
&args.change_descriptor,
- Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
- DB_MAGIC.as_bytes(),
- args.db_path,
- )?,
+ changeset,
args.network,
)?;
println!(
let connected_to = block_emission.connected_to();
let start_apply_block = Instant::now();
wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?;
- wallet.commit()?;
+ db.write_changes(&wallet.take_staged())?;
let elapsed = start_apply_block.elapsed().as_secs_f32();
println!(
"Applied block {} at height {} in {}s",
Emission::Mempool(mempool_emission) => {
let start_apply_mempool = Instant::now();
wallet.apply_unconfirmed_txs(mempool_emission.iter().map(|(tx, time)| (tx, *time)));
- wallet.commit()?;
+ db.write_changes(&wallet.take_staged())?;
println!(
"Applied unconfirmed transactions in {}s",
start_apply_mempool.elapsed().as_secs_f32()