refactor(persist): update file_store, sqlite, wallet to use bdk_chain::persist
Also update examples and remove bdk_persist crate.
This commit is contained in:
parent
54b0c11cbe
commit
9e97ac0330
@ -9,7 +9,6 @@ members = [
|
|||||||
"crates/esplora",
|
"crates/esplora",
|
||||||
"crates/bitcoind_rpc",
|
"crates/bitcoind_rpc",
|
||||||
"crates/hwi",
|
"crates/hwi",
|
||||||
"crates/persist",
|
|
||||||
"crates/testenv",
|
"crates/testenv",
|
||||||
"example-crates/example_cli",
|
"example-crates/example_cli",
|
||||||
"example-crates/example_electrum",
|
"example-crates/example_electrum",
|
||||||
|
@ -26,8 +26,7 @@ rand = "0.8"
|
|||||||
proptest = "1.2.0"
|
proptest = "1.2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std", "miniscript", "persist"]
|
default = ["std", "miniscript"]
|
||||||
std = ["bitcoin/std", "miniscript?/std"]
|
std = ["bitcoin/std", "miniscript?/std"]
|
||||||
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
|
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
|
||||||
persist = ["miniscript"]
|
|
||||||
async = ["async-trait"]
|
async = ["async-trait"]
|
||||||
|
@ -207,7 +207,7 @@ impl<K> KeychainTxOutIndex<K> {
|
|||||||
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
||||||
/// Return a reference to the internal [`SpkTxOutIndex`].
|
/// 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`.
|
/// [struct-level docs](KeychainTxOutIndex) for more about `lookahead`.
|
||||||
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
|
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
|
||||||
&self.inner
|
&self.inner
|
||||||
|
@ -50,7 +50,7 @@ pub use descriptor_ext::{DescriptorExt, DescriptorId};
|
|||||||
mod spk_iter;
|
mod spk_iter;
|
||||||
#[cfg(feature = "miniscript")]
|
#[cfg(feature = "miniscript")]
|
||||||
pub use spk_iter::*;
|
pub use spk_iter::*;
|
||||||
#[cfg(feature = "persist")]
|
#[cfg(feature = "miniscript")]
|
||||||
pub mod persist;
|
pub mod persist;
|
||||||
pub mod spk_client;
|
pub mod spk_client;
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
//! 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.
|
//! 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
|
//! The [`CombinedChangeSet`] type encapsulates a combination of [`crate`] structures that are
|
||||||
//! typically persisted together.
|
//! typically persisted together.
|
||||||
|
|
||||||
@ -16,7 +13,6 @@ use bitcoin::Network;
|
|||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use core::default::Default;
|
use core::default::Default;
|
||||||
use core::fmt::{Debug, Display};
|
use core::fmt::{Debug, Display};
|
||||||
use core::mem;
|
|
||||||
|
|
||||||
/// A changeset containing [`crate`] structures typically persisted together.
|
/// A changeset containing [`crate`] structures typically persisted together.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@ -92,7 +88,7 @@ impl<K, A> From<indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>>
|
|||||||
///
|
///
|
||||||
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
|
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
|
||||||
/// that are to be persisted, or retrieved from persistence.
|
/// 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.
|
/// The error the backend returns when it fails to write.
|
||||||
type WriteError: Debug + Display;
|
type WriteError: Debug + Display;
|
||||||
|
|
||||||
@ -113,7 +109,7 @@ pub trait Persist<C> {
|
|||||||
fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError>;
|
fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> Persist<C> for () {
|
impl<C> PersistBackend<C> for () {
|
||||||
type WriteError = Infallible;
|
type WriteError = Infallible;
|
||||||
type LoadError = Infallible;
|
type LoadError = Infallible;
|
||||||
|
|
||||||
@ -132,7 +128,7 @@ impl<C> Persist<C> for () {
|
|||||||
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
|
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
|
||||||
/// that are to be persisted, or retrieved from persistence.
|
/// that are to be persisted, or retrieved from persistence.
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait PersistAsync<C> {
|
pub trait PersistBackendAsync<C> {
|
||||||
/// The error the backend returns when it fails to write.
|
/// The error the backend returns when it fails to write.
|
||||||
type WriteError: Debug + Display;
|
type WriteError: Debug + Display;
|
||||||
|
|
||||||
@ -155,7 +151,7 @@ pub trait PersistAsync<C> {
|
|||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<C> PersistAsync<C> for () {
|
impl<C> PersistBackendAsync<C> for () {
|
||||||
type WriteError = Infallible;
|
type WriteError = Infallible;
|
||||||
type LoadError = Infallible;
|
type LoadError = Infallible;
|
||||||
|
|
||||||
@ -167,288 +163,3 @@ impl<C> PersistAsync<C> for () {
|
|||||||
Ok(None)
|
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,15 +5,13 @@ edition = "2021"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
documentation = "https://docs.rs/bdk_file_store"
|
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"]
|
keywords = ["bitcoin", "persist", "persistence", "bdk", "file"]
|
||||||
authors = ["Bitcoin Dev Kit Developers"]
|
authors = ["Bitcoin Dev Kit Developers"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1", default-features = false }
|
|
||||||
bdk_chain = { path = "../chain", version = "0.15.0", features = [ "serde", "miniscript" ] }
|
bdk_chain = { path = "../chain", version = "0.15.0", features = [ "serde", "miniscript" ] }
|
||||||
bdk_persist = { path = "../persist", version = "0.3.0"}
|
|
||||||
bincode = { version = "1" }
|
bincode = { version = "1" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# BDK File Store
|
# 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.
|
The main structure is [`Store`] which works with any [`bdk_chain`] based changesets to persist data into a flat file.
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use crate::{bincode_options, EntryIter, FileError, IterError};
|
use crate::{bincode_options, EntryIter, FileError, IterError};
|
||||||
use anyhow::anyhow;
|
use bdk_chain::persist::PersistBackend;
|
||||||
use bdk_chain::Append;
|
use bdk_chain::Append;
|
||||||
use bdk_persist::PersistBackend;
|
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
@ -25,19 +24,21 @@ where
|
|||||||
impl<C> PersistBackend<C> for Store<C>
|
impl<C> PersistBackend<C> for Store<C>
|
||||||
where
|
where
|
||||||
C: Append
|
C: Append
|
||||||
|
+ Debug
|
||||||
+ serde::Serialize
|
+ serde::Serialize
|
||||||
+ serde::de::DeserializeOwned
|
+ serde::de::DeserializeOwned
|
||||||
+ core::marker::Send
|
+ core::marker::Send
|
||||||
+ core::marker::Sync,
|
+ 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)
|
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()
|
self.aggregate_changesets()
|
||||||
.map_err(|e| anyhow!(e.iter_error).context("error loading from persistence backend"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
//! let first_device = devices.remove(0)?;
|
//! let first_device = devices.remove(0)?;
|
||||||
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
|
//! 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,
|
//! # Network::Testnet,
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
[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"]
|
|
@ -1,5 +0,0 @@
|
|||||||
# 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.
|
|
@ -1,73 +0,0 @@
|
|||||||
#![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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
|
||||||
#![no_std]
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
|
|
||||||
mod changeset;
|
|
||||||
mod persist;
|
|
||||||
pub use changeset::*;
|
|
||||||
pub use persist::*;
|
|
@ -1,106 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,15 +5,13 @@ edition = "2021"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
documentation = "https://docs.rs/bdk_sqlite"
|
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"]
|
keywords = ["bitcoin", "persist", "persistence", "bdk", "sqlite"]
|
||||||
authors = ["Bitcoin Dev Kit Developers"]
|
authors = ["Bitcoin Dev Kit Developers"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1", default-features = false }
|
|
||||||
bdk_chain = { path = "../chain", version = "0.15.0", features = ["serde", "miniscript"] }
|
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"] }
|
rusqlite = { version = "0.31.0", features = ["bundled"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
@ -1,8 +1,8 @@
|
|||||||
# BDK SQLite
|
# 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
|
[SQLite]: https://www.sqlite.org/index.html
|
||||||
|
@ -12,10 +12,10 @@ use std::str::FromStr;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
use bdk_chain::persist::{CombinedChangeSet, PersistBackend};
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
indexed_tx_graph, keychain, local_chain, tx_graph, Anchor, Append, DescriptorExt, DescriptorId,
|
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.
|
/// Persists data in to a relational schema based [SQLite] database file.
|
||||||
///
|
///
|
||||||
@ -57,21 +57,23 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, A, C> bdk_persist::PersistBackend<C> for Store<K, A>
|
impl<K, A> PersistBackend<CombinedChangeSet<K, A>> for Store<K, A>
|
||||||
where
|
where
|
||||||
K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
|
K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
|
||||||
A: Anchor + 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<()> {
|
type WriteError = Error;
|
||||||
self.write(&changeset.clone().into())
|
type LoadError = Error;
|
||||||
.map_err(|e| anyhow::anyhow!(e).context("unable to write changes to sqlite database"))
|
|
||||||
|
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()
|
self.read()
|
||||||
.map(|c| c.map(Into::into))
|
|
||||||
.map_err(|e| anyhow::anyhow!(e).context("unable to read changes from sqlite database"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,11 +563,11 @@ mod test {
|
|||||||
use bdk_chain::bitcoin::Network::Testnet;
|
use bdk_chain::bitcoin::Network::Testnet;
|
||||||
use bdk_chain::bitcoin::{secp256k1, BlockHash, OutPoint};
|
use bdk_chain::bitcoin::{secp256k1, BlockHash, OutPoint};
|
||||||
use bdk_chain::miniscript::Descriptor;
|
use bdk_chain::miniscript::Descriptor;
|
||||||
|
use bdk_chain::persist::{CombinedChangeSet, PersistBackend};
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
indexed_tx_graph, keychain, tx_graph, BlockId, ConfirmationHeightAnchor,
|
indexed_tx_graph, keychain, tx_graph, BlockId, ConfirmationHeightAnchor,
|
||||||
ConfirmationTimeHeightAnchor, DescriptorExt,
|
ConfirmationTimeHeightAnchor, DescriptorExt,
|
||||||
};
|
};
|
||||||
use bdk_persist::PersistBackend;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -576,8 +578,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor(
|
fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor() {
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (test_changesets, agg_test_changesets) =
|
let (test_changesets, agg_test_changesets) =
|
||||||
create_test_changesets(&|height, time, hash| ConfirmationTimeHeightAnchor {
|
create_test_changesets(&|height, time, hash| ConfirmationTimeHeightAnchor {
|
||||||
confirmation_height: height,
|
confirmation_height: height,
|
||||||
@ -593,15 +594,13 @@ mod test {
|
|||||||
store.write_changes(changeset).expect("write changeset");
|
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));
|
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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) =
|
let (test_changesets, agg_test_changesets) =
|
||||||
create_test_changesets(&|height, _time, hash| ConfirmationHeightAnchor {
|
create_test_changesets(&|height, _time, hash| ConfirmationHeightAnchor {
|
||||||
confirmation_height: height,
|
confirmation_height: height,
|
||||||
@ -616,14 +615,13 @@ mod test {
|
|||||||
store.write_changes(changeset).expect("write changeset");
|
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));
|
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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) =
|
let (test_changesets, agg_test_changesets) =
|
||||||
create_test_changesets(&|height, _time, hash| BlockId { height, hash });
|
create_test_changesets(&|height, _time, hash| BlockId { height, hash });
|
||||||
|
|
||||||
@ -634,10 +632,9 @@ mod test {
|
|||||||
store.write_changes(changeset).expect("write changeset");
|
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));
|
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_test_changesets<A: Anchor + Copy>(
|
fn create_test_changesets<A: Anchor + Copy>(
|
||||||
|
@ -13,14 +13,12 @@ edition = "2021"
|
|||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1", default-features = false }
|
|
||||||
rand = "^0.8"
|
rand = "^0.8"
|
||||||
miniscript = { version = "12.0.0", features = ["serde"], default-features = false }
|
miniscript = { version = "12.0.0", features = ["serde"], default-features = false }
|
||||||
bitcoin = { version = "0.32.0", features = ["serde", "base64", "rand-std"], default-features = false }
|
bitcoin = { version = "0.32.0", features = ["serde", "base64", "rand-std"], default-features = false }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_json = { version = "^1.0" }
|
serde_json = { version = "^1.0" }
|
||||||
bdk_chain = { path = "../chain", version = "0.15.0", features = ["miniscript", "serde"], default-features = false }
|
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
|
# Optional dependencies
|
||||||
bip39 = { version = "2.0", optional = true }
|
bip39 = { version = "2.0", optional = true }
|
||||||
|
@ -57,11 +57,12 @@ that the `Wallet` can use to update its view of the chain.
|
|||||||
|
|
||||||
## Persistence
|
## 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**
|
**Implementations**
|
||||||
|
|
||||||
* [`bdk_file_store`]: A simple flat-file implementation of [`PersistBackend`].
|
* [`bdk_file_store`]: A simple flat-file implementation of [`PersistBackend`].
|
||||||
|
* [`bdk_sqlite`]: A simple sqlite implementation of [`PersistBackend`].
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
@ -71,15 +72,16 @@ use bdk_wallet::{bitcoin::Network, wallet::{ChangeSet, Wallet}};
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Create a new file `Store`.
|
// 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 descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
|
||||||
let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)";
|
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.
|
// Insert a single `TxOut` at `OutPoint` into the wallet.
|
||||||
let _ = wallet.insert_txout(outpoint, txout);
|
let _ = wallet.insert_txout(outpoint, txout);
|
||||||
wallet.commit().expect("must write to database");
|
wallet.commit_to(&mut db).expect("must commit changes to database");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -115,7 +117,7 @@ fn main() {
|
|||||||
<!-- use bdk_wallet::bitcoin::Network; -->
|
<!-- use bdk_wallet::bitcoin::Network; -->
|
||||||
|
|
||||||
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
|
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
|
||||||
<!-- let wallet = Wallet::new_no_persist( -->
|
<!-- let wallet = Wallet::new( -->
|
||||||
<!-- "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
|
<!-- "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
|
||||||
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
|
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
|
||||||
<!-- Network::Testnet, -->
|
<!-- Network::Testnet, -->
|
||||||
@ -144,7 +146,7 @@ fn main() {
|
|||||||
|
|
||||||
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
|
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
|
||||||
<!-- let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); -->
|
<!-- 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/*)", -->
|
<!-- "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
|
||||||
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
|
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
|
||||||
<!-- Network::Testnet, -->
|
<!-- Network::Testnet, -->
|
||||||
@ -180,7 +182,7 @@ fn main() {
|
|||||||
<!-- use bdk_wallet::bitcoin::Network; -->
|
<!-- use bdk_wallet::bitcoin::Network; -->
|
||||||
|
|
||||||
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
|
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
|
||||||
<!-- let wallet = Wallet::new_no_persist( -->
|
<!-- let wallet = Wallet::new( -->
|
||||||
<!-- "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", -->
|
<!-- "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", -->
|
||||||
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), -->
|
<!-- Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), -->
|
||||||
<!-- Network::Testnet, -->
|
<!-- Network::Testnet, -->
|
||||||
@ -220,9 +222,10 @@ license, shall be dual licensed as above, without any additional terms or
|
|||||||
conditions.
|
conditions.
|
||||||
|
|
||||||
[`Wallet`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/wallet/struct.Wallet.html
|
[`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_chain`]: https://docs.rs/bdk_chain/latest
|
||||||
[`bdk_file_store`]: https://docs.rs/bdk_file_store/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_electrum`]: https://docs.rs/bdk_electrum/latest
|
||||||
[`bdk_esplora`]: https://docs.rs/bdk_esplora/latest
|
[`bdk_esplora`]: https://docs.rs/bdk_esplora/latest
|
||||||
[`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest
|
[`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest
|
||||||
|
@ -77,11 +77,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create a new wallet from descriptors
|
// 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!(
|
println!(
|
||||||
"First derived address from the descriptor: \n{}",
|
"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
|
// BDK also has it's own `Policy` structure to represent the spending condition in a more
|
||||||
|
@ -81,12 +81,11 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
|||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let key_internal =
|
/// let key_internal =
|
||||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||||
/// let mut wallet =
|
/// let mut wallet = Wallet::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
|
||||||
/// Wallet::new_no_persist(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// wallet
|
/// wallet
|
||||||
/// .next_unused_address(KeychainKind::External)?
|
/// .next_unused_address(KeychainKind::External)
|
||||||
/// .to_string(),
|
/// .to_string(),
|
||||||
/// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
|
/// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
|
||||||
/// );
|
/// );
|
||||||
@ -114,7 +113,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
|||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let key_internal =
|
/// let key_internal =
|
||||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||||
/// let mut wallet = Wallet::new_no_persist(
|
/// let mut wallet = Wallet::new(
|
||||||
/// P2Wpkh_P2Sh(key_external),
|
/// P2Wpkh_P2Sh(key_external),
|
||||||
/// P2Wpkh_P2Sh(key_internal),
|
/// P2Wpkh_P2Sh(key_internal),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@ -122,7 +121,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
|||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// wallet
|
/// wallet
|
||||||
/// .next_unused_address(KeychainKind::External)?
|
/// .next_unused_address(KeychainKind::External)
|
||||||
/// .to_string(),
|
/// .to_string(),
|
||||||
/// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
|
/// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
|
||||||
/// );
|
/// );
|
||||||
@ -151,12 +150,11 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
|||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let key_internal =
|
/// let key_internal =
|
||||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||||
/// let mut wallet =
|
/// let mut wallet = Wallet::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
|
||||||
/// Wallet::new_no_persist(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// wallet
|
/// wallet
|
||||||
/// .next_unused_address(KeychainKind::External)?
|
/// .next_unused_address(KeychainKind::External)
|
||||||
/// .to_string(),
|
/// .to_string(),
|
||||||
/// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
|
/// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
|
||||||
/// );
|
/// );
|
||||||
@ -184,12 +182,11 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
|||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let key_internal =
|
/// let key_internal =
|
||||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||||
/// let mut wallet =
|
/// let mut wallet = Wallet::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
|
||||||
/// Wallet::new_no_persist(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
|
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// wallet
|
/// wallet
|
||||||
/// .next_unused_address(KeychainKind::External)?
|
/// .next_unused_address(KeychainKind::External)
|
||||||
/// .to_string(),
|
/// .to_string(),
|
||||||
/// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
|
/// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
|
||||||
/// );
|
/// );
|
||||||
@ -218,13 +215,13 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
|
|||||||
/// use bdk_wallet::template::Bip44;
|
/// use bdk_wallet::template::Bip44;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// 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.clone(), KeychainKind::External),
|
||||||
/// Bip44(key, KeychainKind::Internal),
|
/// Bip44(key, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
@ -255,13 +252,13 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
|||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
||||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
/// 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.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Bip44Public(key, fingerprint, KeychainKind::Internal),
|
/// Bip44Public(key, fingerprint, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
@ -291,13 +288,13 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
|||||||
/// use bdk_wallet::template::Bip49;
|
/// use bdk_wallet::template::Bip49;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// 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.clone(), KeychainKind::External),
|
||||||
/// Bip49(key, KeychainKind::Internal),
|
/// Bip49(key, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
@ -328,13 +325,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
|||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
||||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
/// 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.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Bip49Public(key, fingerprint, KeychainKind::Internal),
|
/// Bip49Public(key, fingerprint, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
@ -364,13 +361,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
|||||||
/// use bdk_wallet::template::Bip84;
|
/// use bdk_wallet::template::Bip84;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// 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.clone(), KeychainKind::External),
|
||||||
/// Bip84(key, KeychainKind::Internal),
|
/// Bip84(key, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
@ -401,13 +398,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
|||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
/// 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.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Bip84Public(key, fingerprint, KeychainKind::Internal),
|
/// Bip84Public(key, fingerprint, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
@ -437,13 +434,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
|||||||
/// use bdk_wallet::template::Bip86;
|
/// use bdk_wallet::template::Bip86;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// 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.clone(), KeychainKind::External),
|
||||||
/// Bip86(key, KeychainKind::Internal),
|
/// Bip86(key, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
@ -474,13 +471,13 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
|
|||||||
///
|
///
|
||||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
/// 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.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Bip86Public(key, fingerprint, KeychainKind::Internal),
|
/// Bip86Public(key, fingerprint, KeychainKind::Internal),
|
||||||
/// Network::Testnet,
|
/// 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");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
//! # use bitcoin::*;
|
//! # use bitcoin::*;
|
||||||
//! # use bdk_wallet::wallet::{self, ChangeSet, coin_selection::*, coin_selection};
|
//! # use bdk_wallet::wallet::{self, ChangeSet, coin_selection::*, coin_selection};
|
||||||
//! # use bdk_wallet::wallet::error::CreateTxError;
|
//! # use bdk_wallet::wallet::error::CreateTxError;
|
||||||
//! # use bdk_persist::PersistBackend;
|
|
||||||
//! # use bdk_wallet::*;
|
//! # use bdk_wallet::*;
|
||||||
//! # use bdk_wallet::wallet::coin_selection::decide_change;
|
//! # use bdk_wallet::wallet::coin_selection::decide_change;
|
||||||
//! # use anyhow::Error;
|
//! # use anyhow::Error;
|
||||||
|
@ -50,8 +50,6 @@ impl std::error::Error for MiniscriptPsbtError {}
|
|||||||
pub enum CreateTxError {
|
pub enum CreateTxError {
|
||||||
/// There was a problem with the descriptors passed in
|
/// There was a problem with the descriptors passed in
|
||||||
Descriptor(DescriptorError),
|
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
|
/// There was a problem while extracting and manipulating policies
|
||||||
Policy(PolicyError),
|
Policy(PolicyError),
|
||||||
/// Spending policy is not compatible with this [`KeychainKind`]
|
/// Spending policy is not compatible with this [`KeychainKind`]
|
||||||
@ -114,13 +112,6 @@ impl fmt::Display for CreateTxError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Descriptor(e) => e.fmt(f),
|
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),
|
Self::Policy(e) => e.fmt(f),
|
||||||
CreateTxError::SpendingPolicyRequired(keychain_kind) => {
|
CreateTxError::SpendingPolicyRequired(keychain_kind) => {
|
||||||
write!(f, "Spending policy required: {:?}", keychain_kind)
|
write!(f, "Spending policy required: {:?}", keychain_kind)
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
//! }"#;
|
//! }"#;
|
||||||
//!
|
//!
|
||||||
//! let import = FullyNodedExport::from_str(import)?;
|
//! let import = FullyNodedExport::from_str(import)?;
|
||||||
//! let wallet = Wallet::new_no_persist(
|
//! let wallet = Wallet::new(
|
||||||
//! &import.descriptor(),
|
//! &import.descriptor(),
|
||||||
//! &import.change_descriptor().expect("change descriptor"),
|
//! &import.change_descriptor().expect("change descriptor"),
|
||||||
//! Network::Testnet,
|
//! Network::Testnet,
|
||||||
@ -42,7 +42,7 @@
|
|||||||
//! # use bitcoin::*;
|
//! # use bitcoin::*;
|
||||||
//! # use bdk_wallet::wallet::export::*;
|
//! # use bdk_wallet::wallet::export::*;
|
||||||
//! # use bdk_wallet::*;
|
//! # 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/0/*)",
|
||||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
|
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
|
||||||
//! Network::Testnet,
|
//! Network::Testnet,
|
||||||
@ -222,7 +222,7 @@ mod test {
|
|||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> 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 {
|
let transaction = Transaction {
|
||||||
input: vec![],
|
input: vec![],
|
||||||
output: vec![],
|
output: vec![],
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
//! let first_device = devices.remove(0)?;
|
//! let first_device = devices.remove(0)?;
|
||||||
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
|
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
|
||||||
//!
|
//!
|
||||||
//! # let mut wallet = Wallet::new_no_persist(
|
//! # let mut wallet = Wallet::new(
|
||||||
//! # "",
|
//! # "",
|
||||||
//! # None,
|
//! # None,
|
||||||
//! # Network::Testnet,
|
//! # Network::Testnet,
|
||||||
|
@ -31,7 +31,6 @@ use bdk_chain::{
|
|||||||
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
|
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
|
||||||
Indexed, IndexedTxGraph,
|
Indexed, IndexedTxGraph,
|
||||||
};
|
};
|
||||||
use bdk_persist::{Persist, PersistBackend};
|
|
||||||
use bitcoin::secp256k1::{All, Secp256k1};
|
use bitcoin::secp256k1::{All, Secp256k1};
|
||||||
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
|
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
@ -41,6 +40,7 @@ use bitcoin::{
|
|||||||
use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
|
use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
|
||||||
use bitcoin::{constants::genesis_block, Amount};
|
use bitcoin::{constants::genesis_block, Amount};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use core::mem;
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use descriptor::error::Error as DescriptorError;
|
use descriptor::error::Error as DescriptorError;
|
||||||
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
|
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
|
||||||
@ -85,6 +85,11 @@ const COINBASE_MATURITY: u32 = 100;
|
|||||||
/// 1. output *descriptors* from which it can derive addresses.
|
/// 1. output *descriptors* from which it can derive addresses.
|
||||||
/// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors.
|
/// 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
|
/// [`signer`]: crate::signer
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
@ -92,7 +97,7 @@ pub struct Wallet {
|
|||||||
change_signers: Arc<SignersContainer>,
|
change_signers: Arc<SignersContainer>,
|
||||||
chain: LocalChain,
|
chain: LocalChain,
|
||||||
indexed_graph: IndexedTxGraph<ConfirmationTimeHeightAnchor, KeychainTxOutIndex<KeychainKind>>,
|
indexed_graph: IndexedTxGraph<ConfirmationTimeHeightAnchor, KeychainTxOutIndex<KeychainKind>>,
|
||||||
persist: Persist<ChangeSet>,
|
stage: ChangeSet,
|
||||||
network: Network,
|
network: Network,
|
||||||
secp: SecpCtx,
|
secp: SecpCtx,
|
||||||
}
|
}
|
||||||
@ -136,7 +141,8 @@ impl From<SyncResult> for Update {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The changes made to a wallet by applying an [`Update`].
|
/// 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.
|
/// A derived address and the index it was found at.
|
||||||
/// For convenience this automatically derefs to `Address`
|
/// For convenience this automatically derefs to `Address`
|
||||||
@ -164,36 +170,6 @@ impl fmt::Display for AddressInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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`].
|
/// The error type when constructing a fresh [`Wallet`].
|
||||||
///
|
///
|
||||||
/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
|
/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
|
||||||
@ -202,23 +178,14 @@ impl Wallet {
|
|||||||
/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
|
/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum NewError {
|
pub enum NewError {
|
||||||
/// Database already has data.
|
|
||||||
NonEmptyDatabase,
|
|
||||||
/// There was problem with the passed-in descriptor(s).
|
/// There was problem with the passed-in descriptor(s).
|
||||||
Descriptor(crate::descriptor::DescriptorError),
|
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 {
|
impl fmt::Display for NewError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
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::Descriptor(e) => e.fmt(f),
|
||||||
NewError::Persist(e) => e.fmt(f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,19 +193,15 @@ impl fmt::Display for NewError {
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl std::error::Error for NewError {}
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum LoadError {
|
pub enum LoadError {
|
||||||
/// There was a problem with the passed-in descriptor(s).
|
/// There was a problem with the passed-in descriptor(s).
|
||||||
Descriptor(crate::descriptor::DescriptorError),
|
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.
|
/// Data loaded from persistence is missing network type.
|
||||||
MissingNetwork,
|
MissingNetwork,
|
||||||
/// Data loaded from persistence is missing genesis hash.
|
/// Data loaded from persistence is missing genesis hash.
|
||||||
@ -251,10 +214,6 @@ impl fmt::Display for LoadError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
LoadError::Descriptor(e) => e.fmt(f),
|
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::MissingNetwork => write!(f, "loaded data is missing network type"),
|
||||||
LoadError::MissingGenesis => write!(f, "loaded data is missing genesis hash"),
|
LoadError::MissingGenesis => write!(f, "loaded data is missing genesis hash"),
|
||||||
LoadError::MissingDescriptor(k) => {
|
LoadError::MissingDescriptor(k) => {
|
||||||
@ -277,10 +236,6 @@ impl std::error::Error for LoadError {}
|
|||||||
pub enum NewOrLoadError {
|
pub enum NewOrLoadError {
|
||||||
/// There is a problem with the passed-in descriptor.
|
/// There is a problem with the passed-in descriptor.
|
||||||
Descriptor(crate::descriptor::DescriptorError),
|
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.
|
/// The loaded genesis hash does not match what was provided.
|
||||||
LoadedGenesisDoesNotMatch {
|
LoadedGenesisDoesNotMatch {
|
||||||
/// The expected genesis block hash.
|
/// The expected genesis block hash.
|
||||||
@ -308,14 +263,6 @@ impl fmt::Display for NewOrLoadError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
NewOrLoadError::Descriptor(e) => e.fmt(f),
|
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 } => {
|
NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
|
||||||
write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
|
write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
|
||||||
}
|
}
|
||||||
@ -403,11 +350,10 @@ impl Wallet {
|
|||||||
pub fn new<E: IntoWalletDescriptor>(
|
pub fn new<E: IntoWalletDescriptor>(
|
||||||
descriptor: E,
|
descriptor: E,
|
||||||
change_descriptor: E,
|
change_descriptor: E,
|
||||||
db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Self, NewError> {
|
) -> Result<Self, NewError> {
|
||||||
let genesis_hash = genesis_block(network).block_hash();
|
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.
|
/// Initialize an empty [`Wallet`] with a custom genesis hash.
|
||||||
@ -417,15 +363,9 @@ impl Wallet {
|
|||||||
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
|
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||||
descriptor: E,
|
descriptor: E,
|
||||||
change_descriptor: E,
|
change_descriptor: E,
|
||||||
mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
|
||||||
network: Network,
|
network: Network,
|
||||||
genesis_hash: BlockHash,
|
genesis_hash: BlockHash,
|
||||||
) -> Result<Self, NewError> {
|
) -> Result<Self, NewError> {
|
||||||
if let Ok(changeset) = db.load_from_persistence() {
|
|
||||||
if changeset.is_some() {
|
|
||||||
return Err(NewError::NonEmptyDatabase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
|
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
|
||||||
let mut index = KeychainTxOutIndex::<KeychainKind>::default();
|
let mut index = KeychainTxOutIndex::<KeychainKind>::default();
|
||||||
@ -436,13 +376,11 @@ impl Wallet {
|
|||||||
|
|
||||||
let indexed_graph = IndexedTxGraph::new(index);
|
let indexed_graph = IndexedTxGraph::new(index);
|
||||||
|
|
||||||
let mut persist = Persist::new(db);
|
let staged = ChangeSet {
|
||||||
persist.stage(ChangeSet {
|
|
||||||
chain: chain_changeset,
|
chain: chain_changeset,
|
||||||
indexed_tx_graph: indexed_graph.initial_changeset(),
|
indexed_tx_graph: indexed_graph.initial_changeset(),
|
||||||
network: Some(network),
|
network: Some(network),
|
||||||
});
|
};
|
||||||
persist.commit().map_err(NewError::Persist)?;
|
|
||||||
|
|
||||||
Ok(Wallet {
|
Ok(Wallet {
|
||||||
signers,
|
signers,
|
||||||
@ -450,12 +388,24 @@ impl Wallet {
|
|||||||
network,
|
network,
|
||||||
chain,
|
chain,
|
||||||
indexed_graph,
|
indexed_graph,
|
||||||
persist,
|
stage: staged,
|
||||||
secp,
|
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
|
/// 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
|
/// calling this method the [`Wallet`] **won't** know the secret keys, and as such, won't be
|
||||||
@ -473,10 +423,11 @@ impl Wallet {
|
|||||||
/// # use bdk_sqlite::{Store, rusqlite::Connection};
|
/// # use bdk_sqlite::{Store, rusqlite::Connection};
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() -> Result<(), anyhow::Error> {
|
/// # fn main() -> Result<(), anyhow::Error> {
|
||||||
|
/// # use bdk_chain::persist::PersistBackend;
|
||||||
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||||
/// # let file_path = temp_dir.path().join("store.db");
|
/// # let file_path = temp_dir.path().join("store.db");
|
||||||
/// # let conn = Connection::open(file_path).expect("must open connection");
|
/// # 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 secp = Secp256k1::new();
|
||||||
///
|
///
|
||||||
/// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
|
/// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
|
||||||
@ -484,8 +435,8 @@ impl Wallet {
|
|||||||
///
|
///
|
||||||
/// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
|
/// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
|
||||||
/// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
|
/// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
|
||||||
///
|
/// let changeset = db.load_changes()?.expect("there must be an existing changeset");
|
||||||
/// let mut wallet = Wallet::load(db)?;
|
/// let mut wallet = Wallet::load_from_changeset(changeset)?;
|
||||||
///
|
///
|
||||||
/// external_signer_container.signers().into_iter()
|
/// external_signer_container.signers().into_iter()
|
||||||
/// .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
|
/// .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
|
||||||
@ -497,20 +448,7 @@ impl Wallet {
|
|||||||
///
|
///
|
||||||
/// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
|
/// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
|
||||||
/// passed-in descriptors to the [`Wallet`].
|
/// passed-in descriptors to the [`Wallet`].
|
||||||
pub fn load(
|
pub fn load_from_changeset(changeset: ChangeSet) -> Result<Self, LoadError> {
|
||||||
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> {
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
|
let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
|
||||||
let chain =
|
let chain =
|
||||||
@ -538,157 +476,156 @@ impl Wallet {
|
|||||||
let mut indexed_graph = IndexedTxGraph::new(index);
|
let mut indexed_graph = IndexedTxGraph::new(index);
|
||||||
indexed_graph.apply_changeset(changeset.indexed_tx_graph);
|
indexed_graph.apply_changeset(changeset.indexed_tx_graph);
|
||||||
|
|
||||||
let persist = Persist::new(db);
|
let stage = ChangeSet::default();
|
||||||
|
|
||||||
Ok(Wallet {
|
Ok(Wallet {
|
||||||
signers,
|
signers,
|
||||||
change_signers,
|
change_signers,
|
||||||
chain,
|
chain,
|
||||||
indexed_graph,
|
indexed_graph,
|
||||||
persist,
|
stage,
|
||||||
network,
|
network,
|
||||||
secp,
|
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 [`Wallet`] has different parameters to those provided.
|
/// 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/*)";
|
||||||
|
///
|
||||||
|
/// let mut wallet = Wallet::new_or_load(external_descriptor, internal_descriptor, changeset, Testnet)?;
|
||||||
|
/// # Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn new_or_load<E: IntoWalletDescriptor>(
|
pub fn new_or_load<E: IntoWalletDescriptor>(
|
||||||
descriptor: E,
|
descriptor: E,
|
||||||
change_descriptor: E,
|
change_descriptor: E,
|
||||||
db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
changeset: Option<ChangeSet>,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Self, NewOrLoadError> {
|
) -> Result<Self, NewOrLoadError> {
|
||||||
let genesis_hash = genesis_block(network).block_hash();
|
let genesis_hash = genesis_block(network).block_hash();
|
||||||
Self::new_or_load_with_genesis_hash(
|
Self::new_or_load_with_genesis_hash(
|
||||||
descriptor,
|
descriptor,
|
||||||
change_descriptor,
|
change_descriptor,
|
||||||
db,
|
changeset,
|
||||||
network,
|
network,
|
||||||
genesis_hash,
|
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.
|
/// 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
|
/// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is
|
||||||
/// useful for syncing from alternative networks.
|
/// useful for syncing from alternative networks.
|
||||||
pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
|
pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||||
descriptor: E,
|
descriptor: E,
|
||||||
change_descriptor: E,
|
change_descriptor: E,
|
||||||
mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
changeset: Option<ChangeSet>,
|
||||||
network: Network,
|
network: Network,
|
||||||
genesis_hash: BlockHash,
|
genesis_hash: BlockHash,
|
||||||
) -> Result<Self, NewOrLoadError> {
|
) -> Result<Self, NewOrLoadError> {
|
||||||
let changeset = db
|
if let Some(changeset) = changeset {
|
||||||
.load_from_persistence()
|
let mut wallet = Self::load_from_changeset(changeset).map_err(|e| match e {
|
||||||
.map_err(NewOrLoadError::Persist)?;
|
LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
|
||||||
match changeset {
|
LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
|
||||||
Some(changeset) => {
|
expected: network,
|
||||||
let mut wallet = Self::load_from_changeset(db, changeset).map_err(|e| match e {
|
got: None,
|
||||||
LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
|
},
|
||||||
LoadError::Persist(e) => NewOrLoadError::Persist(e),
|
LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
|
||||||
LoadError::NotInitialized => NewOrLoadError::NotInitialized,
|
expected: genesis_hash,
|
||||||
LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
|
got: None,
|
||||||
expected: network,
|
},
|
||||||
|
LoadError::MissingDescriptor(keychain) => {
|
||||||
|
NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||||
got: None,
|
got: None,
|
||||||
},
|
keychain,
|
||||||
LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
|
|
||||||
expected: genesis_hash,
|
|
||||||
got: None,
|
|
||||||
},
|
|
||||||
LoadError::MissingDescriptor(keychain) => {
|
|
||||||
NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
|
||||||
got: None,
|
|
||||||
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 {
|
if wallet.network != network {
|
||||||
expected: genesis_hash,
|
return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
|
||||||
got: Some(wallet.chain.genesis_hash()),
|
expected: network,
|
||||||
});
|
got: Some(wallet.network),
|
||||||
}
|
});
|
||||||
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(wallet)
|
|
||||||
}
|
}
|
||||||
None => Self::new_with_genesis_hash(
|
if wallet.chain.genesis_hash() != genesis_hash {
|
||||||
descriptor,
|
return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
|
||||||
change_descriptor,
|
expected: genesis_hash,
|
||||||
db,
|
got: Some(wallet.chain.genesis_hash()),
|
||||||
network,
|
});
|
||||||
genesis_hash,
|
}
|
||||||
)
|
|
||||||
.map_err(|e| match e {
|
let (expected_descriptor, expected_descriptor_keymap) = descriptor
|
||||||
NewError::NonEmptyDatabase => {
|
.into_wallet_descriptor(&wallet.secp, network)
|
||||||
unreachable!("database is already checked to have no data")
|
.map_err(NewOrLoadError::Descriptor)?;
|
||||||
}
|
let wallet_descriptor = wallet.public_descriptor(KeychainKind::External);
|
||||||
NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
|
if wallet_descriptor != &expected_descriptor {
|
||||||
NewError::Persist(e) => NewOrLoadError::Persist(e),
|
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(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,30 +669,46 @@ impl Wallet {
|
|||||||
|
|
||||||
/// Attempt to reveal the next address of the given `keychain`.
|
/// 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
|
/// 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),
|
/// 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.
|
/// ```rust,no_run
|
||||||
pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
|
/// # use bdk_chain::persist::PersistBackend;
|
||||||
let ((index, spk), index_changeset) = self
|
/// # use bdk_wallet::wallet::{Wallet, ChangeSet};
|
||||||
.indexed_graph
|
/// # use bdk_wallet::KeychainKind;
|
||||||
.index
|
/// # 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)
|
.reveal_next_spk(&keychain)
|
||||||
.expect("keychain must exist");
|
.expect("keychain must exist");
|
||||||
|
|
||||||
self.persist
|
stage.append(indexed_tx_graph::ChangeSet::from(index_changeset).into());
|
||||||
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
|
|
||||||
|
|
||||||
Ok(AddressInfo {
|
AddressInfo {
|
||||||
index,
|
index,
|
||||||
address: Address::from_script(spk.as_script(), self.network)
|
address: Address::from_script(spk.as_script(), self.network)
|
||||||
.expect("must have address form"),
|
.expect("must have address form"),
|
||||||
keychain,
|
keychain,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reveal addresses up to and including the target `index` and return an iterator
|
/// Reveal addresses up to and including the target `index` and return an iterator
|
||||||
@ -765,28 +718,26 @@ impl Wallet {
|
|||||||
/// possible index. If all addresses up to the given `index` are already revealed, then
|
/// possible index. If all addresses up to the given `index` are already revealed, then
|
||||||
/// no new addresses are returned.
|
/// no new addresses are 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. See [`Wallet::reveal_next_address`].
|
||||||
/// If writing to persistent storage fails.
|
|
||||||
pub fn reveal_addresses_to(
|
pub fn reveal_addresses_to(
|
||||||
&mut self,
|
&mut self,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
index: u32,
|
index: u32,
|
||||||
) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
|
) -> impl Iterator<Item = AddressInfo> + '_ {
|
||||||
let (spks, index_changeset) = self
|
let (spks, index_changeset) = self
|
||||||
.indexed_graph
|
.indexed_graph
|
||||||
.index
|
.index
|
||||||
.reveal_to_target(&keychain, index)
|
.reveal_to_target(&keychain, index)
|
||||||
.expect("keychain must exist");
|
.expect("keychain must exist");
|
||||||
|
|
||||||
self.persist
|
self.stage(indexed_tx_graph::ChangeSet::from(index_changeset).into());
|
||||||
.stage_and_commit(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,
|
index,
|
||||||
address: Address::from_script(&spk, self.network).expect("must have address form"),
|
address: Address::from_script(&spk, self.network).expect("must have address form"),
|
||||||
keychain,
|
keychain,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the next unused address for the given `keychain`, i.e. the address with the lowest
|
/// Get the next unused address for the given `keychain`, i.e. the address with the lowest
|
||||||
@ -795,25 +746,24 @@ impl Wallet {
|
|||||||
/// This will attempt to derive and reveal a new address if no newly revealed addresses
|
/// 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).
|
/// are available. See also [`reveal_next_address`](Self::reveal_next_address).
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// **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`].
|
||||||
/// If writing to persistent storage fails.
|
pub fn next_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo {
|
||||||
pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
|
let index = &mut self.indexed_graph.index;
|
||||||
let ((index, spk), index_changeset) = self
|
|
||||||
.indexed_graph
|
let ((index, spk), index_changeset) = index
|
||||||
.index
|
|
||||||
.next_unused_spk(&keychain)
|
.next_unused_spk(&keychain)
|
||||||
.expect("keychain must exist");
|
.expect("keychain must exist");
|
||||||
|
|
||||||
self.persist
|
self.stage
|
||||||
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
|
.append(indexed_tx_graph::ChangeSet::from(index_changeset).into());
|
||||||
|
|
||||||
Ok(AddressInfo {
|
AddressInfo {
|
||||||
index,
|
index,
|
||||||
address: Address::from_script(spk.as_script(), self.network)
|
address: Address::from_script(spk.as_script(), self.network)
|
||||||
.expect("must have address form"),
|
.expect("must have address form"),
|
||||||
keychain,
|
keychain,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks an address used of the given `keychain` at `index`.
|
/// Marks an address used of the given `keychain` at `index`.
|
||||||
@ -952,19 +902,20 @@ impl Wallet {
|
|||||||
/// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will
|
/// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will
|
||||||
/// not be returned in [`list_unspent`] or [`list_output`].
|
/// not be returned in [`list_unspent`] or [`list_output`].
|
||||||
///
|
///
|
||||||
/// Any inserted `TxOut`s are not persisted until [`commit`] is called.
|
/// **WARNINGS:** This should only be used to add `TxOut`s that the wallet does not own. Only
|
||||||
///
|
|
||||||
/// **WARNING:** 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!
|
/// 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`]: Self::calculate_fee
|
||||||
/// [`calculate_fee_rate`]: Self::calculate_fee_rate
|
/// [`calculate_fee_rate`]: Self::calculate_fee_rate
|
||||||
/// [`list_unspent`]: Self::list_unspent
|
/// [`list_unspent`]: Self::list_unspent
|
||||||
/// [`list_output`]: Self::list_output
|
/// [`list_output`]: Self::list_output
|
||||||
/// [`commit`]: Self::commit
|
|
||||||
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) {
|
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) {
|
||||||
let additions = self.indexed_graph.insert_txout(outpoint, 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.
|
/// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase transaction.
|
||||||
@ -1118,11 +1069,14 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new checkpoint to the wallet's internal view of the chain.
|
/// 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
|
/// Returns whether anything changed with the insertion (e.g. `false` if checkpoint was already
|
||||||
/// there).
|
/// 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
|
/// [`commit`]: Self::commit
|
||||||
pub fn insert_checkpoint(
|
pub fn insert_checkpoint(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -1130,12 +1084,12 @@ impl Wallet {
|
|||||||
) -> Result<bool, local_chain::AlterCheckPointError> {
|
) -> Result<bool, local_chain::AlterCheckPointError> {
|
||||||
let changeset = self.chain.insert_block(block_id)?;
|
let changeset = self.chain.insert_block(block_id)?;
|
||||||
let changed = !changeset.is_empty();
|
let changed = !changeset.is_empty();
|
||||||
self.persist.stage(changeset.into());
|
self.stage(changeset.into());
|
||||||
Ok(changed)
|
Ok(changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a transaction to the wallet's internal view of the chain. This stages but does not
|
/// Add a transaction to the wallet's internal view of the chain. This stages the change,
|
||||||
/// [`commit`] the change.
|
/// you must persist it later.
|
||||||
///
|
///
|
||||||
/// Returns whether anything changed with the transaction insertion (e.g. `false` if the
|
/// Returns whether anything changed with the transaction insertion (e.g. `false` if the
|
||||||
/// transaction was already inserted at the same position).
|
/// transaction was already inserted at the same position).
|
||||||
@ -1144,10 +1098,13 @@ impl Wallet {
|
|||||||
/// Therefore you should use [`insert_checkpoint`] to insert new checkpoints before manually
|
/// Therefore you should use [`insert_checkpoint`] to insert new checkpoints before manually
|
||||||
/// inserting new transactions.
|
/// 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
|
/// is >= the `position`'s height. The caller is responsible for ensuring the `tx` exists in our
|
||||||
/// local view of the best chain's history.
|
/// 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
|
/// [`commit`]: Self::commit
|
||||||
/// [`latest_checkpoint`]: Self::latest_checkpoint
|
/// [`latest_checkpoint`]: Self::latest_checkpoint
|
||||||
/// [`insert_checkpoint`]: Self::insert_checkpoint
|
/// [`insert_checkpoint`]: Self::insert_checkpoint
|
||||||
@ -1189,7 +1146,7 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let changed = !changeset.is_empty();
|
let changed = !changeset.is_empty();
|
||||||
self.persist.stage(changeset);
|
self.stage(changeset);
|
||||||
Ok(changed)
|
Ok(changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1240,7 +1197,7 @@ impl Wallet {
|
|||||||
/// # use bdk_wallet::bitcoin::Network;
|
/// # use bdk_wallet::bitcoin::Network;
|
||||||
/// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
|
/// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
|
||||||
/// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
|
/// 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()) {
|
/// 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/*
|
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
|
||||||
/// println!("secret_key: {}", secret_key);
|
/// println!("secret_key: {}", secret_key);
|
||||||
@ -1267,7 +1224,6 @@ impl Wallet {
|
|||||||
/// # use bdk_wallet::*;
|
/// # use bdk_wallet::*;
|
||||||
/// # use bdk_wallet::wallet::ChangeSet;
|
/// # use bdk_wallet::wallet::ChangeSet;
|
||||||
/// # use bdk_wallet::wallet::error::CreateTxError;
|
/// # use bdk_wallet::wallet::error::CreateTxError;
|
||||||
/// # use bdk_persist::PersistBackend;
|
|
||||||
/// # use anyhow::Error;
|
/// # use anyhow::Error;
|
||||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||||
/// # let mut wallet = doctest_wallet!();
|
/// # let mut wallet = doctest_wallet!();
|
||||||
@ -1514,11 +1470,9 @@ impl Wallet {
|
|||||||
.next_unused_spk(&change_keychain)
|
.next_unused_spk(&change_keychain)
|
||||||
.expect("keychain must exist");
|
.expect("keychain must exist");
|
||||||
self.indexed_graph.index.mark_used(change_keychain, index);
|
self.indexed_graph.index.mark_used(change_keychain, index);
|
||||||
self.persist
|
self.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
|
||||||
.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
|
index_changeset,
|
||||||
index_changeset,
|
)));
|
||||||
)));
|
|
||||||
self.persist.commit().map_err(CreateTxError::Persist)?;
|
|
||||||
spk
|
spk
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1617,7 +1571,6 @@ impl Wallet {
|
|||||||
/// # use bdk_wallet::*;
|
/// # use bdk_wallet::*;
|
||||||
/// # use bdk_wallet::wallet::ChangeSet;
|
/// # use bdk_wallet::wallet::ChangeSet;
|
||||||
/// # use bdk_wallet::wallet::error::CreateTxError;
|
/// # use bdk_wallet::wallet::error::CreateTxError;
|
||||||
/// # use bdk_persist::PersistBackend;
|
|
||||||
/// # use anyhow::Error;
|
/// # use anyhow::Error;
|
||||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||||
/// # let mut wallet = doctest_wallet!();
|
/// # let mut wallet = doctest_wallet!();
|
||||||
@ -1796,7 +1749,6 @@ impl Wallet {
|
|||||||
/// # use bdk_wallet::*;
|
/// # use bdk_wallet::*;
|
||||||
/// # use bdk_wallet::wallet::ChangeSet;
|
/// # use bdk_wallet::wallet::ChangeSet;
|
||||||
/// # use bdk_wallet::wallet::error::CreateTxError;
|
/// # use bdk_wallet::wallet::error::CreateTxError;
|
||||||
/// # use bdk_persist::PersistBackend;
|
|
||||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||||
/// # let mut wallet = doctest_wallet!();
|
/// # let mut wallet = doctest_wallet!();
|
||||||
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
|
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
|
||||||
@ -2319,11 +2271,14 @@ impl Wallet {
|
|||||||
.to_string()
|
.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
|
/// Usually you create an `update` by interacting with some blockchain data source and inserting
|
||||||
/// transactions related to your wallet into it.
|
/// 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
|
/// [`commit`]: Self::commit
|
||||||
pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
|
pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
|
||||||
let update = update.into();
|
let update = update.into();
|
||||||
@ -2342,27 +2297,10 @@ impl Wallet {
|
|||||||
changeset.append(ChangeSet::from(
|
changeset.append(ChangeSet::from(
|
||||||
self.indexed_graph.apply_update(update.graph),
|
self.indexed_graph.apply_update(update.graph),
|
||||||
));
|
));
|
||||||
self.persist.stage(changeset);
|
self.stage(changeset);
|
||||||
Ok(())
|
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`].
|
/// Get a reference to the inner [`TxGraph`].
|
||||||
pub fn tx_graph(&self) -> &TxGraph<ConfirmationTimeHeightAnchor> {
|
pub fn tx_graph(&self) -> &TxGraph<ConfirmationTimeHeightAnchor> {
|
||||||
self.indexed_graph.graph()
|
self.indexed_graph.graph()
|
||||||
@ -2411,6 +2349,10 @@ impl Wallet {
|
|||||||
/// The `connected_to` parameter informs the wallet how this block connects to the internal
|
/// 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
|
/// [`LocalChain`]. Relevant transactions are filtered from the `block` and inserted into the
|
||||||
/// internal [`TxGraph`].
|
/// 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(
|
pub fn apply_block_connected_to(
|
||||||
&mut self,
|
&mut self,
|
||||||
block: &Block,
|
block: &Block,
|
||||||
@ -2428,7 +2370,7 @@ impl Wallet {
|
|||||||
.apply_block_relevant(block, height)
|
.apply_block_relevant(block, height)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
self.persist.stage(changeset);
|
self.stage(changeset);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2440,6 +2382,10 @@ impl Wallet {
|
|||||||
/// when the transaction was last seen in the mempool. This is used for conflict resolution
|
/// 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
|
/// when there is conflicting unconfirmed transactions. The transaction with the later
|
||||||
/// `last_seen` is prioritized.
|
/// `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>(
|
pub fn apply_unconfirmed_txs<'t>(
|
||||||
&mut self,
|
&mut self,
|
||||||
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
|
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
|
||||||
@ -2447,7 +2393,7 @@ impl Wallet {
|
|||||||
let indexed_graph_changeset = self
|
let indexed_graph_changeset = self
|
||||||
.indexed_graph
|
.indexed_graph
|
||||||
.batch_insert_relevant_unconfirmed(unconfirmed_txs);
|
.batch_insert_relevant_unconfirmed(unconfirmed_txs);
|
||||||
self.persist.stage(ChangeSet::from(indexed_graph_changeset));
|
self.stage(ChangeSet::from(indexed_graph_changeset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2584,7 +2530,7 @@ macro_rules! doctest_wallet {
|
|||||||
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
|
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
|
||||||
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
|
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
|
||||||
|
|
||||||
let mut wallet = Wallet::new_no_persist(
|
let mut wallet = Wallet::new(
|
||||||
descriptor,
|
descriptor,
|
||||||
change_descriptor,
|
change_descriptor,
|
||||||
Network::Regtest,
|
Network::Regtest,
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
//!
|
//!
|
||||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
|
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
|
||||||
//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
|
//! 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(
|
//! wallet.add_signer(
|
||||||
//! KeychainKind::External,
|
//! KeychainKind::External,
|
||||||
//! SignerOrdering(200),
|
//! SignerOrdering(200),
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
//! # use bdk_wallet::*;
|
//! # use bdk_wallet::*;
|
||||||
//! # use bdk_wallet::wallet::ChangeSet;
|
//! # use bdk_wallet::wallet::ChangeSet;
|
||||||
//! # use bdk_wallet::wallet::error::CreateTxError;
|
//! # use bdk_wallet::wallet::error::CreateTxError;
|
||||||
//! # use bdk_persist::PersistBackend;
|
|
||||||
//! # use anyhow::Error;
|
//! # use anyhow::Error;
|
||||||
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
|
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
|
||||||
//! # let mut wallet = doctest_wallet!();
|
//! # let mut wallet = doctest_wallet!();
|
||||||
@ -68,7 +67,6 @@ use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo};
|
|||||||
/// # use core::str::FromStr;
|
/// # use core::str::FromStr;
|
||||||
/// # use bdk_wallet::wallet::ChangeSet;
|
/// # use bdk_wallet::wallet::ChangeSet;
|
||||||
/// # use bdk_wallet::wallet::error::CreateTxError;
|
/// # use bdk_wallet::wallet::error::CreateTxError;
|
||||||
/// # use bdk_persist::PersistBackend;
|
|
||||||
/// # use anyhow::Error;
|
/// # use anyhow::Error;
|
||||||
/// # let mut wallet = doctest_wallet!();
|
/// # let mut wallet = doctest_wallet!();
|
||||||
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
|
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
|
||||||
@ -640,7 +638,6 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
|
|||||||
/// # use bdk_wallet::*;
|
/// # use bdk_wallet::*;
|
||||||
/// # use bdk_wallet::wallet::ChangeSet;
|
/// # use bdk_wallet::wallet::ChangeSet;
|
||||||
/// # use bdk_wallet::wallet::error::CreateTxError;
|
/// # use bdk_wallet::wallet::error::CreateTxError;
|
||||||
/// # use bdk_persist::PersistBackend;
|
|
||||||
/// # use anyhow::Error;
|
/// # use anyhow::Error;
|
||||||
/// # let to_address =
|
/// # let to_address =
|
||||||
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
|
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
|
||||||
@ -675,6 +672,9 @@ impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs> {
|
|||||||
/// Returns a new [`Psbt`] per [`BIP174`].
|
/// Returns a new [`Psbt`] per [`BIP174`].
|
||||||
///
|
///
|
||||||
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
/// [`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> {
|
pub fn finish(self) -> Result<Psbt, CreateTxError> {
|
||||||
self.wallet
|
self.wallet
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
@ -16,7 +16,7 @@ use std::str::FromStr;
|
|||||||
/// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000
|
/// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000
|
||||||
/// sats are the transaction fee.
|
/// sats are the transaction fee.
|
||||||
pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) {
|
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 receive_address = wallet.peek_address(KeychainKind::External, 0).address;
|
||||||
let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
|
let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
|
||||||
.expect("address")
|
.expect("address")
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ use bdk_bitcoind_rpc::{
|
|||||||
bitcoincore_rpc::{Auth, Client, RpcApi},
|
bitcoincore_rpc::{Auth, Client, RpcApi},
|
||||||
Emitter,
|
Emitter,
|
||||||
};
|
};
|
||||||
|
use bdk_chain::persist::PersistBackend;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{constants::genesis_block, Block, Transaction},
|
bitcoin::{constants::genesis_block, Block, Transaction},
|
||||||
indexed_tx_graph, keychain,
|
indexed_tx_graph, keychain,
|
||||||
@ -137,8 +138,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let genesis_hash = genesis_block(args.network).block_hash();
|
let genesis_hash = genesis_block(args.network).block_hash();
|
||||||
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
|
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
db.stage((chain_changeset, Default::default()));
|
db.write_changes(&(chain_changeset, Default::default()))?;
|
||||||
db.commit()?;
|
|
||||||
chain
|
chain
|
||||||
} else {
|
} else {
|
||||||
LocalChain::from_changeset(init_chain_changeset)?
|
LocalChain::from_changeset(init_chain_changeset)?
|
||||||
@ -191,12 +191,11 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.apply_update(emission.checkpoint)
|
.apply_update(emission.checkpoint)
|
||||||
.expect("must always apply as we receive blocks in order from emitter");
|
.expect("must always apply as we receive blocks in order from emitter");
|
||||||
let graph_changeset = graph.apply_block_relevant(&emission.block, height);
|
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
|
// commit staged db changes in intervals
|
||||||
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
||||||
last_db_commit = Instant::now();
|
last_db_commit = Instant::now();
|
||||||
db.commit()?;
|
|
||||||
println!(
|
println!(
|
||||||
"[{:>10}s] committed to db (took {}s)",
|
"[{:>10}s] committed to db (took {}s)",
|
||||||
start.elapsed().as_secs_f32(),
|
start.elapsed().as_secs_f32(),
|
||||||
@ -232,8 +231,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
);
|
);
|
||||||
{
|
{
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
db.stage((local_chain::ChangeSet::default(), graph_changeset));
|
db.write_changes(&(local_chain::ChangeSet::default(), graph_changeset))?;
|
||||||
db.commit()?; // commit one last time
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RpcCommands::Live { rpc_args } => {
|
RpcCommands::Live { rpc_args } => {
|
||||||
@ -317,11 +315,10 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
db.stage(changeset);
|
db.write_changes(&changeset)?;
|
||||||
|
|
||||||
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
||||||
last_db_commit = Instant::now();
|
last_db_commit = Instant::now();
|
||||||
db.commit()?;
|
|
||||||
println!(
|
println!(
|
||||||
"[{:>10}s] committed to db (took {}s)",
|
"[{:>10}s] committed to db (took {}s)",
|
||||||
start.elapsed().as_secs_f32(),
|
start.elapsed().as_secs_f32(),
|
||||||
|
@ -7,7 +7,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk_chain = { path = "../../crates/chain", features = ["serde", "miniscript"]}
|
bdk_chain = { path = "../../crates/chain", features = ["serde", "miniscript"]}
|
||||||
bdk_persist = { path = "../../crates/persist" }
|
|
||||||
bdk_file_store = { path = "../../crates/file_store" }
|
bdk_file_store = { path = "../../crates/file_store" }
|
||||||
bdk_tmp_plan = { path = "../../nursery/tmp_plan" }
|
bdk_tmp_plan = { path = "../../nursery/tmp_plan" }
|
||||||
bdk_coin_select = { path = "../../nursery/coin_select" }
|
bdk_coin_select = { path = "../../nursery/coin_select" }
|
||||||
|
@ -3,6 +3,7 @@ use anyhow::Context;
|
|||||||
use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue};
|
use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue};
|
||||||
use bdk_file_store::Store;
|
use bdk_file_store::Store;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::{cmp::Reverse, collections::BTreeMap, path::PathBuf, sync::Mutex, time::Duration};
|
use std::{cmp::Reverse, collections::BTreeMap, path::PathBuf, sync::Mutex, time::Duration};
|
||||||
|
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
@ -22,9 +23,9 @@ use bdk_chain::{
|
|||||||
Anchor, Append, ChainOracle, DescriptorExt, FullTxOut,
|
Anchor, Append, ChainOracle, DescriptorExt, FullTxOut,
|
||||||
};
|
};
|
||||||
pub use bdk_file_store;
|
pub use bdk_file_store;
|
||||||
use bdk_persist::{Persist, PersistBackend};
|
|
||||||
pub use clap;
|
pub use clap;
|
||||||
|
|
||||||
|
use bdk_chain::persist::PersistBackend;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
|
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
|
||||||
@ -446,7 +447,7 @@ pub fn planned_utxos<A: Anchor, O: ChainOracle, K: Clone + bdk_tmp_plan::CanDeri
|
|||||||
|
|
||||||
pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainOracle, C>(
|
pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainOracle, C>(
|
||||||
graph: &Mutex<KeychainTxGraph<A>>,
|
graph: &Mutex<KeychainTxGraph<A>>,
|
||||||
db: &Mutex<Persist<C>>,
|
db: &Mutex<Store<C>>,
|
||||||
chain: &Mutex<O>,
|
chain: &Mutex<O>,
|
||||||
keymap: &BTreeMap<DescriptorPublicKey, DescriptorSecretKey>,
|
keymap: &BTreeMap<DescriptorPublicKey, DescriptorSecretKey>,
|
||||||
network: Network,
|
network: Network,
|
||||||
@ -455,7 +456,14 @@ pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainO
|
|||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
O::Error: std::error::Error + Send + Sync + 'static,
|
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 {
|
match cmd {
|
||||||
Commands::ChainSpecific(_) => unreachable!("example code should handle this!"),
|
Commands::ChainSpecific(_) => unreachable!("example code should handle this!"),
|
||||||
@ -474,7 +482,7 @@ where
|
|||||||
let ((spk_i, spk), index_changeset) =
|
let ((spk_i, spk), index_changeset) =
|
||||||
spk_chooser(index, &Keychain::External).expect("Must exist");
|
spk_chooser(index, &Keychain::External).expect("Must exist");
|
||||||
let db = &mut *db.lock().unwrap();
|
let db = &mut *db.lock().unwrap();
|
||||||
db.stage_and_commit(C::from((
|
db.write_changes(&C::from((
|
||||||
local_chain::ChangeSet::default(),
|
local_chain::ChangeSet::default(),
|
||||||
indexed_tx_graph::ChangeSet::from(index_changeset),
|
indexed_tx_graph::ChangeSet::from(index_changeset),
|
||||||
)))?;
|
)))?;
|
||||||
@ -622,7 +630,7 @@ where
|
|||||||
// If we're unable to persist this, then we don't want to broadcast.
|
// If we're unable to persist this, then we don't want to broadcast.
|
||||||
{
|
{
|
||||||
let db = &mut *db.lock().unwrap();
|
let db = &mut *db.lock().unwrap();
|
||||||
db.stage_and_commit(C::from((
|
db.write_changes(&C::from((
|
||||||
local_chain::ChangeSet::default(),
|
local_chain::ChangeSet::default(),
|
||||||
indexed_tx_graph::ChangeSet::from(index_changeset),
|
indexed_tx_graph::ChangeSet::from(index_changeset),
|
||||||
)))?;
|
)))?;
|
||||||
@ -647,7 +655,7 @@ where
|
|||||||
// We know the tx is at least unconfirmed now. Note if persisting here fails,
|
// 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
|
// it's not a big deal since we can always find it again form
|
||||||
// blockchain.
|
// blockchain.
|
||||||
db.lock().unwrap().stage_and_commit(C::from((
|
db.lock().unwrap().write_changes(&C::from((
|
||||||
local_chain::ChangeSet::default(),
|
local_chain::ChangeSet::default(),
|
||||||
keychain_changeset,
|
keychain_changeset,
|
||||||
)))?;
|
)))?;
|
||||||
@ -666,7 +674,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The initial state returned by [`init`].
|
/// 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.
|
/// Arguments parsed by the cli.
|
||||||
pub args: Args<CS, S>,
|
pub args: Args<CS, S>,
|
||||||
/// Descriptor keymap.
|
/// Descriptor keymap.
|
||||||
@ -674,7 +685,7 @@ pub struct Init<CS: clap::Subcommand, S: clap::Args, C> {
|
|||||||
/// Keychain-txout index.
|
/// Keychain-txout index.
|
||||||
pub index: KeychainTxOutIndex<Keychain>,
|
pub index: KeychainTxOutIndex<Keychain>,
|
||||||
/// Persistence backend.
|
/// Persistence backend.
|
||||||
pub db: Mutex<Persist<C>>,
|
pub db: Mutex<Store<C>>,
|
||||||
/// Initial changeset.
|
/// Initial changeset.
|
||||||
pub init_changeset: C,
|
pub init_changeset: C,
|
||||||
}
|
}
|
||||||
@ -690,6 +701,7 @@ where
|
|||||||
+ Append
|
+ Append
|
||||||
+ Serialize
|
+ Serialize
|
||||||
+ DeserializeOwned
|
+ DeserializeOwned
|
||||||
|
+ Debug
|
||||||
+ core::marker::Send
|
+ core::marker::Send
|
||||||
+ core::marker::Sync
|
+ core::marker::Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
@ -724,13 +736,13 @@ where
|
|||||||
Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)),
|
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 {
|
Ok(Init {
|
||||||
args,
|
args,
|
||||||
keymap,
|
keymap,
|
||||||
index,
|
index,
|
||||||
db: Mutex::new(Persist::new(db_backend)),
|
db: Mutex::new(db_backend),
|
||||||
init_changeset,
|
init_changeset,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use std::{
|
|||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use bdk_chain::persist::PersistBackend;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{constants::genesis_block, Address, Network, Txid},
|
bitcoin::{constants::genesis_block, Address, Network, Txid},
|
||||||
collections::BTreeSet,
|
collections::BTreeSet,
|
||||||
@ -351,7 +352,6 @@ fn main() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
db.stage(db_changeset);
|
db.write_changes(&db_changeset)?;
|
||||||
db.commit()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use std::{
|
|||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use bdk_chain::persist::PersistBackend;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{constants::genesis_block, Address, Network, Txid},
|
bitcoin::{constants::genesis_block, Address, Network, Txid},
|
||||||
indexed_tx_graph::{self, IndexedTxGraph},
|
indexed_tx_graph::{self, IndexedTxGraph},
|
||||||
@ -361,7 +362,6 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// We persist the changes
|
// We persist the changes
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
db.stage((local_chain_changeset, indexed_tx_graph_changeset));
|
db.write_changes(&(local_chain_changeset, indexed_tx_graph_changeset))?;
|
||||||
db.commit()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ const SEND_AMOUNT: Amount = Amount::from_sat(5000);
|
|||||||
const STOP_GAP: usize = 50;
|
const STOP_GAP: usize = 50;
|
||||||
const BATCH_SIZE: usize = 5;
|
const BATCH_SIZE: usize = 5;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@ -11,24 +12,28 @@ use bdk_electrum::BdkElectrumClient;
|
|||||||
use bdk_file_store::Store;
|
use bdk_file_store::Store;
|
||||||
use bdk_wallet::bitcoin::{Address, Amount};
|
use bdk_wallet::bitcoin::{Address, Amount};
|
||||||
use bdk_wallet::chain::collections::HashSet;
|
use bdk_wallet::chain::collections::HashSet;
|
||||||
|
use bdk_wallet::chain::persist::PersistBackend;
|
||||||
use bdk_wallet::{bitcoin::Network, Wallet};
|
use bdk_wallet::{bitcoin::Network, Wallet};
|
||||||
use bdk_wallet::{KeychainKind, SignOptions};
|
use bdk_wallet::{KeychainKind, SignOptions};
|
||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
let db_path = std::env::temp_dir().join("bdk-electrum-example");
|
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)?;
|
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 external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||||
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
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(
|
let mut wallet = Wallet::new_or_load(
|
||||||
external_descriptor,
|
external_descriptor,
|
||||||
internal_descriptor,
|
internal_descriptor,
|
||||||
db,
|
changeset,
|
||||||
Network::Testnet,
|
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);
|
println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
@ -67,7 +72,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
println!();
|
println!();
|
||||||
|
|
||||||
wallet.apply_update(update)?;
|
wallet.apply_update(update)?;
|
||||||
wallet.commit()?;
|
db.write_changes(&wallet.take_staged())?;
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
println!("Wallet balance after syncing: {} sats", balance.total());
|
println!("Wallet balance after syncing: {} sats", balance.total());
|
||||||
|
@ -7,6 +7,7 @@ use bdk_wallet::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bdk_sqlite::{rusqlite::Connection, Store};
|
use bdk_sqlite::{rusqlite::Connection, Store};
|
||||||
|
use bdk_wallet::chain::persist::PersistBackend;
|
||||||
|
|
||||||
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
|
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
|
||||||
const STOP_GAP: usize = 50;
|
const STOP_GAP: usize = 50;
|
||||||
@ -16,18 +17,20 @@ const PARALLEL_REQUESTS: usize = 5;
|
|||||||
async fn main() -> Result<(), anyhow::Error> {
|
async fn main() -> Result<(), anyhow::Error> {
|
||||||
let db_path = "bdk-esplora-async-example.sqlite";
|
let db_path = "bdk-esplora-async-example.sqlite";
|
||||||
let conn = Connection::open(db_path)?;
|
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 external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||||
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||||
|
let changeset = db.load_changes()?;
|
||||||
|
|
||||||
let mut wallet = Wallet::new_or_load(
|
let mut wallet = Wallet::new_or_load(
|
||||||
external_descriptor,
|
external_descriptor,
|
||||||
internal_descriptor,
|
internal_descriptor,
|
||||||
db,
|
changeset,
|
||||||
Network::Signet,
|
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);
|
println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
@ -75,7 +78,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
let _ = update.graph_update.update_last_seen_unconfirmed(now);
|
let _ = update.graph_update.update_last_seen_unconfirmed(now);
|
||||||
|
|
||||||
wallet.apply_update(update)?;
|
wallet.apply_update(update)?;
|
||||||
wallet.commit()?;
|
db.write_changes(&wallet.take_staged())?;
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
|
@ -7,6 +7,7 @@ use std::{collections::BTreeSet, io::Write, str::FromStr};
|
|||||||
|
|
||||||
use bdk_esplora::{esplora_client, EsploraExt};
|
use bdk_esplora::{esplora_client, EsploraExt};
|
||||||
use bdk_file_store::Store;
|
use bdk_file_store::Store;
|
||||||
|
use bdk_wallet::chain::persist::PersistBackend;
|
||||||
use bdk_wallet::{
|
use bdk_wallet::{
|
||||||
bitcoin::{Address, Amount, Network},
|
bitcoin::{Address, Amount, Network},
|
||||||
KeychainKind, SignOptions, Wallet,
|
KeychainKind, SignOptions, Wallet,
|
||||||
@ -14,19 +15,21 @@ use bdk_wallet::{
|
|||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
let db_path = std::env::temp_dir().join("bdk-esplora-example");
|
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)?;
|
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 external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||||
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||||
|
let changeset = db.load_changes()?;
|
||||||
|
|
||||||
let mut wallet = Wallet::new_or_load(
|
let mut wallet = Wallet::new_or_load(
|
||||||
external_descriptor,
|
external_descriptor,
|
||||||
internal_descriptor,
|
internal_descriptor,
|
||||||
db,
|
changeset,
|
||||||
Network::Testnet,
|
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);
|
println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
@ -52,7 +55,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
let _ = update.graph_update.update_last_seen_unconfirmed(now);
|
let _ = update.graph_update.update_last_seen_unconfirmed(now);
|
||||||
|
|
||||||
wallet.apply_update(update)?;
|
wallet.apply_update(update)?;
|
||||||
wallet.commit()?;
|
db.write_changes(&wallet.take_staged())?;
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
|
@ -3,6 +3,7 @@ use bdk_bitcoind_rpc::{
|
|||||||
Emitter,
|
Emitter,
|
||||||
};
|
};
|
||||||
use bdk_file_store::Store;
|
use bdk_file_store::Store;
|
||||||
|
use bdk_wallet::chain::persist::PersistBackend;
|
||||||
use bdk_wallet::{
|
use bdk_wallet::{
|
||||||
bitcoin::{Block, Network, Transaction},
|
bitcoin::{Block, Network, Transaction},
|
||||||
wallet::Wallet,
|
wallet::Wallet,
|
||||||
@ -86,13 +87,16 @@ fn main() -> anyhow::Result<()> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let start_load_wallet = Instant::now();
|
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(
|
let mut wallet = Wallet::new_or_load(
|
||||||
&args.descriptor,
|
&args.descriptor,
|
||||||
&args.change_descriptor,
|
&args.change_descriptor,
|
||||||
Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
|
changeset,
|
||||||
DB_MAGIC.as_bytes(),
|
|
||||||
args.db_path,
|
|
||||||
)?,
|
|
||||||
args.network,
|
args.network,
|
||||||
)?;
|
)?;
|
||||||
println!(
|
println!(
|
||||||
@ -143,7 +147,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let connected_to = block_emission.connected_to();
|
let connected_to = block_emission.connected_to();
|
||||||
let start_apply_block = Instant::now();
|
let start_apply_block = Instant::now();
|
||||||
wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?;
|
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();
|
let elapsed = start_apply_block.elapsed().as_secs_f32();
|
||||||
println!(
|
println!(
|
||||||
"Applied block {} at height {} in {}s",
|
"Applied block {} at height {} in {}s",
|
||||||
@ -153,7 +157,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
Emission::Mempool(mempool_emission) => {
|
Emission::Mempool(mempool_emission) => {
|
||||||
let start_apply_mempool = Instant::now();
|
let start_apply_mempool = Instant::now();
|
||||||
wallet.apply_unconfirmed_txs(mempool_emission.iter().map(|(tx, time)| (tx, *time)));
|
wallet.apply_unconfirmed_txs(mempool_emission.iter().map(|(tx, time)| (tx, *time)));
|
||||||
wallet.commit()?;
|
db.write_changes(&wallet.take_staged())?;
|
||||||
println!(
|
println!(
|
||||||
"Applied unconfirmed transactions in {}s",
|
"Applied unconfirmed transactions in {}s",
|
||||||
start_apply_mempool.elapsed().as_secs_f32()
|
start_apply_mempool.elapsed().as_secs_f32()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user