feat!: rm persist
submodule
Remove `PersistBackend`, `PersistBackendAsync`, `StageExt` and `StageExtAsync`. Remove `async` feature flag and dependency. Update examples and wallet.
This commit is contained in:
parent
782eb56bd4
commit
1eca568be5
@ -19,7 +19,6 @@ serde_crate = { package = "serde", version = "1", optional = true, features = ["
|
|||||||
# Use hashbrown as a feature flag to have HashSet and HashMap from it.
|
# Use hashbrown as a feature flag to have HashSet and HashMap from it.
|
||||||
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
|
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
|
||||||
miniscript = { version = "12.0.0", optional = true, default-features = false }
|
miniscript = { version = "12.0.0", optional = true, default-features = false }
|
||||||
async-trait = { version = "0.1.80", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
@ -29,4 +28,3 @@ proptest = "1.2.0"
|
|||||||
default = ["std", "miniscript"]
|
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"]
|
||||||
async = ["async-trait"]
|
|
||||||
|
95
crates/chain/src/changeset.rs
Normal file
95
crates/chain/src/changeset.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
//! This module is home to the [`PersistBackend`] trait which defines the behavior of a data store
|
||||||
|
//! required to persist changes made to BDK data structures.
|
||||||
|
//!
|
||||||
|
//! The [`CombinedChangeSet`] type encapsulates a combination of [`crate`] structures that are
|
||||||
|
//! typically persisted together.
|
||||||
|
|
||||||
|
/// A changeset containing [`crate`] structures typically persisted together.
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
derive(crate::serde::Deserialize, crate::serde::Serialize),
|
||||||
|
serde(
|
||||||
|
crate = "crate::serde",
|
||||||
|
bound(
|
||||||
|
deserialize = "A: Ord + crate::serde::Deserialize<'de>, K: Ord + crate::serde::Deserialize<'de>",
|
||||||
|
serialize = "A: Ord + crate::serde::Serialize, K: Ord + crate::serde::Serialize",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub struct CombinedChangeSet<K, A> {
|
||||||
|
/// Changes to the [`LocalChain`](crate::local_chain::LocalChain).
|
||||||
|
pub chain: crate::local_chain::ChangeSet,
|
||||||
|
/// Changes to [`IndexedTxGraph`](crate::indexed_tx_graph::IndexedTxGraph).
|
||||||
|
pub indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
|
||||||
|
/// Stores the network type of the transaction data.
|
||||||
|
pub network: Option<bitcoin::Network>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl<K, A> core::default::Default for CombinedChangeSet<K, A> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
chain: core::default::Default::default(),
|
||||||
|
indexed_tx_graph: core::default::Default::default(),
|
||||||
|
network: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl<K: Ord, A: crate::Anchor> crate::Append for CombinedChangeSet<K, A> {
|
||||||
|
fn append(&mut self, other: Self) {
|
||||||
|
crate::Append::append(&mut self.chain, other.chain);
|
||||||
|
crate::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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
|
||||||
|
fn from(chain: crate::local_chain::ChangeSet) -> Self {
|
||||||
|
Self {
|
||||||
|
chain,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>>
|
||||||
|
for CombinedChangeSet<K, A>
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
indexed_tx_graph,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl<K, A> From<crate::keychain::ChangeSet<K>> for CombinedChangeSet<K, A> {
|
||||||
|
fn from(indexer: crate::keychain::ChangeSet<K>) -> Self {
|
||||||
|
Self {
|
||||||
|
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet {
|
||||||
|
indexer,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,7 +50,8 @@ 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::*;
|
||||||
pub mod persist;
|
mod changeset;
|
||||||
|
pub use changeset::*;
|
||||||
pub mod spk_client;
|
pub mod spk_client;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
@ -1,279 +0,0 @@
|
|||||||
//! This module is home to the [`PersistBackend`] trait which defines the behavior of a data store
|
|
||||||
//! required to persist changes made to BDK data structures.
|
|
||||||
//!
|
|
||||||
//! The [`CombinedChangeSet`] type encapsulates a combination of [`crate`] structures that are
|
|
||||||
//! typically persisted together.
|
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use core::convert::Infallible;
|
|
||||||
use core::fmt::{Debug, Display};
|
|
||||||
|
|
||||||
use crate::Append;
|
|
||||||
|
|
||||||
/// A changeset containing [`crate`] structures typically persisted together.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "serde",
|
|
||||||
derive(crate::serde::Deserialize, crate::serde::Serialize),
|
|
||||||
serde(
|
|
||||||
crate = "crate::serde",
|
|
||||||
bound(
|
|
||||||
deserialize = "A: Ord + crate::serde::Deserialize<'de>, K: Ord + crate::serde::Deserialize<'de>",
|
|
||||||
serialize = "A: Ord + crate::serde::Serialize, K: Ord + crate::serde::Serialize",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub struct CombinedChangeSet<K, A> {
|
|
||||||
/// Changes to the [`LocalChain`](crate::local_chain::LocalChain).
|
|
||||||
pub chain: crate::local_chain::ChangeSet,
|
|
||||||
/// Changes to [`IndexedTxGraph`](crate::indexed_tx_graph::IndexedTxGraph).
|
|
||||||
pub indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
|
|
||||||
/// Stores the network type of the transaction data.
|
|
||||||
pub network: Option<bitcoin::Network>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl<K, A> core::default::Default for CombinedChangeSet<K, A> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
chain: core::default::Default::default(),
|
|
||||||
indexed_tx_graph: core::default::Default::default(),
|
|
||||||
network: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl<K: Ord, A: crate::Anchor> crate::Append for CombinedChangeSet<K, A> {
|
|
||||||
fn append(&mut self, other: Self) {
|
|
||||||
crate::Append::append(&mut self.chain, other.chain);
|
|
||||||
crate::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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
|
|
||||||
fn from(chain: crate::local_chain::ChangeSet) -> Self {
|
|
||||||
Self {
|
|
||||||
chain,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>>
|
|
||||||
for CombinedChangeSet<K, A>
|
|
||||||
{
|
|
||||||
fn from(
|
|
||||||
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
indexed_tx_graph,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl<K, A> From<crate::keychain::ChangeSet<K>> for CombinedChangeSet<K, A> {
|
|
||||||
fn from(indexer: crate::keychain::ChangeSet<K>) -> Self {
|
|
||||||
Self {
|
|
||||||
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet {
|
|
||||||
indexer,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A persistence backend for writing and loading changesets.
|
|
||||||
///
|
|
||||||
/// `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> {
|
|
||||||
/// The error the backend returns when it fails to write.
|
|
||||||
type WriteError: Debug + Display;
|
|
||||||
|
|
||||||
/// The error the backend returns when it fails to load changesets `C`.
|
|
||||||
type LoadError: Debug + Display;
|
|
||||||
|
|
||||||
/// 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_changes
|
|
||||||
fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>;
|
|
||||||
|
|
||||||
/// Return the aggregate changeset `C` from persistence.
|
|
||||||
fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> PersistBackend<C> for () {
|
|
||||||
type WriteError = Infallible;
|
|
||||||
type LoadError = Infallible;
|
|
||||||
|
|
||||||
fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
/// An async persistence backend for writing and loading changesets.
|
|
||||||
///
|
|
||||||
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
|
|
||||||
/// that are to be persisted, or retrieved from persistence.
|
|
||||||
#[async_trait]
|
|
||||||
pub trait PersistBackendAsync<C> {
|
|
||||||
/// The error the backend returns when it fails to write.
|
|
||||||
type WriteError: Debug + Display;
|
|
||||||
|
|
||||||
/// The error the backend returns when it fails to load changesets `C`.
|
|
||||||
type LoadError: Debug + Display;
|
|
||||||
|
|
||||||
/// 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_changes
|
|
||||||
async fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>;
|
|
||||||
|
|
||||||
/// Return the aggregate changeset `C` from persistence.
|
|
||||||
async fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
#[async_trait]
|
|
||||||
impl<C> PersistBackendAsync<C> for () {
|
|
||||||
type WriteError = Infallible;
|
|
||||||
type LoadError = Infallible;
|
|
||||||
|
|
||||||
async fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extends a changeset so that it acts as a convenient staging area for any [`PersistBackend`].
|
|
||||||
///
|
|
||||||
/// Not all changes to the in-memory representation needs to be written to disk right away.
|
|
||||||
/// [`Append::append`] can be used to *stage* changes first and then [`StageExt::commit_to`] can be
|
|
||||||
/// used to write changes to disk.
|
|
||||||
pub trait StageExt: Append + Default + Sized {
|
|
||||||
/// Commit the staged changes to the persistence `backend`.
|
|
||||||
///
|
|
||||||
/// Changes that are committed (if any) are returned.
|
|
||||||
///
|
|
||||||
/// # Error
|
|
||||||
///
|
|
||||||
/// Returns a backend-defined error if this fails.
|
|
||||||
fn commit_to<B>(&mut self, backend: &mut B) -> Result<Option<Self>, B::WriteError>
|
|
||||||
where
|
|
||||||
B: PersistBackend<Self>,
|
|
||||||
{
|
|
||||||
// do not do anything if changeset is empty
|
|
||||||
if self.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
backend.write_changes(&*self)?;
|
|
||||||
// only clear if changes are written successfully to backend
|
|
||||||
Ok(Some(core::mem::take(self)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stages a new `changeset` and commits it (alongside any other previously staged changes) to
|
|
||||||
/// the persistence `backend`.
|
|
||||||
///
|
|
||||||
/// Convenience method for calling [`Append::append`] and then [`StageExt::commit_to`].
|
|
||||||
fn append_and_commit_to<B>(
|
|
||||||
&mut self,
|
|
||||||
changeset: Self,
|
|
||||||
backend: &mut B,
|
|
||||||
) -> Result<Option<Self>, B::WriteError>
|
|
||||||
where
|
|
||||||
B: PersistBackend<Self>,
|
|
||||||
{
|
|
||||||
Append::append(self, changeset);
|
|
||||||
self.commit_to(backend)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Append + Default> StageExt for C {}
|
|
||||||
|
|
||||||
/// Extends a changeset so that it acts as a convenient staging area for any
|
|
||||||
/// [`PersistBackendAsync`].
|
|
||||||
///
|
|
||||||
/// Not all changes to the in-memory representation needs to be written to disk right away.
|
|
||||||
/// [`Append::append`] can be used to *stage* changes first and then [`StageExtAsync::commit_to`]
|
|
||||||
/// can be used to write changes to disk.
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
#[async_trait]
|
|
||||||
pub trait StageExtAsync: Append + Default + Sized + Send + Sync {
|
|
||||||
/// Commit the staged changes to the persistence `backend`.
|
|
||||||
///
|
|
||||||
/// Changes that are committed (if any) are returned.
|
|
||||||
///
|
|
||||||
/// # Error
|
|
||||||
///
|
|
||||||
/// Returns a backend-defined error if this fails.
|
|
||||||
async fn commit_to<B>(&mut self, backend: &mut B) -> Result<Option<Self>, B::WriteError>
|
|
||||||
where
|
|
||||||
B: PersistBackendAsync<Self> + Send + Sync,
|
|
||||||
{
|
|
||||||
// do not do anything if changeset is empty
|
|
||||||
if self.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
backend.write_changes(&*self).await?;
|
|
||||||
// only clear if changes are written successfully to backend
|
|
||||||
Ok(Some(core::mem::take(self)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stages a new `changeset` and commits it (alongside any other previously staged changes) to
|
|
||||||
/// the persistence `backend`.
|
|
||||||
///
|
|
||||||
/// Convenience method for calling [`Append::append`] and then [`StageExtAsync::commit_to`].
|
|
||||||
async fn append_and_commit_to<B>(
|
|
||||||
&mut self,
|
|
||||||
changeset: Self,
|
|
||||||
backend: &mut B,
|
|
||||||
) -> Result<Option<Self>, B::WriteError>
|
|
||||||
where
|
|
||||||
B: PersistBackendAsync<Self> + Send + Sync,
|
|
||||||
{
|
|
||||||
Append::append(self, changeset);
|
|
||||||
self.commit_to(backend).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
#[async_trait]
|
|
||||||
impl<C: Append + Default + Send + Sync> StageExtAsync for C {}
|
|
@ -1,6 +1,6 @@
|
|||||||
# BDK File Store
|
# BDK File Store
|
||||||
|
|
||||||
This is a simple append-only flat file implementation of [`PersistBackend`](bdk_chain::persist::PersistBackend).
|
This is a simple append-only flat file database for persisting [`bdk_chain`] changesets.
|
||||||
|
|
||||||
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,5 +1,4 @@
|
|||||||
use crate::{bincode_options, EntryIter, FileError, IterError};
|
use crate::{bincode_options, EntryIter, FileError, IterError};
|
||||||
use bdk_chain::persist::PersistBackend;
|
|
||||||
use bdk_chain::Append;
|
use bdk_chain::Append;
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
use std::{
|
use std::{
|
||||||
@ -21,27 +20,6 @@ where
|
|||||||
marker: PhantomData<C>,
|
marker: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> PersistBackend<C> for Store<C>
|
|
||||||
where
|
|
||||||
C: Append
|
|
||||||
+ Debug
|
|
||||||
+ serde::Serialize
|
|
||||||
+ serde::de::DeserializeOwned
|
|
||||||
+ core::marker::Send
|
|
||||||
+ core::marker::Sync,
|
|
||||||
{
|
|
||||||
type WriteError = io::Error;
|
|
||||||
type LoadError = AggregateChangesetsError<C>;
|
|
||||||
|
|
||||||
fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
|
|
||||||
self.append_changeset(changeset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_changes(&mut self) -> Result<Option<C>, Self::LoadError> {
|
|
||||||
self.aggregate_changesets()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> Store<C>
|
impl<C> Store<C>
|
||||||
where
|
where
|
||||||
C: Append
|
C: Append
|
||||||
@ -448,7 +426,7 @@ mod test {
|
|||||||
acc
|
acc
|
||||||
});
|
});
|
||||||
// We write after a short read.
|
// We write after a short read.
|
||||||
db.write_changes(&last_changeset)
|
db.append_changeset(&last_changeset)
|
||||||
.expect("last write must succeed");
|
.expect("last write must succeed");
|
||||||
Append::append(&mut exp_aggregation, last_changeset.clone());
|
Append::append(&mut exp_aggregation, last_changeset.clone());
|
||||||
drop(db);
|
drop(db);
|
||||||
|
@ -12,7 +12,7 @@ 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::CombinedChangeSet;
|
||||||
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,
|
||||||
};
|
};
|
||||||
@ -57,26 +57,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, A> PersistBackend<CombinedChangeSet<K, A>> for Store<K, A>
|
|
||||||
where
|
|
||||||
K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
|
|
||||||
A: Anchor + for<'de> Deserialize<'de> + Serialize + Send,
|
|
||||||
{
|
|
||||||
type WriteError = Error;
|
|
||||||
type LoadError = Error;
|
|
||||||
|
|
||||||
fn write_changes(
|
|
||||||
&mut self,
|
|
||||||
changeset: &CombinedChangeSet<K, A>,
|
|
||||||
) -> Result<(), Self::WriteError> {
|
|
||||||
self.write(changeset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_changes(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Self::LoadError> {
|
|
||||||
self.read()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Network table related functions.
|
/// Network table related functions.
|
||||||
impl<K, A> Store<K, A> {
|
impl<K, A> Store<K, A> {
|
||||||
/// Insert [`Network`] for which all other tables data is valid.
|
/// Insert [`Network`] for which all other tables data is valid.
|
||||||
@ -482,13 +462,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Functions to read and write all [`ChangeSet`] data.
|
/// Functions to read and write all [`CombinedChangeSet`] data.
|
||||||
impl<K, A> Store<K, A>
|
impl<K, A> 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,
|
||||||
{
|
{
|
||||||
fn write(&mut self, changeset: &CombinedChangeSet<K, A>) -> Result<(), Error> {
|
/// Write the given `changeset` atomically.
|
||||||
|
pub fn write(&mut self, changeset: &CombinedChangeSet<K, A>) -> Result<(), Error> {
|
||||||
// no need to write anything if changeset is empty
|
// no need to write anything if changeset is empty
|
||||||
if changeset.is_empty() {
|
if changeset.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -513,7 +494,8 @@ where
|
|||||||
db_transaction.commit().map_err(Error::Sqlite)
|
db_transaction.commit().map_err(Error::Sqlite)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Error> {
|
/// Read the entire database and return the aggregate [`CombinedChangeSet`].
|
||||||
|
pub fn read(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Error> {
|
||||||
let db_transaction = self.db_transaction()?;
|
let db_transaction = self.db_transaction()?;
|
||||||
|
|
||||||
let network = Self::select_network(&db_transaction)?;
|
let network = Self::select_network(&db_transaction)?;
|
||||||
@ -563,7 +545,7 @@ 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::CombinedChangeSet;
|
||||||
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,
|
||||||
@ -591,10 +573,10 @@ mod test {
|
|||||||
.expect("create new memory db store");
|
.expect("create new memory db store");
|
||||||
|
|
||||||
test_changesets.iter().for_each(|changeset| {
|
test_changesets.iter().for_each(|changeset| {
|
||||||
store.write_changes(changeset).expect("write changeset");
|
store.write(changeset).expect("write changeset");
|
||||||
});
|
});
|
||||||
|
|
||||||
let agg_changeset = store.load_changes().expect("aggregated changeset");
|
let agg_changeset = store.read().expect("aggregated changeset");
|
||||||
|
|
||||||
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
||||||
}
|
}
|
||||||
@ -612,10 +594,10 @@ mod test {
|
|||||||
.expect("create new memory db store");
|
.expect("create new memory db store");
|
||||||
|
|
||||||
test_changesets.iter().for_each(|changeset| {
|
test_changesets.iter().for_each(|changeset| {
|
||||||
store.write_changes(changeset).expect("write changeset");
|
store.write(changeset).expect("write changeset");
|
||||||
});
|
});
|
||||||
|
|
||||||
let agg_changeset = store.load_changes().expect("aggregated changeset");
|
let agg_changeset = store.read().expect("aggregated changeset");
|
||||||
|
|
||||||
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
||||||
}
|
}
|
||||||
@ -629,10 +611,10 @@ mod test {
|
|||||||
let mut store = Store::<Keychain, BlockId>::new(conn).expect("create new memory db store");
|
let mut store = Store::<Keychain, BlockId>::new(conn).expect("create new memory db store");
|
||||||
|
|
||||||
test_changesets.iter().for_each(|changeset| {
|
test_changesets.iter().for_each(|changeset| {
|
||||||
store.write_changes(changeset).expect("write changeset");
|
store.write(changeset).expect("write changeset");
|
||||||
});
|
});
|
||||||
|
|
||||||
let agg_changeset = store.load_changes().expect("aggregated changeset");
|
let agg_changeset = store.read().expect("aggregated changeset");
|
||||||
|
|
||||||
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
assert_eq!(agg_changeset, Some(agg_test_changesets));
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ js-sys = "0.3"
|
|||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
|
std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
|
||||||
async = ["bdk_chain/async"]
|
|
||||||
compiler = ["miniscript/compiler"]
|
compiler = ["miniscript/compiler"]
|
||||||
all-keys = ["keys-bip39"]
|
all-keys = ["keys-bip39"]
|
||||||
keys-bip39 = ["bip39"]
|
keys-bip39 = ["bip39"]
|
||||||
|
@ -26,7 +26,6 @@ use bdk_chain::{
|
|||||||
local_chain::{
|
local_chain::{
|
||||||
self, ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain,
|
self, ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain,
|
||||||
},
|
},
|
||||||
persist::{PersistBackend, StageExt},
|
|
||||||
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
|
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
|
||||||
tx_graph::{CanonicalTx, TxGraph},
|
tx_graph::{CanonicalTx, TxGraph},
|
||||||
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
|
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
|
||||||
@ -85,12 +84,12 @@ 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
|
/// The user is responsible for loading and writing wallet changes which are represented as
|
||||||
/// [`PersistBackend`]. See individual functions and example for instructions on when [`Wallet`]
|
/// [`ChangeSet`]s (see [`take_staged`]). Also see individual functions and example for instructions
|
||||||
/// state needs to be persisted.
|
/// on when [`Wallet`] state needs to be persisted.
|
||||||
///
|
///
|
||||||
/// [`PersistBackend`]: bdk_chain::persist::PersistBackend
|
|
||||||
/// [`signer`]: crate::signer
|
/// [`signer`]: crate::signer
|
||||||
|
/// [`take_staged`]: Wallet::take_staged
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
signers: Arc<SignersContainer>,
|
signers: Arc<SignersContainer>,
|
||||||
@ -141,8 +140,7 @@ 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 =
|
pub type ChangeSet = bdk_chain::CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>;
|
||||||
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`
|
||||||
@ -408,14 +406,13 @@ impl Wallet {
|
|||||||
/// # use bdk_wallet::descriptor::Descriptor;
|
/// # use bdk_wallet::descriptor::Descriptor;
|
||||||
/// # use bitcoin::key::Secp256k1;
|
/// # use bitcoin::key::Secp256k1;
|
||||||
/// # use bdk_wallet::KeychainKind;
|
/// # use bdk_wallet::KeychainKind;
|
||||||
/// # 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 mut 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();
|
||||||
@ -423,7 +420,7 @@ 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 changeset = db.read()?.expect("there must be an existing changeset");
|
||||||
/// let mut wallet = Wallet::load_from_changeset(changeset)?;
|
/// let mut wallet = Wallet::load_from_changeset(changeset)?;
|
||||||
///
|
///
|
||||||
/// external_signer_container.signers().into_iter()
|
/// external_signer_container.signers().into_iter()
|
||||||
@ -482,13 +479,12 @@ impl Wallet {
|
|||||||
/// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
|
/// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use bdk_chain::persist::PersistBackend;
|
|
||||||
/// # use bdk_wallet::Wallet;
|
/// # use bdk_wallet::Wallet;
|
||||||
/// # use bdk_sqlite::{Store, rusqlite::Connection};
|
/// use bdk_sqlite::{Store, rusqlite::Connection};
|
||||||
/// # use bitcoin::Network::Testnet;
|
/// # use bitcoin::Network::Testnet;
|
||||||
/// # let conn = Connection::open_in_memory().expect("must open connection");
|
/// let conn = Connection::open_in_memory().expect("must open connection");
|
||||||
/// let mut db = Store::new(conn).expect("must create db");
|
/// let mut db = Store::new(conn).expect("must create db");
|
||||||
/// let changeset = db.load_changes()?;
|
/// let changeset = db.read()?;
|
||||||
///
|
///
|
||||||
/// 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/*)";
|
||||||
@ -666,16 +662,17 @@ impl Wallet {
|
|||||||
/// calls to this method before closing the wallet. For example:
|
/// calls to this method before closing the wallet. For example:
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use bdk_chain::persist::PersistBackend;
|
|
||||||
/// # use bdk_wallet::wallet::{Wallet, ChangeSet};
|
/// # use bdk_wallet::wallet::{Wallet, ChangeSet};
|
||||||
/// # use bdk_wallet::KeychainKind;
|
/// # use bdk_wallet::KeychainKind;
|
||||||
/// # use bdk_sqlite::{Store, rusqlite::Connection};
|
/// use bdk_sqlite::{rusqlite::Connection, Store};
|
||||||
/// # let conn = Connection::open_in_memory().expect("must open connection");
|
/// let conn = Connection::open_in_memory().expect("must open connection");
|
||||||
/// # let mut db = Store::new(conn).expect("must create store");
|
/// let mut db = Store::new(conn).expect("must create store");
|
||||||
/// # let changeset = ChangeSet::default();
|
/// # let changeset = ChangeSet::default();
|
||||||
/// # let mut wallet = Wallet::load_from_changeset(changeset).expect("load wallet");
|
/// # let mut wallet = Wallet::load_from_changeset(changeset).expect("load wallet");
|
||||||
/// let next_address = wallet.reveal_next_address(KeychainKind::External);
|
/// let next_address = wallet.reveal_next_address(KeychainKind::External);
|
||||||
/// wallet.commit_to(&mut db)?;
|
/// if let Some(changeset) = wallet.take_staged() {
|
||||||
|
/// db.write(&changeset)?;
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// // Now it's safe to show the user their next address!
|
/// // Now it's safe to show the user their next address!
|
||||||
/// println!("Next address: {}", next_address.address);
|
/// println!("Next address: {}", next_address.address);
|
||||||
@ -2283,44 +2280,22 @@ impl Wallet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commits all currently [`staged`](Wallet::staged) changes to the `persist_backend`.
|
/// Get a reference of the staged [`ChangeSet`] that are yet to be committed (if any).
|
||||||
///
|
pub fn staged(&self) -> Option<&ChangeSet> {
|
||||||
/// This returns whether anything was persisted.
|
if self.stage.is_empty() {
|
||||||
///
|
None
|
||||||
/// # Error
|
} else {
|
||||||
///
|
Some(&self.stage)
|
||||||
/// Returns a backend-defined error if this fails.
|
}
|
||||||
pub fn commit_to<B>(&mut self, persist_backend: &mut B) -> Result<bool, B::WriteError>
|
|
||||||
where
|
|
||||||
B: PersistBackend<ChangeSet>,
|
|
||||||
{
|
|
||||||
let committed = StageExt::commit_to(&mut self.stage, persist_backend)?;
|
|
||||||
Ok(committed.is_some())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commits all currently [`staged`](Wallet::staged) changes to the async `persist_backend`.
|
/// Take the staged [`ChangeSet`] to be persisted now (if any).
|
||||||
///
|
pub fn take_staged(&mut self) -> Option<ChangeSet> {
|
||||||
/// This returns whether anything was persisted.
|
if self.stage.is_empty() {
|
||||||
///
|
None
|
||||||
/// # Error
|
} else {
|
||||||
///
|
Some(core::mem::take(&mut self.stage))
|
||||||
/// Returns a backend-defined error if this fails.
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
pub async fn commit_to_async<B>(
|
|
||||||
&mut self,
|
|
||||||
persist_backend: &mut B,
|
|
||||||
) -> Result<bool, B::WriteError>
|
|
||||||
where
|
|
||||||
B: bdk_chain::persist::PersistBackendAsync<ChangeSet> + Send + Sync,
|
|
||||||
{
|
|
||||||
let committed =
|
|
||||||
bdk_chain::persist::StageExtAsync::commit_to(&mut self.stage, persist_backend).await?;
|
|
||||||
Ok(committed.is_some())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the staged [`ChangeSet`] that is yet to be committed.
|
|
||||||
pub fn staged(&self) -> &ChangeSet {
|
|
||||||
&self.stage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the inner [`TxGraph`].
|
/// Get a reference to the inner [`TxGraph`].
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use anyhow::anyhow;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use bdk_chain::collections::BTreeMap;
|
use bdk_chain::collections::BTreeMap;
|
||||||
use bdk_chain::COINBASE_MATURITY;
|
use bdk_chain::COINBASE_MATURITY;
|
||||||
use bdk_chain::{persist::PersistBackend, BlockId, ConfirmationTime};
|
use bdk_chain::{BlockId, ConfirmationTime};
|
||||||
use bdk_sqlite::rusqlite::Connection;
|
use bdk_sqlite::rusqlite::Connection;
|
||||||
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
|
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
|
||||||
use bdk_wallet::psbt::PsbtUtils;
|
use bdk_wallet::psbt::PsbtUtils;
|
||||||
@ -13,7 +12,7 @@ use bdk_wallet::signer::{SignOptions, SignerError};
|
|||||||
use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
|
use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
|
||||||
use bdk_wallet::wallet::error::CreateTxError;
|
use bdk_wallet::wallet::error::CreateTxError;
|
||||||
use bdk_wallet::wallet::tx_builder::AddForeignUtxoError;
|
use bdk_wallet::wallet::tx_builder::AddForeignUtxoError;
|
||||||
use bdk_wallet::wallet::{AddressInfo, Balance, NewError, Wallet};
|
use bdk_wallet::wallet::{AddressInfo, Balance, ChangeSet, NewError, Wallet};
|
||||||
use bdk_wallet::KeychainKind;
|
use bdk_wallet::KeychainKind;
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
use bitcoin::key::Secp256k1;
|
use bitcoin::key::Secp256k1;
|
||||||
@ -72,11 +71,18 @@ const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48];
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_recovers_wallet() -> anyhow::Result<()> {
|
fn load_recovers_wallet() -> anyhow::Result<()> {
|
||||||
fn run<B, FN, FR>(filename: &str, create_new: FN, recover: FR) -> anyhow::Result<()>
|
fn run<Db, New, Recover, Read, Write>(
|
||||||
|
filename: &str,
|
||||||
|
create_new: New,
|
||||||
|
recover: Recover,
|
||||||
|
read: Read,
|
||||||
|
write: Write,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
B: PersistBackend<bdk_wallet::wallet::ChangeSet> + Send + Sync + 'static,
|
New: Fn(&Path) -> anyhow::Result<Db>,
|
||||||
FN: Fn(&Path) -> anyhow::Result<B>,
|
Recover: Fn(&Path) -> anyhow::Result<Db>,
|
||||||
FR: Fn(&Path) -> anyhow::Result<B>,
|
Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
|
||||||
|
Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
|
||||||
{
|
{
|
||||||
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(filename);
|
let file_path = temp_dir.path().join(filename);
|
||||||
@ -91,9 +97,9 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// persist new wallet changes
|
// persist new wallet changes
|
||||||
let mut db = create_new(&file_path).expect("must create db");
|
let mut db = create_new(&file_path).expect("must create db");
|
||||||
wallet
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
.commit_to(&mut db)
|
write(&mut db, &changeset)?;
|
||||||
.map_err(|e| anyhow!("write changes error: {}", e))?;
|
}
|
||||||
wallet.spk_index().clone()
|
wallet.spk_index().clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,10 +107,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
|
|||||||
{
|
{
|
||||||
// load persisted wallet changes
|
// load persisted wallet changes
|
||||||
let db = &mut recover(&file_path).expect("must recover db");
|
let db = &mut recover(&file_path).expect("must recover db");
|
||||||
let changeset = db
|
let changeset = read(db).expect("must recover wallet").expect("changeset");
|
||||||
.load_changes()
|
|
||||||
.expect("must recover wallet")
|
|
||||||
.expect("changeset");
|
|
||||||
|
|
||||||
let wallet = Wallet::load_from_changeset(changeset).expect("must recover wallet");
|
let wallet = Wallet::load_from_changeset(changeset).expect("must recover wallet");
|
||||||
assert_eq!(wallet.network(), Network::Testnet);
|
assert_eq!(wallet.network(), Network::Testnet);
|
||||||
@ -132,11 +135,15 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
|
|||||||
"store.db",
|
"store.db",
|
||||||
|path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
|
|path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
|
||||||
|path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
|
|path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
|
||||||
|
|db| Ok(bdk_file_store::Store::aggregate_changesets(db)?),
|
||||||
|
|db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?),
|
||||||
)?;
|
)?;
|
||||||
run(
|
run(
|
||||||
"store.sqlite",
|
"store.sqlite",
|
||||||
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
||||||
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
||||||
|
|db| Ok(bdk_sqlite::Store::read(db)?),
|
||||||
|
|db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -144,10 +151,16 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_or_load() -> anyhow::Result<()> {
|
fn new_or_load() -> anyhow::Result<()> {
|
||||||
fn run<B, F>(filename: &str, new_or_load: F) -> anyhow::Result<()>
|
fn run<Db, NewOrRecover, Read, Write>(
|
||||||
|
filename: &str,
|
||||||
|
new_or_load: NewOrRecover,
|
||||||
|
read: Read,
|
||||||
|
write: Write,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
B: PersistBackend<bdk_wallet::wallet::ChangeSet> + Send + Sync + 'static,
|
NewOrRecover: Fn(&Path) -> anyhow::Result<Db>,
|
||||||
F: Fn(&Path) -> anyhow::Result<B>,
|
Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
|
||||||
|
Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
|
||||||
{
|
{
|
||||||
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(filename);
|
let file_path = temp_dir.path().join(filename);
|
||||||
@ -158,18 +171,16 @@ fn new_or_load() -> anyhow::Result<()> {
|
|||||||
let wallet = &mut Wallet::new_or_load(desc, change_desc, None, Network::Testnet)
|
let wallet = &mut Wallet::new_or_load(desc, change_desc, None, Network::Testnet)
|
||||||
.expect("must init wallet");
|
.expect("must init wallet");
|
||||||
let mut db = new_or_load(&file_path).expect("must create db");
|
let mut db = new_or_load(&file_path).expect("must create db");
|
||||||
wallet
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
.commit_to(&mut db)
|
write(&mut db, &changeset)?;
|
||||||
.map_err(|e| anyhow!("write changes error: {}", e))?;
|
}
|
||||||
wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
|
wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// wrong network
|
// wrong network
|
||||||
{
|
{
|
||||||
let mut db = new_or_load(&file_path).expect("must create db");
|
let mut db = new_or_load(&file_path).expect("must create db");
|
||||||
let changeset = db
|
let changeset = read(&mut db)?;
|
||||||
.load_changes()
|
|
||||||
.map_err(|e| anyhow!("load changes error: {}", e))?;
|
|
||||||
let err = Wallet::new_or_load(desc, change_desc, changeset, Network::Bitcoin)
|
let err = Wallet::new_or_load(desc, change_desc, changeset, Network::Bitcoin)
|
||||||
.expect_err("wrong network");
|
.expect_err("wrong network");
|
||||||
assert!(
|
assert!(
|
||||||
@ -192,9 +203,7 @@ fn new_or_load() -> anyhow::Result<()> {
|
|||||||
bitcoin::blockdata::constants::genesis_block(Network::Testnet).block_hash();
|
bitcoin::blockdata::constants::genesis_block(Network::Testnet).block_hash();
|
||||||
|
|
||||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||||
let changeset = db
|
let changeset = read(db)?;
|
||||||
.load_changes()
|
|
||||||
.map_err(|e| anyhow!("load changes error: {}", e))?;
|
|
||||||
let err = Wallet::new_or_load_with_genesis_hash(
|
let err = Wallet::new_or_load_with_genesis_hash(
|
||||||
desc,
|
desc,
|
||||||
change_desc,
|
change_desc,
|
||||||
@ -223,9 +232,7 @@ fn new_or_load() -> anyhow::Result<()> {
|
|||||||
.0;
|
.0;
|
||||||
|
|
||||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||||
let changeset = db
|
let changeset = read(db)?;
|
||||||
.load_changes()
|
|
||||||
.map_err(|e| anyhow!("load changes error: {}", e))?;
|
|
||||||
let err =
|
let err =
|
||||||
Wallet::new_or_load(exp_descriptor, exp_change_desc, changeset, Network::Testnet)
|
Wallet::new_or_load(exp_descriptor, exp_change_desc, changeset, Network::Testnet)
|
||||||
.expect_err("wrong external descriptor");
|
.expect_err("wrong external descriptor");
|
||||||
@ -249,9 +256,7 @@ fn new_or_load() -> anyhow::Result<()> {
|
|||||||
.0;
|
.0;
|
||||||
|
|
||||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||||
let changeset = db
|
let changeset = read(db)?;
|
||||||
.load_changes()
|
|
||||||
.map_err(|e| anyhow!("load changes error: {}", e))?;
|
|
||||||
let err = Wallet::new_or_load(desc, exp_descriptor, changeset, Network::Testnet)
|
let err = Wallet::new_or_load(desc, exp_descriptor, changeset, Network::Testnet)
|
||||||
.expect_err("wrong internal descriptor");
|
.expect_err("wrong internal descriptor");
|
||||||
assert!(
|
assert!(
|
||||||
@ -268,9 +273,7 @@ fn new_or_load() -> anyhow::Result<()> {
|
|||||||
// all parameters match
|
// all parameters match
|
||||||
{
|
{
|
||||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||||
let changeset = db
|
let changeset = read(db)?;
|
||||||
.load_changes()
|
|
||||||
.map_err(|e| anyhow!("load changes error: {}", e))?;
|
|
||||||
let wallet = Wallet::new_or_load(desc, change_desc, changeset, Network::Testnet)
|
let wallet = Wallet::new_or_load(desc, change_desc, changeset, Network::Testnet)
|
||||||
.expect("must recover wallet");
|
.expect("must recover wallet");
|
||||||
assert_eq!(wallet.network(), Network::Testnet);
|
assert_eq!(wallet.network(), Network::Testnet);
|
||||||
@ -282,12 +285,18 @@ fn new_or_load() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
run("store.db", |path| {
|
run(
|
||||||
Ok(bdk_file_store::Store::open_or_create_new(DB_MAGIC, path)?)
|
"store.db",
|
||||||
})?;
|
|path| Ok(bdk_file_store::Store::open_or_create_new(DB_MAGIC, path)?),
|
||||||
run("store.sqlite", |path| {
|
|db| Ok(bdk_file_store::Store::aggregate_changesets(db)?),
|
||||||
Ok(bdk_sqlite::Store::new(Connection::open(path)?)?)
|
|db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?),
|
||||||
})?;
|
)?;
|
||||||
|
run(
|
||||||
|
"store.sqlite",
|
||||||
|
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
||||||
|
|db| Ok(bdk_sqlite::Store::read(db)?),
|
||||||
|
|db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ use bdk_bitcoind_rpc::{
|
|||||||
bitcoincore_rpc::{Auth, Client, RpcApi},
|
bitcoincore_rpc::{Auth, Client, RpcApi},
|
||||||
Emitter,
|
Emitter,
|
||||||
};
|
};
|
||||||
use bdk_chain::persist::{PersistBackend, StageExt};
|
|
||||||
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,
|
||||||
@ -138,7 +137,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.write_changes(&(chain_changeset, Default::default()))?;
|
db.append_changeset(&(chain_changeset, Default::default()))?;
|
||||||
chain
|
chain
|
||||||
} else {
|
} else {
|
||||||
LocalChain::from_changeset(init_chain_changeset)?
|
LocalChain::from_changeset(init_chain_changeset)?
|
||||||
@ -197,7 +196,9 @@ fn main() -> anyhow::Result<()> {
|
|||||||
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
||||||
let db = &mut *db.lock().unwrap();
|
let db = &mut *db.lock().unwrap();
|
||||||
last_db_commit = Instant::now();
|
last_db_commit = Instant::now();
|
||||||
db_stage.commit_to(db)?;
|
if !db_stage.is_empty() {
|
||||||
|
db.append_changeset(&core::mem::take(&mut db_stage))?;
|
||||||
|
}
|
||||||
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(),
|
||||||
@ -233,10 +234,10 @@ fn main() -> anyhow::Result<()> {
|
|||||||
);
|
);
|
||||||
{
|
{
|
||||||
let db = &mut *db.lock().unwrap();
|
let db = &mut *db.lock().unwrap();
|
||||||
db_stage.append_and_commit_to(
|
db_stage.append((local_chain::ChangeSet::default(), graph_changeset));
|
||||||
(local_chain::ChangeSet::default(), graph_changeset),
|
if !db_stage.is_empty() {
|
||||||
db,
|
db.append_changeset(&core::mem::take(&mut db_stage))?;
|
||||||
)?;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RpcCommands::Live { rpc_args } => {
|
RpcCommands::Live { rpc_args } => {
|
||||||
@ -324,7 +325,9 @@ fn main() -> anyhow::Result<()> {
|
|||||||
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
if last_db_commit.elapsed() >= DB_COMMIT_DELAY {
|
||||||
let db = &mut *db.lock().unwrap();
|
let db = &mut *db.lock().unwrap();
|
||||||
last_db_commit = Instant::now();
|
last_db_commit = Instant::now();
|
||||||
db_stage.commit_to(db)?;
|
if !db_stage.is_empty() {
|
||||||
|
db.append_changeset(&core::mem::take(&mut db_stage))?;
|
||||||
|
}
|
||||||
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(),
|
||||||
|
@ -25,7 +25,6 @@ use bdk_chain::{
|
|||||||
pub use bdk_file_store;
|
pub use bdk_file_store;
|
||||||
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>>;
|
||||||
@ -482,7 +481,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.write_changes(&C::from((
|
db.append_changeset(&C::from((
|
||||||
local_chain::ChangeSet::default(),
|
local_chain::ChangeSet::default(),
|
||||||
indexed_tx_graph::ChangeSet::from(index_changeset),
|
indexed_tx_graph::ChangeSet::from(index_changeset),
|
||||||
)))?;
|
)))?;
|
||||||
@ -630,7 +629,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.write_changes(&C::from((
|
db.append_changeset(&C::from((
|
||||||
local_chain::ChangeSet::default(),
|
local_chain::ChangeSet::default(),
|
||||||
indexed_tx_graph::ChangeSet::from(index_changeset),
|
indexed_tx_graph::ChangeSet::from(index_changeset),
|
||||||
)))?;
|
)))?;
|
||||||
@ -655,7 +654,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().write_changes(&C::from((
|
db.lock().unwrap().append_changeset(&C::from((
|
||||||
local_chain::ChangeSet::default(),
|
local_chain::ChangeSet::default(),
|
||||||
keychain_changeset,
|
keychain_changeset,
|
||||||
)))?;
|
)))?;
|
||||||
@ -736,7 +735,7 @@ 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_changes()?.unwrap_or_default();
|
let init_changeset = db_backend.aggregate_changesets()?.unwrap_or_default();
|
||||||
|
|
||||||
Ok(Init {
|
Ok(Init {
|
||||||
args,
|
args,
|
||||||
|
@ -3,7 +3,6 @@ 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,
|
||||||
@ -352,6 +351,6 @@ fn main() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
db.write_changes(&db_changeset)?;
|
db.append_changeset(&db_changeset)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ 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},
|
||||||
@ -362,6 +361,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.write_changes(&(local_chain_changeset, indexed_tx_graph_changeset))?;
|
db.append_changeset(&(local_chain_changeset, indexed_tx_graph_changeset))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ 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};
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
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
|
let changeset = db
|
||||||
.load_changes()
|
.aggregate_changesets()
|
||||||
.map_err(|e| anyhow!("load changes error: {}", e))?;
|
.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,
|
||||||
@ -33,7 +32,9 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let address = wallet.next_unused_address(KeychainKind::External);
|
let address = wallet.next_unused_address(KeychainKind::External);
|
||||||
wallet.commit_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.append_changeset(&changeset)?;
|
||||||
|
}
|
||||||
println!("Generated Address: {}", address);
|
println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
@ -72,7 +73,9 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
println!();
|
println!();
|
||||||
|
|
||||||
wallet.apply_update(update)?;
|
wallet.apply_update(update)?;
|
||||||
wallet.commit_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.append_changeset(&changeset)?;
|
||||||
|
}
|
||||||
|
|
||||||
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,7 +7,6 @@ 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;
|
||||||
@ -20,7 +19,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
let mut 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 changeset = db.read()?;
|
||||||
|
|
||||||
let mut wallet = Wallet::new_or_load(
|
let mut wallet = Wallet::new_or_load(
|
||||||
external_descriptor,
|
external_descriptor,
|
||||||
@ -30,7 +29,9 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let address = wallet.next_unused_address(KeychainKind::External);
|
let address = wallet.next_unused_address(KeychainKind::External);
|
||||||
wallet.commit_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.write(&changeset)?;
|
||||||
|
}
|
||||||
println!("Generated Address: {}", address);
|
println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
@ -78,7 +79,9 @@ 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_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.write(&changeset)?;
|
||||||
|
}
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
|
@ -7,7 +7,6 @@ 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,
|
||||||
@ -19,7 +18,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
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 changeset = db.aggregate_changesets()?;
|
||||||
|
|
||||||
let mut wallet = Wallet::new_or_load(
|
let mut wallet = Wallet::new_or_load(
|
||||||
external_descriptor,
|
external_descriptor,
|
||||||
@ -29,7 +28,9 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let address = wallet.next_unused_address(KeychainKind::External);
|
let address = wallet.next_unused_address(KeychainKind::External);
|
||||||
wallet.commit_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.append_changeset(&changeset)?;
|
||||||
|
}
|
||||||
println!("Generated Address: {}", address);
|
println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
@ -55,7 +56,9 @@ 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_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.append_changeset(&changeset)?;
|
||||||
|
}
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let balance = wallet.balance();
|
let balance = wallet.balance();
|
||||||
|
@ -3,7 +3,6 @@ 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,
|
||||||
@ -91,7 +90,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
DB_MAGIC.as_bytes(),
|
DB_MAGIC.as_bytes(),
|
||||||
args.db_path,
|
args.db_path,
|
||||||
)?;
|
)?;
|
||||||
let changeset = db.load_changes()?;
|
let changeset = db.aggregate_changesets()?;
|
||||||
|
|
||||||
let mut wallet = Wallet::new_or_load(
|
let mut wallet = Wallet::new_or_load(
|
||||||
&args.descriptor,
|
&args.descriptor,
|
||||||
@ -147,7 +146,9 @@ 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_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.append_changeset(&changeset)?;
|
||||||
|
}
|
||||||
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",
|
||||||
@ -157,7 +158,9 @@ 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_to(&mut db)?;
|
if let Some(changeset) = wallet.take_staged() {
|
||||||
|
db.append_changeset(&changeset)?;
|
||||||
|
}
|
||||||
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