Skip to content

Commit 27d2cb7

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 30675df commit 27d2cb7

25 files changed

+176
-135
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: 29 additions & 42 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")]
@@ -1008,6 +1002,23 @@ impl From<PushBytesError> for CreateTxError {
10081002
}
10091003
}
10101004

1005+
impl From<BdkCreateWithPersistError<PersistenceError>> for CreateWithPersistError {
1006+
fn from(error: BdkCreateWithPersistError<PersistenceError>) -> Self {
1007+
match error {
1008+
BdkCreateWithPersistError::Persist(e) => CreateWithPersistError::Persist {
1009+
error_message: e.to_string(),
1010+
},
1011+
BdkCreateWithPersistError::Descriptor(e) => CreateWithPersistError::Descriptor {
1012+
error_message: e.to_string(),
1013+
},
1014+
// Objects cannot currently be used in enumerations
1015+
BdkCreateWithPersistError::DataAlreadyExists(_e) => {
1016+
CreateWithPersistError::DataAlreadyExists
1017+
}
1018+
}
1019+
}
1020+
}
1021+
10111022
impl From<BdkCreateWithPersistError<chain::rusqlite::Error>> for CreateWithPersistError {
10121023
fn from(error: BdkCreateWithPersistError<chain::rusqlite::Error>) -> Self {
10131024
match error {
@@ -1251,6 +1262,14 @@ impl From<BdkLoadWithPersistError<chain::rusqlite::Error>> for LoadWithPersistEr
12511262
}
12521263
}
12531264

1265+
impl From<BdkSqliteError> for PersistenceError {
1266+
fn from(error: BdkSqliteError) -> Self {
1267+
PersistenceError::Reason {
1268+
error_message: error.to_string(),
1269+
}
1270+
}
1271+
}
1272+
12541273
impl From<bdk_wallet::miniscript::Error> for MiniscriptError {
12551274
fn from(error: bdk_wallet::miniscript::Error) -> Self {
12561275
use bdk_wallet::miniscript::Error as BdkMiniscriptError;
@@ -1343,7 +1362,7 @@ impl From<BdkParseAmountError> for ParseAmountError {
13431362

13441363
impl From<std::io::Error> for PersistenceError {
13451364
fn from(error: std::io::Error) -> Self {
1346-
PersistenceError::Write {
1365+
PersistenceError::Reason {
13471366
error_message: error.to_string(),
13481367
}
13491368
}
@@ -1513,14 +1532,6 @@ pub enum HashParseError {
15131532
InvalidHash { len: u32 },
15141533
}
15151534

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-
15241535
impl From<bdk_kyoto::builder::SqlInitializationError> for CbfBuilderError {
15251536
fn from(value: bdk_kyoto::builder::SqlInitializationError) -> Self {
15261537
CbfBuilderError::DatabaseError {
@@ -1544,7 +1555,7 @@ mod test {
15441555
use crate::error::SignerError;
15451556
use crate::error::{
15461557
Bip32Error, Bip39Error, CannotConnectError, DescriptorError, DescriptorKeyError,
1547-
ElectrumError, EsploraError, ExtractTxError, PersistenceError, PsbtError, PsbtParseError,
1558+
ElectrumError, EsploraError, ExtractTxError, PsbtError, PsbtParseError,
15481559
RequestBuilderError, TransactionError, TxidParseError,
15491560
};
15501561

@@ -1929,30 +1940,6 @@ mod test {
19291940
}
19301941
}
19311942

1932-
#[test]
1933-
fn test_persistence_error() {
1934-
let cases = vec![
1935-
(
1936-
std::io::Error::new(
1937-
std::io::ErrorKind::Other,
1938-
"unable to persist the new address",
1939-
)
1940-
.into(),
1941-
"writing to persistence error: unable to persist the new address",
1942-
),
1943-
(
1944-
PersistenceError::Write {
1945-
error_message: "failed to write to storage".to_string(),
1946-
},
1947-
"writing to persistence error: failed to write to storage",
1948-
),
1949-
];
1950-
1951-
for (error, expected_message) in cases {
1952-
assert_eq!(error.to_string(), expected_message);
1953-
}
1954-
}
1955-
19561943
#[test]
19571944
fn test_error_psbt() {
19581945
let cases = vec![

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
25,
621621
)
622622
.unwrap();

bdk-ffi/src/wallet.rs

Lines changed: 23 additions & 21 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,17 @@ impl Wallet {
4645
descriptor: Arc<Descriptor>,
4746
change_descriptor: Arc<Descriptor>,
4847
network: Network,
49-
connection: Arc<Connection>,
48+
persistence: Arc<Persister>,
5049
lookahead: u32,
5150
) -> Result<Self, CreateWithPersistError> {
5251
let descriptor = descriptor.to_string_with_secret();
5352
let change_descriptor = change_descriptor.to_string_with_secret();
54-
let mut binding = connection.get_store();
55-
let db: &mut BdkConnection = binding.borrow_mut();
53+
let mut persist_lock = persistence.inner.lock().unwrap();
54+
let deref = persist_lock.deref_mut();
5655
let params = BdkWallet::create(descriptor, change_descriptor)
5756
.network(network)
5857
.lookahead(lookahead);
59-
let wallet: PersistedWallet<BdkConnection> = params.create_wallet(db)?;
58+
let wallet: PersistedWallet<PersistenceType> = params.create_wallet(deref)?;
6059
Ok(Wallet {
6160
inner_mutex: Mutex::new(wallet),
6261
})
@@ -69,18 +68,21 @@ impl Wallet {
6968
pub fn load(
7069
descriptor: Arc<Descriptor>,
7170
change_descriptor: Arc<Descriptor>,
72-
connection: Arc<Connection>,
71+
persistence: Arc<Persister>,
7372
) -> Result<Wallet, LoadWithPersistError> {
7473
let descriptor = descriptor.to_string_with_secret();
7574
let change_descriptor = change_descriptor.to_string_with_secret();
76-
let mut binding = connection.get_store();
77-
let db: &mut BdkConnection = binding.borrow_mut();
75+
let mut persist_lock = persistence.inner.lock().unwrap();
76+
let deref = persist_lock.deref_mut();
7877

79-
let wallet: PersistedWallet<BdkConnection> = BdkWallet::load()
78+
let wallet: PersistedWallet<PersistenceType> = BdkWallet::load()
8079
.descriptor(KeychainKind::External, Some(descriptor))
8180
.descriptor(KeychainKind::Internal, Some(change_descriptor))
8281
.extract_keys()
83-
.load_wallet(db)?
82+
.load_wallet(deref)
83+
.map_err(|e| LoadWithPersistError::Persist {
84+
error_message: e.to_string(),
85+
})?
8486
.ok_or(LoadWithPersistError::CouldNotLoad)?;
8587

8688
Ok(Wallet {
@@ -404,13 +406,13 @@ impl Wallet {
404406
/// Returns whether any new changes were persisted.
405407
///
406408
/// If the persister errors, the staged changes will not be cleared.
407-
pub fn persist(&self, connection: Arc<Connection>) -> Result<bool, SqliteError> {
408-
let mut binding = connection.get_store();
409-
let db: &mut BdkConnection = binding.borrow_mut();
409+
pub fn persist(&self, persistence: Arc<Persister>) -> Result<bool, PersistenceError> {
410+
let mut persist_lock = persistence.inner.lock().unwrap();
411+
let deref = persist_lock.deref_mut();
410412
self.get_wallet()
411-
.persist(db)
412-
.map_err(|e| SqliteError::Sqlite {
413-
rusqlite_error: e.to_string(),
413+
.persist(deref)
414+
.map_err(|e| PersistenceError::Reason {
415+
error_message: e.to_string(),
414416
})
415417
}
416418

@@ -421,7 +423,7 @@ impl Wallet {
421423
}
422424

423425
impl Wallet {
424-
pub(crate) fn get_wallet(&self) -> MutexGuard<PersistedWallet<BdkConnection>> {
426+
pub(crate) fn get_wallet(&self) -> MutexGuard<PersistedWallet<PersistenceType>> {
425427
self.inner_mutex.lock().expect("wallet")
426428
}
427429
}

bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveElectrumClientTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class LiveElectrumClientTest {
1818

1919
@Test
2020
fun testSyncedBalance() {
21-
var conn: Connection = Connection.newInMemory()
21+
var conn: Persister = Persister.newInMemory()
2222
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
2323
val electrumClient: ElectrumClient = ElectrumClient(SIGNET_ELECTRUM_URL)
2424
val fullScanRequest: FullScanRequest = wallet.startFullScan().build()

bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveKyotoTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class LiveKyotoTest {
2828

2929
@Test
3030
fun testKyoto() {
31-
val conn: Connection = Connection.newInMemory()
31+
val conn: Persister = Persister.newInMemory()
3232
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
3333
val peers = listOf(peer)
3434
runBlocking {

0 commit comments

Comments
 (0)