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:
志宇 2024-06-14 20:42:25 +08:00
parent 782eb56bd4
commit 1eca568be5
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
18 changed files with 239 additions and 469 deletions

View File

@ -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"]

View 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()
}
}
}

View File

@ -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)]

View File

@ -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 {}

View File

@ -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.

View 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);

View File

@ -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));
} }

View File

@ -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"]

View File

@ -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`].

View File

@ -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(())
} }

View File

@ -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(),

View File

@ -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,

View File

@ -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(())
} }

View File

@ -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(())
} }

View File

@ -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());

View File

@ -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();

View File

@ -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();

View File

@ -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()