Skip to content

Commit 174128a

Browse files
committed
feat!: add abitrary persistence
With the following approach we can accomplish two things at once: 1. Make it easy to add new Rust backends without sacrifing performance 2. Allow for arbitrary persistence accross the FFI To accomplish this we can differentiate between a native backend and a foreign backend. The foreign backend interacts with the FFI `ChangeSet`, whereas the native backend can just use the Rust changeset directly. Whenever a new backend is introduced in Rust, a new enum variant may simply be added to the `PersistenceType`. To build a Sqlite backend, or a foreign backend, the user will use the `Persister` structure. Abitrary persistence is allowed by implementing the `Persistence` trait. This was accomplished with no changes to #756. I hope 1. motivates this change thoroughly, as we expect BDK will add support for new backends in the future. I am also interested in the applications of 2., where a user might be able to do encrypted and cloud storage.
1 parent 2c359be commit 174128a

File tree

5 files changed

+113
-62
lines changed

5 files changed

+113
-62
lines changed

bdk-ffi/src/bdk.udl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ interface ParseAmountError {
197197

198198
[Error]
199199
interface PersistenceError {
200-
Write(string error_message);
200+
Reason(string error_message);
201201
};
202202

203203
[Error]

bdk-ffi/src/error.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ pub enum ParseAmountError {
564564

565565
#[derive(Debug, thiserror::Error)]
566566
pub enum PersistenceError {
567-
#[error("writing to persistence error: {error_message}")]
568-
Write { error_message: String },
567+
#[error("persistence error: {error_message}")]
568+
Reason { error_message: String },
569569
}
570570

571571
#[derive(Debug, thiserror::Error)]
@@ -745,12 +745,6 @@ pub enum SignerError {
745745
Psbt { error_message: String },
746746
}
747747

748-
#[derive(Debug, thiserror::Error, uniffi::Error)]
749-
pub enum SqliteError {
750-
#[error("sqlite error: {rusqlite_error}")]
751-
Sqlite { rusqlite_error: String },
752-
}
753-
754748
#[derive(Debug, thiserror::Error)]
755749
pub enum TransactionError {
756750
#[error("io error")]
@@ -1251,6 +1245,14 @@ impl From<BdkLoadWithPersistError<chain::rusqlite::Error>> for LoadWithPersistEr
12511245
}
12521246
}
12531247

1248+
impl From<BdkSqliteError> for PersistenceError {
1249+
fn from(error: BdkSqliteError) -> Self {
1250+
PersistenceError::Reason {
1251+
error_message: error.to_string(),
1252+
}
1253+
}
1254+
}
1255+
12541256
impl From<bdk_wallet::miniscript::Error> for MiniscriptError {
12551257
fn from(error: bdk_wallet::miniscript::Error) -> Self {
12561258
use bdk_wallet::miniscript::Error as BdkMiniscriptError;
@@ -1343,7 +1345,7 @@ impl From<BdkParseAmountError> for ParseAmountError {
13431345

13441346
impl From<std::io::Error> for PersistenceError {
13451347
fn from(error: std::io::Error) -> Self {
1346-
PersistenceError::Write {
1348+
PersistenceError::Reason {
13471349
error_message: error.to_string(),
13481350
}
13491351
}
@@ -1513,14 +1515,6 @@ pub enum HashParseError {
15131515
InvalidHash { len: u32 },
15141516
}
15151517

1516-
impl From<BdkSqliteError> for SqliteError {
1517-
fn from(error: BdkSqliteError) -> Self {
1518-
SqliteError::Sqlite {
1519-
rusqlite_error: error.to_string(),
1520-
}
1521-
}
1522-
}
1523-
15241518
impl From<bdk_kyoto::builder::SqlInitializationError> for CbfBuilderError {
15251519
fn from(value: bdk_kyoto::builder::SqlInitializationError) -> Self {
15261520
CbfBuilderError::DatabaseError {
@@ -1941,7 +1935,7 @@ mod test {
19411935
"writing to persistence error: unable to persist the new address",
19421936
),
19431937
(
1944-
PersistenceError::Write {
1938+
PersistenceError::Reason {
19451939
error_message: "failed to write to storage".to_string(),
19461940
},
19471941
"writing to persistence error: failed to write to storage",

bdk-ffi/src/store.rs

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,86 @@
1-
use crate::error::SqliteError;
1+
use crate::error::PersistenceError;
2+
use crate::types::ChangeSet;
23

3-
use bdk_wallet::rusqlite::Connection as BdkConnection;
4+
use bdk_wallet::{rusqlite::Connection as BdkConnection, WalletPersister};
45

5-
use std::sync::Mutex;
6-
use std::sync::MutexGuard;
6+
use std::ops::DerefMut;
7+
use std::sync::{Arc, Mutex};
78

8-
/// A connection to a SQLite database.
9+
/// Definition of a wallet persistence implementation.
10+
#[uniffi::export]
11+
pub trait Persistence: Send + Sync {
12+
/// Initialize the total aggregate `ChangeSet` for the underlying wallet.
13+
fn initialize(&self) -> Result<ChangeSet, PersistenceError>;
14+
15+
/// Perist a `ChangeSet` to the total aggregate changeset of the wallet.
16+
fn persist(&self, changeset: &ChangeSet) -> Result<(), PersistenceError>;
17+
}
18+
19+
pub(crate) enum PersistenceType {
20+
Foreign(Arc<dyn Persistence>),
21+
Sql(Mutex<BdkConnection>),
22+
}
23+
24+
/// Wallet backend implementations.
925
#[derive(uniffi::Object)]
10-
pub struct Connection(Mutex<BdkConnection>);
26+
pub struct Persister {
27+
pub(crate) inner: Mutex<PersistenceType>,
28+
}
1129

1230
#[uniffi::export]
13-
impl Connection {
14-
/// Open a new connection to a SQLite database. If a database does not exist at the path, one is
15-
/// created.
31+
impl Persister {
32+
/// Create a new Sqlite connection at the specified file path.
1633
#[uniffi::constructor]
17-
pub fn new(path: String) -> Result<Self, SqliteError> {
18-
let connection = BdkConnection::open(path)?;
19-
Ok(Self(Mutex::new(connection)))
34+
pub fn new_sqlite(path: String) -> Result<Self, PersistenceError> {
35+
let conn = BdkConnection::open(path)?;
36+
Ok(Self {
37+
inner: PersistenceType::Sql(conn.into()).into(),
38+
})
2039
}
2140

22-
/// Open a new connection to an in-memory SQLite database.
41+
/// Create a new connection in memory.
2342
#[uniffi::constructor]
24-
pub fn new_in_memory() -> Result<Self, SqliteError> {
25-
let connection = BdkConnection::open_in_memory()?;
26-
Ok(Self(Mutex::new(connection)))
43+
pub fn new_in_memory() -> Result<Self, PersistenceError> {
44+
let conn = BdkConnection::open_in_memory()?;
45+
Ok(Self {
46+
inner: PersistenceType::Sql(conn.into()).into(),
47+
})
48+
}
49+
50+
/// Use a native persistence layer.
51+
#[uniffi::constructor]
52+
pub fn custom(persistence: Arc<dyn Persistence>) -> Self {
53+
Self {
54+
inner: PersistenceType::Foreign(persistence).into(),
55+
}
2756
}
2857
}
2958

30-
impl Connection {
31-
pub(crate) fn get_store(&self) -> MutexGuard<BdkConnection> {
32-
self.0.lock().expect("must lock")
59+
impl WalletPersister for PersistenceType {
60+
type Error = PersistenceError;
61+
62+
fn initialize(persister: &mut Self) -> Result<bdk_wallet::ChangeSet, Self::Error> {
63+
match persister {
64+
PersistenceType::Sql(ref conn) => {
65+
let mut lock = conn.lock().unwrap();
66+
let deref = lock.deref_mut();
67+
Ok(BdkConnection::initialize(deref)?)
68+
}
69+
PersistenceType::Foreign(any) => any.initialize().map(|changeset| changeset.into()),
70+
}
71+
}
72+
73+
fn persist(persister: &mut Self, changeset: &bdk_wallet::ChangeSet) -> Result<(), Self::Error> {
74+
match persister {
75+
PersistenceType::Sql(ref conn) => {
76+
let mut lock = conn.lock().unwrap();
77+
let deref = lock.deref_mut();
78+
Ok(BdkConnection::persist(deref, changeset)?)
79+
}
80+
PersistenceType::Foreign(any) => {
81+
let ffi_changeset: ChangeSet = changeset.clone().into();
82+
any.persist(&ffi_changeset)
83+
}
84+
}
3385
}
3486
}

bdk-ffi/src/tx_builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ pub enum ChangeSpendPolicy {
549549
mod tests {
550550
use crate::bitcoin::{Amount, Script};
551551
use crate::{
552-
descriptor::Descriptor, esplora::EsploraClient, store::Connection,
552+
descriptor::Descriptor, esplora::EsploraClient, store::Persister,
553553
types::FullScanScriptInspector, wallet::Wallet,
554554
};
555555
use bdk_wallet::bitcoin::Network;
@@ -616,7 +616,7 @@ mod tests {
616616
Arc::new(Descriptor::new(external_descriptor, Network::Signet).unwrap()),
617617
Arc::new(Descriptor::new(internal_descriptor, Network::Signet).unwrap()),
618618
Network::Signet,
619-
Arc::new(Connection::new_in_memory().unwrap()),
619+
Arc::new(Persister::new_in_memory().unwrap()),
620620
)
621621
.unwrap();
622622
let client = EsploraClient::new("https://mutinynet.com/api/".to_string(), None);

bdk-ffi/src/wallet.rs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,20 @@ use crate::bitcoin::{Amount, FeeRate, OutPoint, Psbt, Script, Transaction, Txid}
22
use crate::descriptor::Descriptor;
33
use crate::error::{
44
CalculateFeeError, CannotConnectError, CreateWithPersistError, DescriptorError,
5-
LoadWithPersistError, SignerError, SqliteError, TxidParseError,
5+
LoadWithPersistError, PersistenceError, SignerError, TxidParseError,
66
};
7-
use crate::store::Connection;
7+
use crate::store::{PersistenceType, Persister};
88
use crate::types::{
99
AddressInfo, Balance, BlockId, CanonicalTx, FullScanRequestBuilder, KeychainAndIndex,
1010
LocalOutput, Policy, SentAndReceivedValues, SignOptions, SyncRequestBuilder, UnconfirmedTx,
1111
Update,
1212
};
1313

1414
use bdk_wallet::bitcoin::Network;
15-
use bdk_wallet::rusqlite::Connection as BdkConnection;
1615
use bdk_wallet::signer::SignOptions as BdkSignOptions;
1716
use bdk_wallet::{KeychainKind, PersistedWallet, Wallet as BdkWallet};
1817

19-
use std::borrow::BorrowMut;
18+
use std::ops::DerefMut;
2019
use std::sync::{Arc, Mutex, MutexGuard};
2120

2221
/// A Bitcoin wallet.
@@ -33,7 +32,7 @@ use std::sync::{Arc, Mutex, MutexGuard};
3332
/// script pubkeys. See KeychainTxOutIndex::insert_descriptor() for more details.
3433
#[derive(uniffi::Object)]
3534
pub struct Wallet {
36-
inner_mutex: Mutex<PersistedWallet<BdkConnection>>,
35+
inner_mutex: Mutex<PersistedWallet<PersistenceType>>,
3736
}
3837

3938
#[uniffi::export]
@@ -46,17 +45,20 @@ impl Wallet {
4645
descriptor: Arc<Descriptor>,
4746
change_descriptor: Arc<Descriptor>,
4847
network: Network,
49-
connection: Arc<Connection>,
48+
persistence: Arc<Persister>,
5049
) -> Result<Self, CreateWithPersistError> {
5150
let descriptor = descriptor.to_string_with_secret();
5251
let change_descriptor = change_descriptor.to_string_with_secret();
53-
let mut binding = connection.get_store();
54-
let db: &mut BdkConnection = binding.borrow_mut();
52+
let mut persist_lock = persistence.inner.lock().unwrap();
53+
let deref = persist_lock.deref_mut();
5554

56-
let wallet: PersistedWallet<BdkConnection> =
55+
let wallet: PersistedWallet<PersistenceType> =
5756
BdkWallet::create(descriptor, change_descriptor)
5857
.network(network)
59-
.create_wallet(db)?;
58+
.create_wallet(deref)
59+
.map_err(|e| CreateWithPersistError::Persist {
60+
error_message: e.to_string(),
61+
})?;
6062

6163
Ok(Wallet {
6264
inner_mutex: Mutex::new(wallet),
@@ -70,18 +72,21 @@ impl Wallet {
7072
pub fn load(
7173
descriptor: Arc<Descriptor>,
7274
change_descriptor: Arc<Descriptor>,
73-
connection: Arc<Connection>,
75+
persistence: Arc<Persister>,
7476
) -> Result<Wallet, LoadWithPersistError> {
7577
let descriptor = descriptor.to_string_with_secret();
7678
let change_descriptor = change_descriptor.to_string_with_secret();
77-
let mut binding = connection.get_store();
78-
let db: &mut BdkConnection = binding.borrow_mut();
79+
let mut persist_lock = persistence.inner.lock().unwrap();
80+
let deref = persist_lock.deref_mut();
7981

80-
let wallet: PersistedWallet<BdkConnection> = BdkWallet::load()
82+
let wallet: PersistedWallet<PersistenceType> = BdkWallet::load()
8183
.descriptor(KeychainKind::External, Some(descriptor))
8284
.descriptor(KeychainKind::Internal, Some(change_descriptor))
8385
.extract_keys()
84-
.load_wallet(db)?
86+
.load_wallet(deref)
87+
.map_err(|e| LoadWithPersistError::Persist {
88+
error_message: e.to_string(),
89+
})?
8590
.ok_or(LoadWithPersistError::CouldNotLoad)?;
8691

8792
Ok(Wallet {
@@ -405,13 +410,13 @@ impl Wallet {
405410
/// Returns whether any new changes were persisted.
406411
///
407412
/// If the persister errors, the staged changes will not be cleared.
408-
pub fn persist(&self, connection: Arc<Connection>) -> Result<bool, SqliteError> {
409-
let mut binding = connection.get_store();
410-
let db: &mut BdkConnection = binding.borrow_mut();
413+
pub fn persist(&self, persistence: Arc<Persister>) -> Result<bool, PersistenceError> {
414+
let mut persist_lock = persistence.inner.lock().unwrap();
415+
let deref = persist_lock.deref_mut();
411416
self.get_wallet()
412-
.persist(db)
413-
.map_err(|e| SqliteError::Sqlite {
414-
rusqlite_error: e.to_string(),
417+
.persist(deref)
418+
.map_err(|e| PersistenceError::Reason {
419+
error_message: e.to_string(),
415420
})
416421
}
417422

@@ -422,7 +427,7 @@ impl Wallet {
422427
}
423428

424429
impl Wallet {
425-
pub(crate) fn get_wallet(&self) -> MutexGuard<PersistedWallet<BdkConnection>> {
430+
pub(crate) fn get_wallet(&self) -> MutexGuard<PersistedWallet<PersistenceType>> {
426431
self.inner_mutex.lock().expect("wallet")
427432
}
428433
}

0 commit comments

Comments
 (0)