Skip to content
Merged
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
20 changes: 20 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ concurrency:
cancel-in-progress: true

jobs:
rusqlite-compat:
runs-on: self-hosted
timeout-minutes: 30
strategy:
matrix:
# Keep these endpoints in sync with the supported range in Cargo.toml.
rusqlite: ["0.31.0", "0.39.0"]
env:
CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback
steps:
- uses: actions/checkout@v4
- name: Install MSRV toolchain
run: rustup toolchain install 1.85.0 --profile minimal
- name: Test downstream rusqlite compatibility on MSRV
run: |
export CARGO_TARGET_DIR=$(mktemp -d /tmp/cargo-target-ldk-node-rusqlite-XXXXXX)
trap 'rm -rf "$CARGO_TARGET_DIR"' EXIT
cargo +1.85.0 add --manifest-path tests/rusqlite-compat/Cargo.toml rusqlite@=${{ matrix.rusqlite }}
cargo +1.85.0 test --manifest-path tests/rusqlite-compat/Cargo.toml

build:
timeout-minutes: 120
strategy:
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ bdk_wallet = { version = "3.1.0", default-features = false, features = ["std", "

bitreq = { version = "0.3", default-features = false, features = ["async-https", "json-using-serde"] }
rustls = { version = "0.23", default-features = false }
rusqlite = { version = "0.31.0", features = ["bundled"], optional = true }
# rusqlite 0.40.0 and 0.40.1 do not compile on our MSRV.
rusqlite = { version = ">=0.31.0, <0.40.0", features = ["bundled"], optional = true }
bitcoin = "0.32.7"
bip39 = { version = "3.0.0", features = ["rand"] }
bip21 = { version = "0.5", features = ["std"], default-features = false, optional = true }
Expand Down
38 changes: 10 additions & 28 deletions src/io/sqlite_store/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,10 @@ fn migrate_v1_to_v2(connection: &mut Connection, kv_table_name: &str) -> io::Res
})?;

// Update user_version
tx.pragma(Some(rusqlite::DatabaseName::Main), "user_version", 2u16, |_| Ok(())).map_err(
|e| {
let msg = format!("Failed to upgrade user_version from 1 to 2: {}", e);
io::Error::new(io::ErrorKind::Other, msg)
},
)?;
tx.pragma_update(None, "user_version", 2u16).map_err(|e| {
let msg = format!("Failed to upgrade user_version from 1 to 2: {}", e);
io::Error::new(io::ErrorKind::Other, msg)
})?;

