Skip to content

Commit 5a643d0

Browse files
authored
feat: abitrary persistence for wallet
1 parent e59b1e7 commit 5a643d0

30 files changed

+647
-159
lines changed

bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/LiveTxBuilderTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class LiveTxBuilderTest {
2727

2828
@Test
2929
fun testTxBuilder() {
30-
var conn: Connection = Connection.newInMemory()
30+
var conn: Persister = Persister.newInMemory()
3131
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
3232
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
3333
val fullScanRequest: FullScanRequest = wallet.startFullScan().build()
@@ -51,7 +51,7 @@ class LiveTxBuilderTest {
5151

5252
@Test
5353
fun complexTxBuilder() {
54-
var conn: Connection = Connection.newInMemory()
54+
var conn: Persister = Persister.newInMemory()
5555
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
5656
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
5757
val fullScanRequest: FullScanRequest = wallet.startFullScan().build()

bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/LiveWalletTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class LiveWalletTest {
2828

2929
@Test
3030
fun testSyncedBalance() {
31-
var conn: Connection = Connection.newInMemory()
31+
var conn: Persister = Persister.newInMemory()
3232
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
3333
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
3434
val fullScanRequest: FullScanRequest = wallet.startFullScan().build()
@@ -54,7 +54,7 @@ class LiveWalletTest {
5454

5555
@Test
5656
fun testBroadcastTransaction() {
57-
var conn: Connection = Connection.newInMemory()
57+
var conn: Persister = Persister.newInMemory()
5858
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
5959
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
6060
val fullScanRequest: FullScanRequest = wallet.startFullScan().build()

bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/OfflineWalletTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class OfflineWalletTest {
3636

3737
@Test
3838
fun testNewAddress() {
39-
val conn = Connection.newInMemory()
39+
val conn = Persister.newInMemory()
4040
val wallet: Wallet = Wallet(
4141
descriptor,
4242
changeDescriptor,
@@ -58,7 +58,7 @@ class OfflineWalletTest {
5858

5959
@Test
6060
fun testBalance() {
61-
var conn: Connection = Connection.newInMemory()
61+
var conn: Persister = Persister.newInMemory()
6262
val wallet: Wallet = Wallet(
6363
descriptor,
6464
changeDescriptor,

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 & 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")]
@@ -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 {
@@ -1544,7 +1538,7 @@ mod test {
15441538
use crate::error::SignerError;
15451539
use crate::error::{
15461540
Bip32Error, Bip39Error, CannotConnectError, DescriptorError, DescriptorKeyError,
1547-
ElectrumError, EsploraError, ExtractTxError, PersistenceError, PsbtError, PsbtParseError,
1541+
ElectrumError, EsploraError, ExtractTxError, PsbtError, PsbtParseError,
15481542
RequestBuilderError, TransactionError, TxidParseError,
15491543
};
15501544

@@ -1929,30 +1923,6 @@ mod test {
19291923
}
19301924
}
19311925

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-
19561926
#[test]
19571927
fn test_error_psbt() {
19581928
let cases = vec![

bdk-ffi/src/store.rs

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,88 @@
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(with_foreign)]
11+
pub trait Persistence: Send + Sync {
12+
/// Initialize the total aggregate `ChangeSet` for the underlying wallet.
13+
fn initialize(&self) -> Result<Arc<ChangeSet>, PersistenceError>;
14+
15+
/// Persist a `ChangeSet` to the total aggregate changeset of the wallet.
16+
fn persist(&self, changeset: Arc<ChangeSet>) -> Result<(), PersistenceError>;
17+
}
18+
19+
pub(crate) enum PersistenceType {
20+
Custom(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::Custom(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::Custom(any) => any
70+
.initialize()
71+
.map(|changeset| changeset.as_ref().clone().into()),
72+
}
73+
}
74+
75+
fn persist(persister: &mut Self, changeset: &bdk_wallet::ChangeSet) -> Result<(), Self::Error> {
76+
match persister {
77+
PersistenceType::Sql(ref conn) => {
78+
let mut lock = conn.lock().unwrap();
79+
let deref = lock.deref_mut();
80+
Ok(BdkConnection::persist(deref, changeset)?)
81+
}
82+
PersistenceType::Custom(any) => {
83+
let ffi_changeset: ChangeSet = changeset.clone().into();
84+
any.persist(Arc::new(ffi_changeset))
85+
}
86+
}
3387
}
3488
}

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

0 commit comments

Comments
 (0)