Skip to content

Abitrary persistence for wallet #771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class LiveTxBuilderTest {

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

@Test
fun complexTxBuilder() {
var conn: Connection = Connection.newInMemory()
var conn: Persister = Persister.newInMemory()
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class LiveWalletTest {

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

@Test
fun testBroadcastTransaction() {
var conn: Connection = Connection.newInMemory()
var conn: Persister = Persister.newInMemory()
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET, conn)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class OfflineWalletTest {

@Test
fun testNewAddress() {
val conn = Connection.newInMemory()
val conn = Persister.newInMemory()
val wallet: Wallet = Wallet(
descriptor,
changeDescriptor,
Expand All @@ -58,7 +58,7 @@ class OfflineWalletTest {

@Test
fun testBalance() {
var conn: Connection = Connection.newInMemory()
var conn: Persister = Persister.newInMemory()
val wallet: Wallet = Wallet(
descriptor,
changeDescriptor,
Expand Down
2 changes: 1 addition & 1 deletion bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ interface ParseAmountError {

[Error]
interface PersistenceError {
Write(string error_message);
Reason(string error_message);
};

[Error]
Expand Down
54 changes: 12 additions & 42 deletions bdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,8 @@ pub enum ParseAmountError {

#[derive(Debug, thiserror::Error)]
pub enum PersistenceError {
#[error("writing to persistence error: {error_message}")]
Write { error_message: String },
#[error("persistence error: {error_message}")]
Reason { error_message: String },
}

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -745,12 +745,6 @@ pub enum SignerError {
Psbt { error_message: String },
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum SqliteError {
#[error("sqlite error: {rusqlite_error}")]
Sqlite { rusqlite_error: String },
}

#[derive(Debug, thiserror::Error)]
pub enum TransactionError {
#[error("io error")]
Expand Down Expand Up @@ -1251,6 +1245,14 @@ impl From<BdkLoadWithPersistError<chain::rusqlite::Error>> for LoadWithPersistEr
}
}

impl From<BdkSqliteError> for PersistenceError {
fn from(error: BdkSqliteError) -> Self {
PersistenceError::Reason {
error_message: error.to_string(),
}
}
}

impl From<bdk_wallet::miniscript::Error> for MiniscriptError {
fn from(error: bdk_wallet::miniscript::Error) -> Self {
use bdk_wallet::miniscript::Error as BdkMiniscriptError;
Expand Down Expand Up @@ -1343,7 +1345,7 @@ impl From<BdkParseAmountError> for ParseAmountError {

impl From<std::io::Error> for PersistenceError {
fn from(error: std::io::Error) -> Self {
PersistenceError::Write {
PersistenceError::Reason {
error_message: error.to_string(),
}
}
Expand Down Expand Up @@ -1513,14 +1515,6 @@ pub enum HashParseError {
InvalidHash { len: u32 },
}

impl From<BdkSqliteError> for SqliteError {
fn from(error: BdkSqliteError) -> Self {
SqliteError::Sqlite {
rusqlite_error: error.to_string(),
}
}
}

impl From<bdk_kyoto::builder::SqlInitializationError> for CbfBuilderError {
fn from(value: bdk_kyoto::builder::SqlInitializationError) -> Self {
CbfBuilderError::DatabaseError {
Expand All @@ -1544,7 +1538,7 @@ mod test {
use crate::error::SignerError;
use crate::error::{
Bip32Error, Bip39Error, CannotConnectError, DescriptorError, DescriptorKeyError,
ElectrumError, EsploraError, ExtractTxError, PersistenceError, PsbtError, PsbtParseError,
ElectrumError, EsploraError, ExtractTxError, PsbtError, PsbtParseError,
RequestBuilderError, TransactionError, TxidParseError,
};

Expand Down Expand Up @@ -1929,30 +1923,6 @@ mod test {
}
}

#[test]
fn test_persistence_error() {
let cases = vec![
(
std::io::Error::new(
std::io::ErrorKind::Other,
"unable to persist the new address",
)
.into(),
"writing to persistence error: unable to persist the new address",
),
(
PersistenceError::Write {
error_message: "failed to write to storage".to_string(),
},
"writing to persistence error: failed to write to storage",
),
];

for (error, expected_message) in cases {
assert_eq!(error.to_string(), expected_message);
}
}

#[test]
fn test_error_psbt() {
let cases = vec![
Expand Down
92 changes: 73 additions & 19 deletions bdk-ffi/src/store.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,88 @@
use crate::error::SqliteError;
use crate::error::PersistenceError;
use crate::types::ChangeSet;

use bdk_wallet::rusqlite::Connection as BdkConnection;
use bdk_wallet::{rusqlite::Connection as BdkConnection, WalletPersister};

use std::sync::Mutex;
use std::sync::MutexGuard;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex};

/// A connection to a SQLite database.
/// Definition of a wallet persistence implementation.
#[uniffi::export(with_foreign)]
pub trait Persistence: Send + Sync {
/// Initialize the total aggregate `ChangeSet` for the underlying wallet.
fn initialize(&self) -> Result<Arc<ChangeSet>, PersistenceError>;

/// Persist a `ChangeSet` to the total aggregate changeset of the wallet.
fn persist(&self, changeset: Arc<ChangeSet>) -> Result<(), PersistenceError>;
}

pub(crate) enum PersistenceType {
Custom(Arc<dyn Persistence>),
Sql(Mutex<BdkConnection>),
}

/// Wallet backend implementations.
#[derive(uniffi::Object)]
pub struct Connection(Mutex<BdkConnection>);
pub struct Persister {
pub(crate) inner: Mutex<PersistenceType>,
}

#[uniffi::export]
impl Connection {
/// Open a new connection to a SQLite database. If a database does not exist at the path, one is
/// created.
impl Persister {
/// Create a new Sqlite connection at the specified file path.
#[uniffi::constructor]
pub fn new(path: String) -> Result<Self, SqliteError> {
let connection = BdkConnection::open(path)?;
Ok(Self(Mutex::new(connection)))
pub fn new_sqlite(path: String) -> Result<Self, PersistenceError> {
let conn = BdkConnection::open(path)?;
Ok(Self {
inner: PersistenceType::Sql(conn.into()).into(),
})
}

/// Open a new connection to an in-memory SQLite database.
/// Create a new connection in memory.
#[uniffi::constructor]
pub fn new_in_memory() -> Result<Self, SqliteError> {
let connection = BdkConnection::open_in_memory()?;
Ok(Self(Mutex::new(connection)))
pub fn new_in_memory() -> Result<Self, PersistenceError> {
let conn = BdkConnection::open_in_memory()?;
Ok(Self {
inner: PersistenceType::Sql(conn.into()).into(),
})
}

/// Use a native persistence layer.
#[uniffi::constructor]
pub fn custom(persistence: Arc<dyn Persistence>) -> Self {
Self {
inner: PersistenceType::Custom(persistence).into(),
}
}
}

impl Connection {
pub(crate) fn get_store(&self) -> MutexGuard<BdkConnection> {
self.0.lock().expect("must lock")
impl WalletPersister for PersistenceType {
type Error = PersistenceError;

fn initialize(persister: &mut Self) -> Result<bdk_wallet::ChangeSet, Self::Error> {
match persister {
PersistenceType::Sql(ref conn) => {
let mut lock = conn.lock().unwrap();
let deref = lock.deref_mut();
Ok(BdkConnection::initialize(deref)?)
}
PersistenceType::Custom(any) => any
.initialize()
.map(|changeset| changeset.as_ref().clone().into()),
}
}

fn persist(persister: &mut Self, changeset: &bdk_wallet::ChangeSet) -> Result<(), Self::Error> {
match persister {
PersistenceType::Sql(ref conn) => {
let mut lock = conn.lock().unwrap();
let deref = lock.deref_mut();
Ok(BdkConnection::persist(deref, changeset)?)
}
PersistenceType::Custom(any) => {
let ffi_changeset: ChangeSet = changeset.clone().into();
any.persist(Arc::new(ffi_changeset))
}
}
}
}
4 changes: 2 additions & 2 deletions bdk-ffi/src/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ pub enum ChangeSpendPolicy {
mod tests {
use crate::bitcoin::{Amount, Script};
use crate::{
descriptor::Descriptor, esplora::EsploraClient, store::Connection,
descriptor::Descriptor, esplora::EsploraClient, store::Persister,
types::FullScanScriptInspector, wallet::Wallet,
};
use bdk_wallet::bitcoin::Network;
Expand Down Expand Up @@ -616,7 +616,7 @@ mod tests {
Arc::new(Descriptor::new(external_descriptor, Network::Signet).unwrap()),
Arc::new(Descriptor::new(internal_descriptor, Network::Signet).unwrap()),
Network::Signet,
Arc::new(Connection::new_in_memory().unwrap()),
Arc::new(Persister::new_in_memory().unwrap()),
25,
)
.unwrap();
Expand Down
Loading
Loading