tx.commit().map_err(|e| {
let msg = format!("Failed to migrate table {} from v1 to v2: {}", kv_table_name, e);
Expand Down Expand Up @@ -150,12 +148,10 @@ fn migrate_v2_to_v3(connection: &mut Connection, kv_table_name: &str) -> io::Res
tx.execute(&sql, []).map_err(map_err)?;

// Update user_version
tx.pragma(Some(rusqlite::DatabaseName::Main), "user_version", 3u16, |_| Ok(())).map_err(
|e| {
let msg = format!("Failed to upgrade user_version from 2 to 3: {}", e);
io::Error::new(io::ErrorKind::Other, msg)
},
)?;
tx.pragma_update(None, "user_version", 3u16).map_err(|e| {
let msg = format!("Failed to upgrade user_version from 2 to 3: {}", e);
io::Error::new(io::ErrorKind::Other, msg)
})?;

tx.commit().map_err(|e| {
let msg = format!("Failed to migrate table {} from v2 to v3: {}", kv_table_name, e);
Expand Down Expand Up @@ -197,14 +193,7 @@ mod tests {

let connection = Connection::open(db_file_path.clone()).unwrap();

connection
.pragma(
Some(rusqlite::DatabaseName::Main),
"user_version",
old_schema_version,
|_| Ok(()),
)
.unwrap();
connection.pragma_update(None, "user_version", old_schema_version).unwrap();

let sql = format!(
"CREATE TABLE IF NOT EXISTS {} (
Expand Down Expand Up @@ -281,14 +270,7 @@ mod tests {

let connection = Connection::open(db_file_path.clone()).unwrap();

connection
.pragma(
Some(rusqlite::DatabaseName::Main),
"user_version",
old_schema_version,
|_| Ok(()),
)
.unwrap();
connection.pragma_update(None, "user_version", old_schema_version).unwrap();

let sql = format!(
"CREATE TABLE IF NOT EXISTS {} (
Expand Down
24 changes: 11 additions & 13 deletions src/io/sqlite_store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,17 +301,10 @@ impl SqliteStoreInner {

if version_res == 0 {
// New database, set our SCHEMA_USER_VERSION and continue
connection
.pragma(
Some(rusqlite::DatabaseName::Main),
"user_version",
SCHEMA_USER_VERSION,
|_| Ok(()),
)
.map_err(|e| {
let msg = format!("Failed to set PRAGMA user_version: {}", e);
io::Error::new(io::ErrorKind::Other, msg)
})?;
connection.pragma_update(None, "user_version", SCHEMA_USER_VERSION).map_err(|e| {
let msg = format!("Failed to set PRAGMA user_version: {}", e);
io::Error::new(io::ErrorKind::Other, msg)
})?;
} else if version_res < SCHEMA_USER_VERSION {
migrations::migrate_schema(
&mut connection,
Expand Down Expand Up @@ -553,8 +546,13 @@ impl SqliteStoreInner {
self.kv_table_name
);
let count_sql = format!("SELECT COUNT(*) FROM {}", self.kv_table_name);
let count: usize =
locked_conn.query_row(&count_sql, [], |row| row.get(0)).map_err(|e| {
let count = locked_conn
.query_row(&count_sql, [], |row| {
let count: i64 = row.get(0)?;
usize::try_from(count)
.map_err(|_| rusqlite::Error::IntegralValueOutOfRange(0, count))
})
.map_err(|e| {
let msg = format!("Failed to count rows: {}", e);
io::Error::new(io::ErrorKind::Other, msg)
})?;
Expand Down
14 changes: 14 additions & 0 deletions tests/rusqlite-compat/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "ldk-node-rusqlite-compat"
version = "0.0.0"
edition = "2021"
rust-version = "1.85"
publish = false

# Resolve as a downstream application, without LDK Node's legacy test dependencies.
[workspace]

[dependencies]
ldk-node = { path = "../.." }
rusqlite = "=0.39.0"
tokio = { version = "1.39", features = ["macros", "rt"] }
32 changes: 32 additions & 0 deletions tests/rusqlite-compat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Downstream rusqlite compatibility

This separate Cargo workspace checks that an application can select a stock
rusqlite release while using LDK Node's default features. The test writes through
LDK Node, reopens the store, and reads the database with the application's rusqlite.
Neither rusqlite nor libsqlite3-sys is patched.

The root crate's development dependencies on LDK Node 0.6.2 and 0.7.0 require
rusqlite 0.31 unconditionally. They constrain root builds even when individual
tests are filtered out, but do not participate in downstream resolution. Keep
this fixture separate so CI actually tests both ends of the supported range.

From the repository root, run:

```sh
export CARGO_TARGET_DIR=$(mktemp -d /tmp/cargo-target-ldk-node-rusqlite-XXXXXX)
export CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback
cargo +1.85.0 test --manifest-path tests/rusqlite-compat/Cargo.toml
```

The fixture defaults to the maximum tested version. To test another release,
change its direct requirement with
`cargo +1.85.0 add --manifest-path tests/rusqlite-compat/Cargo.toml rusqlite@=0.31.0`
and rerun the test. Restore the maximum requirement afterwards. The resolver
setting selects MSRV-compatible versions of unrelated transitive dependencies.

When changing the supported range, update both endpoints in
`.github/workflows/rust.yml` and test each newly included release locally. The
current upper bound excludes 0.40.0 and 0.40.1, which use `cfg_select!` and fail to
compile on Rust 1.85. Although the bundled-feature probe for 0.40.2 passes on
1.85, Cargo cannot express a disjoint requirement that admits it while excluding
those earlier patches and retaining the older release lines.
40 changes: 40 additions & 0 deletions tests/rusqlite-compat/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// This file is Copyright its original authors, visible in version control history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
// accordance with one or both of these licenses.

#[cfg(test)]
mod tests {
use ldk_node::io::sqlite_store::{
SqliteStore, DEFAULT_KV_TABLE_NAME, DEFAULT_SQLITE_DB_FILE_NAME,
};
use ldk_node::lightning::util::persist::KVStore;

#[tokio::test]
async fn persists_with_the_applications_rusqlite() {
let data_dir =
std::env::temp_dir().join(format!("ldk-node-rusqlite-compat-{}", std::process::id()));
let store = SqliteStore::new(data_dir.clone(), None, None).unwrap();
KVStore::write(&store, "test", "", "key", vec![42]).await.unwrap();
drop(store);

let store = SqliteStore::new(data_dir.clone(), None, None).unwrap();
assert_eq!(KVStore::read(&store, "test", "", "key").await.unwrap(), vec![42]);
drop(store);

let connection =
rusqlite::Connection::open(data_dir.join(DEFAULT_SQLITE_DB_FILE_NAME)).unwrap();
let value: Vec<u8> = connection
.query_row(
&format!("SELECT value FROM {DEFAULT_KV_TABLE_NAME} WHERE key = 'key'"),
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(value, vec![42]);
drop(connection);
std::fs::remove_dir_all(data_dir).unwrap();
}
}
Loading