Skip to content
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

[DRAFT] added sqlx, removed rusqlite #1552

Conversation

matthiasdebernardini
Copy link

Description

async postgres support for persistance

Notes to the reviewers

Here I am implementing async postgres via the SQLx crate. Currently there is only synchronous support via the FileStore and rustqlite implementations.

A big issue with this PR is that Rusqlite uses libsqlite3, and so does SQLx but with different versions. Cargo does not allow this, even though it should work, there is currently a bug that the SQLx maintianers showed me that explains this behavior. Nevertheless, including both sqlx and rusqlite in the same workspace will not work - bdk having a strict msrv requirement also does not help (once the fix lands in cargo, it will take time for the msrv of cargo to catch up).

My issues are with the PersistAsyncWith trait, I was unable to implement it. Some guidance would be appreciated.

@tnull
Copy link
Contributor

tnull commented Aug 14, 2024

Given that SQLx is very explicit about not supporting any kind of MSRV, I don't think switching to it would be a real option?

I think the operations for ChangeSet persistence are really some really trivial INSERTs/SELECTs, I wonder if we could just write a thin wrapper around libsqlite3-sys ourselves rather than pulling in a completely separate project without MSRV and a larger dependency tree? FWIW, we've been looking towards doing the same for LDK/LDK Node (which currently also depends on rusqlite) for some time.

// // migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
// Ok(())
// let db = db_tx.acquire().await.unwrap();
migrate!("./tx_graph_migrations").run(db_tx).await
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot to include these files in the PR.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should no longer be a problem

@evanlinjin
Copy link
Member

evanlinjin commented Aug 14, 2024

@matthiasdebernardini thank you for this PR. I think I figured out the problem. The trait definition was not strict enough with lifetime bounds. Please see here: https://github.com/matthiasdebernardini/bdk/pull/2

I'm wondering if #1547 will be in the beta. If so, maybe it's best I do the fix there. Edit: The fix is included in 8498a5f.

@evanlinjin
Copy link
Member

evanlinjin commented Aug 14, 2024

Looking at @tnull's comment, @matthiasdebernardini can you please elaborate on what you specifically need. Is it postgres support, async support, or do you specifically need postgres + async?

It'll be easier to assist if we know what the goals are.

@evanlinjin evanlinjin marked this pull request as draft August 14, 2024 10:54
@matthiasdebernardini
Copy link
Author

@evanlinjin we want to store our users wallets and load them quickly so the wallet feels snappy, that is our high level goal. We picked postgres because we like it and trust it. We are very paranoid about losing our customers wallet descriptors. Initially, I was storing the flat files in their own column, reading/writing it from the disk and uploading/downloading it from postgres (I had it working in S3 at one point as well).

The reason I want to store it remotely is that we want to keep our bdk api webserver stateless, so the sqlite/flat file method wont be a good fit for us.

As far async support is concerned, I am already running all our bdk code within an axum webserver, so using it is advantageous for scenarios where we need to fetch a lot of wallets all at once. We could go with a sequential (non-async/normal) method of persisting but async would be our preference.

In short, we don't need either, but async postgres is our #1 preference by a long shot. I agree with @tnull our needs are very basic. I picked SQLx because I prefer SQL over an ORM and I was having a lot of success using it to finish our app. I do think if you want to offer async remote persistence for your users out-of-the-box now, then a different crate may be better.

@evanlinjin
Copy link
Member

@matthiasdebernardini if we do the sqlx persistence in a separate crate, would we still run into the bug you mentioned in the PR description?

@matthiasdebernardini
Copy link
Author

It should work as long as you don't enable the rusqlite feature in that project, yes.

@matthiasdebernardini
Copy link
Author

I think reworking the lifetimes might help, I am still unable to make it compile.

error: lifetime may not live long enough
  --> crates/wallet/src/wallet/persisted.rs:36:13
   |
