use core::{
fmt,
future::Future,
marker::PhantomData,
ops::{Deref, DerefMut},
pin::Pin,
};
use alloc::{boxed::Box, string::ToString};
use chain::Merge;
use crate::{
descriptor::{calc_checksum, DescriptorError},
ChangeSet, CreateParams, LoadParams, Wallet,
};
pub trait WalletPersister {
type Error;
fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error>;
fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error>;
}
type FutureResult<'a, T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
pub trait AsyncWalletPersister {
type Error;
fn initialize<'a>(persister: &'a mut Self) -> FutureResult<'a, ChangeSet, Self::Error>
where
Self: 'a;
fn persist<'a>(
persister: &'a mut Self,
changeset: &'a ChangeSet,
) -> FutureResult<'a, (), Self::Error>
where
Self: 'a;
}
#[derive(Debug)]
pub struct PersistedWallet<P> {
inner: Wallet,
_marker: PhantomData<fn(&mut P)>,
}
impl<P> Deref for PersistedWallet<P> {
type Target = Wallet;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<P> DerefMut for PersistedWallet<P> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<P: WalletPersister> PersistedWallet<P> {
pub fn create(
persister: &mut P,
params: CreateParams,
) -> Result<Self, CreateWithPersistError<P::Error>> {
let existing = P::initialize(persister).map_err(CreateWithPersistError::Persist)?;
if !existing.is_empty() {
return Err(CreateWithPersistError::DataAlreadyExists(existing));
}
let mut inner =
Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
if let Some(changeset) = inner.take_staged() {
P::persist(persister, &changeset).map_err(CreateWithPersistError::Persist)?;
}
Ok(Self {
inner,
_marker: PhantomData,
})
}
pub fn load(
persister: &mut P,
params: LoadParams,
) -> Result<Option<Self>, LoadWithPersistError<P::Error>> {
let changeset = P::initialize(persister).map_err(LoadWithPersistError::Persist)?;
Wallet::load_with_params(changeset, params)
.map(|opt| {
opt.map(|inner| PersistedWallet {
inner,
_marker: PhantomData,
})
})
.map_err(LoadWithPersistError::InvalidChangeSet)
}
pub fn persist(&mut self, persister: &mut P) -> Result<bool, P::Error> {
match self.inner.staged_mut() {
Some(stage) => {
P::persist(persister, &*stage)?;
let _ = stage.take();
Ok(true)
}
None => Ok(false),
}
}
}
impl<P: AsyncWalletPersister> PersistedWallet<P> {
pub async fn create_async(
persister: &mut P,
params: CreateParams,
) -> Result<Self, CreateWithPersistError<P::Error>> {
let existing = P::initialize(persister)
.await
.map_err(CreateWithPersistError::Persist)?;
if !existing.is_empty() {
return Err(CreateWithPersistError::DataAlreadyExists(existing));
}
let mut inner =
Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
if let Some(changeset) = inner.take_staged() {
P::persist(persister, &changeset)
.await
.map_err(CreateWithPersistError::Persist)?;
}
Ok(Self {
inner,
_marker: PhantomData,
})
}
pub async fn load_async(
persister: &mut P,
params: LoadParams,
) -> Result<Option<Self>, LoadWithPersistError<P::Error>> {
let changeset = P::initialize(persister)
.await
.map_err(LoadWithPersistError::Persist)?;
Wallet::load_with_params(changeset, params)
.map(|opt| {
opt.map(|inner| PersistedWallet {
inner,
_marker: PhantomData,
})
})
.map_err(LoadWithPersistError::InvalidChangeSet)
}
pub async fn persist_async(&mut self, persister: &mut P) -> Result<bool, P::Error> {
match self.inner.staged_mut() {
Some(stage) => {
P::persist(persister, &*stage).await?;
let _ = stage.take();
Ok(true)
}
None => Ok(false),
}
}
}
#[cfg(feature = "rusqlite")]
impl WalletPersister for bdk_chain::rusqlite::Transaction<'_> {
type Error = bdk_chain::rusqlite::Error;
fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
ChangeSet::init_sqlite_tables(&*persister)?;
ChangeSet::from_sqlite(persister)
}
fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
changeset.persist_to_sqlite(persister)
}
}
#[cfg(feature = "rusqlite")]
impl WalletPersister for bdk_chain::rusqlite::Connection {
type Error = bdk_chain::rusqlite::Error;
fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
let db_tx = persister.transaction()?;
ChangeSet::init_sqlite_tables(&db_tx)?;
let changeset = ChangeSet::from_sqlite(&db_tx)?;
db_tx.commit()?;
Ok(changeset)
}
fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
let db_tx = persister.transaction()?;
changeset.persist_to_sqlite(&db_tx)?;
db_tx.commit()
}
}
#[cfg(feature = "file_store")]
#[derive(Debug)]
pub enum FileStoreError {
Load(bdk_file_store::StoreErrorWithDump<ChangeSet>),
Write(std::io::Error),
}
#[cfg(feature = "file_store")]
impl core::fmt::Display for FileStoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use core::fmt::Display;
match self {
FileStoreError::Load(e) => Display::fmt(e, f),
FileStoreError::Write(e) => Display::fmt(e, f),
}
}
}
#[cfg(feature = "file_store")]
impl std::error::Error for FileStoreError {}
#[cfg(feature = "file_store")]
impl WalletPersister for bdk_file_store::Store<ChangeSet> {
type Error = FileStoreError;
fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
persister
.dump()
.map(Option::unwrap_or_default)
.map_err(FileStoreError::Load)
}
fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
persister.append(changeset).map_err(FileStoreError::Write)
}
}
#[derive(Debug, PartialEq)]
pub enum LoadWithPersistError<E> {
Persist(E),
InvalidChangeSet(crate::LoadError),
}
impl<E: fmt::Display> fmt::Display for LoadWithPersistError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Persist(err) => fmt::Display::fmt(err, f),
Self::InvalidChangeSet(err) => fmt::Display::fmt(&err, f),
}
}
}
#[cfg(feature = "std")]
impl<E: fmt::Debug + fmt::Display> std::error::Error for LoadWithPersistError<E> {}
#[derive(Debug)]
pub enum CreateWithPersistError<E> {
Persist(E),
DataAlreadyExists(ChangeSet),
Descriptor(DescriptorError),
}
impl<E: fmt::Display> fmt::Display for CreateWithPersistError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Persist(err) => write!(f, "{err}"),
Self::DataAlreadyExists(changeset) => {
write!(
f,
"Cannot create wallet in a persister which already contains data: "
)?;
changeset_info(f, changeset)
}
Self::Descriptor(err) => {
write!(f, "{err}")
}
}
}
}
#[cfg(feature = "std")]
impl<E: fmt::Debug + fmt::Display> std::error::Error for CreateWithPersistError<E> {}
fn changeset_info(f: &mut fmt::Formatter<'_>, changeset: &ChangeSet) -> fmt::Result {
let network = changeset
.network
.as_ref()
.map_or("None".to_string(), |n| n.to_string());
let descriptor_checksum = changeset
.descriptor
.as_ref()
.and_then(|d| calc_checksum(&d.to_string()).ok())
.unwrap_or_else(|| "None".to_string());
let change_descriptor_checksum = changeset
.change_descriptor
.as_ref()
.and_then(|d| calc_checksum(&d.to_string()).ok())
.unwrap_or_else(|| "None".to_string());
let tx_count = changeset.tx_graph.txs.len();
let anchor_count = changeset.tx_graph.anchors.len();
let block_count = if let Some(&count) = changeset.local_chain.blocks.keys().last() {
count
} else {
0
};
writeln!(f, " Network: {network}")?;
writeln!(f, " Descriptor Checksum: {descriptor_checksum}")?;
writeln!(
f,
" Change Descriptor Checksum: {change_descriptor_checksum}"
)?;
writeln!(f, " Transaction Count: {tx_count}")?;
writeln!(f, " Anchor Count: {anchor_count}")?;
writeln!(f, " Block Count: {block_count}")?;
Ok(())
}