11 | impl<'c> chain::PersistAsyncWith<sqlx::Transaction<'c, sqlx::Postgres>> for Wallet {
   |      -- lifetime `'c` defined here
...
20 |         db: &mut sqlx::Transaction<'c, sqlx::Postgres>,
   |             - let's call the lifetime of this reference `'1`
...
36 |             Box::pin(create(db, params))
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'c` but it is returning data with lifetime `'1`

error: implementation of `chain::sqlx::Acquire` is not general enough
  --> crates/wallet/src/wallet/persisted.rs:36:13
   |
36 |             Box::pin(create(db, params))
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `chain::sqlx::Acquire` is not general enough
   |
   = note: `chain::sqlx::Acquire<'_>` would have to be implemented for the type `&mut PgConnection`
   = note: ...but `chain::sqlx::Acquire<'0>` is actually implemented for the type `&'0 mut PgConnection`, for some specific lifetime `'0`

error: lifetime may not live long enough
  --> crates/wallet/src/wallet/persisted.rs:56:9
   |
11 | impl<'c> chain::PersistAsyncWith<sqlx::Transaction<'c, sqlx::Postgres>> for Wallet {
   |      -- lifetime `'c` defined here
...
41 |         conn: &mut sqlx::Transaction<'c, sqlx::Postgres>,
   |               - let's call the lifetime of this reference `'1`
...
56 |         Box::pin(load(conn,params))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'c` but it is returning data with lifetime `'1`

error: implementation of `chain::sqlx::Acquire` is not general enough
  --> crates/wallet/src/wallet/persisted.rs:56:9
   |
56 |         Box::pin(load(conn,params))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `chain::sqlx::Acquire` is not general enough
   |
   = note: `chain::sqlx::Acquire<'_>` would have to be implemented for the type `&mut PgConnection`
   = note: ...but `chain::sqlx::Acquire<'0>` is actually implemented for the type `&'0 mut PgConnection`, for some specific lifetime `'0`

error: lifetime may not live long enough
  --> crates/wallet/src/wallet/persisted.rs:69:9
   |
11 | impl<'c> chain::PersistAsyncWith<sqlx::Transaction<'c, sqlx::Postgres>> for Wallet {
   |      -- lifetime `'c` defined here
...
60 |         db: &mut sqlx::Transaction<'c, sqlx::Postgres>,
   |             - let's call the lifetime of this reference `'1`
...
69 |         Box::pin(persist(db, changeset))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'c` but it is returning data with lifetime `'1`

error: lifetime may not live long enough
  --> crates/wallet/src/wallet/persisted.rs:69:9
   |
11 | impl<'c> chain::PersistAsyncWith<sqlx::Transaction<'c, sqlx::Postgres>> for Wallet {
   |      -- lifetime `'c` defined here
...
61 |         changeset: &<Self as chain::Staged>::ChangeSet,
   |                    - let's call the lifetime of this reference `'2`
...
69 |         Box::pin(persist(db, changeset))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'c` but it is returning data with lifetime `'2`

error: implementation of `chain::sqlx::Acquire` is not general enough
  --> crates/wallet/src/wallet/persisted.rs:69:9
   |
69 |         Box::pin(persist(db, changeset))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `chain::sqlx::Acquire` is not general enough
   |
   = note: `chain::sqlx::Acquire<'_>` would have to be implemented for the type `&mut PgConnection`
   = note: ...but `chain::sqlx::Acquire<'0>` is actually implemented for the type `&'0 mut PgConnection`, for some specific lifetime `'0`

error: implementation of `chain::sqlx::Acquire` is not general enough
   --> crates/wallet/src/wallet/persisted.rs:128:9
    |
128 |         Box::pin(persist(db, changeset))
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `chain::sqlx::Acquire` is not general enough
    |
    = note: `chain::sqlx::Acquire<'_>` would have to be implemented for the type `&mut PgConnection`
    = note: ...but `chain::sqlx::Acquire<'0>` is actually implemented for the type `&'0 mut PgConnection`, for some specific lifetime `'0`

error: implementation of `Send` is not general enough
   --> crates/wallet/src/wallet/persisted.rs:128:9
    |
128 |         Box::pin(persist(db, changeset))
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough
    |
    = note: `Send` would have to be implemented for the type `&Pool<Postgres>`
    = note: ...but `Send` is actually implemented for the type `&'0 Pool<Postgres>`, for some specific lifetime `'0`

@evanlinjin evanlinjin mentioned this pull request Aug 15, 2024
4 tasks
@matthiasdebernardini matthiasdebernardini closed this by deleting the head repository Aug 15, 2024
notmandatory added a commit that referenced this pull request Aug 19, 2024
340808e docs(wallet): fixes/improvements for `persisted` and `params` types (志宇)
9600293 feat(wallet)!: add persister (`P`) type param to `PersistedWallet<P>` (志宇)
0616057 revert(chain)!: rm `persit` module (志宇)
a9c5f76 feat(wallet)!: remove dependency on `bdk_chain::Staged` (志宇)
06a9d6c feat(chain,wallet)!: publicize `.init_sqlite_tables` changeset methods (志宇)
039622f feat(wallet)!: introduce `WalletPersister` (志宇)

Pull request description:

  ### Description

  Removed the persistence module in `bdk_chain` (which contained the `PersistWith` and `PersistAsyncWith` traits and a `Persisted<T>` wrapper). Replaced it with simplified versions that are `Wallet`-specific.

  The new traits (`WalletPersister` and `AsyncWalletPersister`) are simpler since they have less type-parameters (being wallet-specific). The old versions were more complex as they were intended to be used with any type. However, we need more time to finalize a works-for-all-bdk-types persistence API.

  Additionally, `WalletPersister` and `AsyncWalletPersister` also introduce the `initialize` method. It is handy to contain db-initialization (i.e. create tables for SQL) here. We can call `initialize` in `PersistedWallet` constructors. So the `PersistedWallet::persist` method does not need to ensure the database is initialized. To accommodate this, I made the `.init_sqlite_table` methods on changeset-types public, and removed the call of this in `from_sqlite` and `.persist_to_sqlite`. The `initialize` method now loads from the persister, so the `load` associated function is removed.

  There was a bug in the old `PersistAsyncWith` trait where the lifetime bounds were not strict enough (refer to the conversation in #1552). This is now fixed in the new `AsyncWalletPersister` trait.

  Docs for the new types are clearer (hopefully). I mentioned implementation details about the new traits and what safety checks were guaranteed in `PersistedWallet`.

  I think it makes sense just to have a wallet-specific persistence API which we will maintain alongside wallet for v1.0. This is less baggage for users and maintainers alike.

  ### Notes to the reviewers

  **How breaking are these changes?**

  Unless if you were implementing your own persistence, then not breaking at all! If you were implementing your own persistence for BDK, the breakages are minimal. The main change is introducing the `initialize` method to persistence traits (which replaces `load`).

  **Acknowledgements**

  I came up with the idea of this while responding to @tnull's suggestions on Discord. Unfortunately we cannot fulfill all of his requests. However, I do believe that this will make things easier for our users.

  ### Changelog notice

  * Removed the `persist` module in `bdk_chain`.
  * Added `WalletPersister`/`AsyncWalletPersister` traits and `PersistedWallet` struct to `bdk_wallet`. These are simplified and safer versions of old structs provided by the `persist` module in `bdk_chain`.
  * Change `.init_sqlite_tables` method to be public on changeset types. `from_sqlite` and `persist_into_sqlite` methods no longer call `.init_sqlite_tables` internally.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### What is left to do:

  - [x] Better docs.

ACKs for top commit:
  notmandatory:
    ACK 340808e

Tree-SHA512: 77643196543ad0a741f9ec51de4aad3893c6d608234ad6709c8688043bf1eef1e5e97e71681be0a6b29dec3fa255e2f0e63e08511c79ea4c0a6ef3286653d40c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

4 participants