From d3c8e3c7cafa069fa58b463db3a6020d75def02a Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 Oct 2026 12:04:55 +0200 Subject: [PATCH 1/3] Use manual downloads for test binaries Fresh builds fail because automatic test binary downloads require yanked zip releases. The no_download configuration still leaves these requirements in Cargo's dependency graph. Use the existing verified download script for local test binaries, including the VSS jobs, which share the Rust workflow's binary caches. Document the required environment so fresh builds and local tests work with the current harness releases. Upstream fix: https://git.rust-bitcoin.org/rust-bitcoin/corepc/pulls/720 Co-Authored-By: HAL 9000 (cherry picked from commit a0b9593c54a6bd2de22f9a3a5ce389e1e81ff357) --- .github/workflows/rust.yml | 4 ++-- .github/workflows/vss-integration.yml | 24 ++++++++++++++++++++++++ Cargo.toml | 6 ++---- README.md | 14 ++++++++++++++ tests/common/mod.rs | 10 +++------- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 32bb5e19e9..d63e4688d3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -107,8 +107,8 @@ jobs: - name: Test on Rust ${{ matrix.toolchain }} if: "matrix.platform != 'windows-latest'" run: | - RUSTFLAGS="--cfg no_download" cargo test + cargo test - name: Test with UniFFI support on Rust ${{ matrix.toolchain }} if: "matrix.platform != 'windows-latest' && matrix.build-uniffi" run: | - RUSTFLAGS="--cfg no_download" cargo test --features uniffi + cargo test --features uniffi diff --git a/.github/workflows/vss-integration.yml b/.github/workflows/vss-integration.yml index 54e118c1a7..2c34147764 100644 --- a/.github/workflows/vss-integration.yml +++ b/.github/workflows/vss-integration.yml @@ -36,6 +36,30 @@ jobs: repository: lightningdevkit/vss-server path: vss-server + - name: Enable caching for bitcoind + id: cache-bitcoind + uses: actions/cache@v4 + with: + path: bin/bitcoind-${{ runner.os }}-${{ runner.arch }} + key: bitcoind-${{ runner.os }}-${{ runner.arch }} + - name: Enable caching for electrs + id: cache-electrs + uses: actions/cache@v4 + with: + path: bin/electrs-${{ runner.os }}-${{ runner.arch }} + key: electrs-${{ runner.os }}-${{ runner.arch }} + - name: Download bitcoind/electrs + if: "steps.cache-bitcoind.outputs.cache-hit != 'true' || steps.cache-electrs.outputs.cache-hit != 'true'" + run: | + source ./ldk-node/scripts/download_bitcoind_electrs.sh + mkdir -p bin + mv "$BITCOIND_EXE" bin/bitcoind-${{ runner.os }}-${{ runner.arch }} + mv "$ELECTRS_EXE" bin/electrs-${{ runner.os }}-${{ runner.arch }} + - name: Set bitcoind/electrs environment variables + run: | + echo "BITCOIND_EXE=$( pwd )/bin/bitcoind-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV" + echo "ELECTRS_EXE=$( pwd )/bin/electrs-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV" + - name: Build and Deploy VSS Server run: | version=$(cargo metadata --no-deps --format-version 1 --manifest-path ldk-node/Cargo.toml | jq -r '.packages[] | select(.name == "ldk-node") | .version') diff --git a/Cargo.toml b/Cargo.toml index 09bce27f38..6c1ae83891 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,10 +77,8 @@ proptest = "1.0.0" regex = "1.5.6" criterion = { version = "0.7.0", features = ["async_tokio"] } -[target.'cfg(not(no_download))'.dev-dependencies] -electrsd = { version = "0.36.1", default-features = false, features = ["legacy", "esplora_a33e97e1", "corepc-node_27_2"] } - -[target.'cfg(no_download)'.dev-dependencies] +# Test binaries are provided via scripts/download_bitcoind_electrs.sh. +# Keep automatic downloads disabled to avoid the yanked zip dependency. electrsd = { version = "0.36.1", default-features = false, features = ["legacy"] } corepc-node = { version = "0.10.0", default-features = false, features = ["27_2"] } diff --git a/README.md b/README.md index d11c5fc8e3..003daba8ac 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,20 @@ LDK Node itself is written in [Rust][rust] and may therefore be natively added a ## MSRV The Minimum Supported Rust Version (MSRV) is currently 1.85.0. +## Running Tests + +Rust integration tests and benchmarks require Bitcoin Core and Esplora-enabled `electrs` binaries. +On Linux and macOS, run the following from the repository root in Bash: + +```bash +source ./scripts/download_bitcoind_electrs.sh +RUSTFLAGS="--cfg tokio_unstable" cargo test +``` + +The script requires `curl`, `unzip`, `tar`, and `shasum`. It verifies the downloads and exports +`BITCOIND_EXE` and `ELECTRS_EXE` for the current shell. Alternatively, set these variables to +compatible binaries you already have. Cargo's automatic binary downloads are disabled. + [api_docs]: https://docs.rs/ldk-node/*/ldk_node/ [api_docs_node]: https://docs.rs/ldk-node/*/ldk_node/struct.Node.html [api_docs_builder]: https://docs.rs/ldk-node/*/ldk_node/struct.Builder.html diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 833dfecc21..3ecc2518c2 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -187,19 +187,15 @@ macro_rules! expect_payment_successful_event { pub(crate) use expect_payment_successful_event; pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) { - let bitcoind_exe = - env::var("BITCOIND_EXE").ok().or_else(|| corepc_node::downloaded_exe_path().ok()).expect( - "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature", - ); + let bitcoind_exe = env::var("BITCOIND_EXE") + .expect("set BITCOIND_EXE or source ./scripts/download_bitcoind_electrs.sh before testing"); let mut bitcoind_conf = corepc_node::Conf::default(); bitcoind_conf.network = "regtest"; bitcoind_conf.args.push("-rest"); let bitcoind = BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap(); let electrs_exe = env::var("ELECTRS_EXE") - .ok() - .or_else(electrsd::downloaded_exe_path) - .expect("you need to provide env var ELECTRS_EXE or specify an electrsd version feature"); + .expect("set ELECTRS_EXE or source ./scripts/download_bitcoind_electrs.sh before testing"); let mut electrsd_conf = electrsd::Conf::default(); electrsd_conf.http_enabled = true; electrsd_conf.network = "regtest"; From 0bbf19fb6b3e88fefea61eaeea04122264d53c25 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 20 Oct 2026 21:23:11 +0200 Subject: [PATCH 2/3] Widen the supported rusqlite range Requiring rusqlite 0.31 prevents applications from combining LDK Node with dependencies that need newer SQLite bindings, even when the persistence APIs remain compatible. Allow releases from 0.31 through 0.39 while preserving Rust 1.85 support and existing persistence behavior. Exclude 0.40 because its first two releases require a newer compiler. Co-Authored-By: HAL 9000 (cherry picked from commit 0969b15fb86fab8ed1adec73844f5f13c2b75e2f) --- Cargo.toml | 3 ++- src/io/sqlite_store/migrations.rs | 24 ++++++++---------------- src/io/sqlite_store/mod.rs | 15 ++++----------- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c1ae83891..5ddf5e926d 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,8 @@ bdk_wallet = { version = "2.2.0", default-features = false, features = ["std", " reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } rustls = { version = "0.23", default-features = false } -rusqlite = { version = "0.31.0", features = ["bundled"] } +# rusqlite 0.40.0 and 0.40.1 do not compile on our MSRV. +rusqlite = { version = ">=0.31.0, <0.40.0", features = ["bundled"] } bitcoin = "0.32.7" bip39 = { version = "2.0.0", features = ["rand"] } bip21 = { version = "0.5", features = ["std"], default-features = false } diff --git a/src/io/sqlite_store/migrations.rs b/src/io/sqlite_store/migrations.rs index abfbdf6ef0..f8c403f6de 100644 --- a/src/io/sqlite_store/migrations.rs +++ b/src/io/sqlite_store/migrations.rs @@ -52,14 +52,13 @@ pub(super) fn migrate_schema( })?; // Update user_version - tx.pragma(Some(rusqlite::DatabaseName::Main), "user_version", to_version, |_| Ok(())) - .map_err(|e| { - let msg = format!( - "Failed to upgrade user_version from {} to {}: {}", - from_version, to_version, e - ); - io::Error::new(io::ErrorKind::Other, msg) - })?; + tx.pragma_update(None, "user_version", to_version).map_err(|e| { + let msg = format!( + "Failed to upgrade user_version from {} to {}: {}", + from_version, to_version, e + ); + io::Error::new(io::ErrorKind::Other, msg) + })?; tx.commit().map_err(|e| { let msg = format!( @@ -104,14 +103,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 {} ( diff --git a/src/io/sqlite_store/mod.rs b/src/io/sqlite_store/mod.rs index 789330cefe..cc931461d4 100644 --- a/src/io/sqlite_store/mod.rs +++ b/src/io/sqlite_store/mod.rs @@ -260,17 +260,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, From 10e29612863a5749318453bd78779785e2e0148e Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 20 Oct 2026 21:23:28 +0200 Subject: [PATCH 3/3] Test rusqlite compatibility at range endpoints Root builds remain on rusqlite 0.31 because historical Node releases are required by upgrade tests. This can conceal incompatibilities with newer releases allowed by the library. Test downstream persistence with exact minimum and maximum versions on Rust 1.85. Keep the fixture independent of the root test graph so both endpoints are actually exercised. Co-Authored-By: HAL 9000 (cherry picked from commit 0d96cbe093c14ba1405fb6fb86736ff5950c3150) --- .github/workflows/rust.yml | 20 ++++++++++++++++ tests/rusqlite-compat/Cargo.toml | 14 +++++++++++ tests/rusqlite-compat/README.md | 32 +++++++++++++++++++++++++ tests/rusqlite-compat/src/lib.rs | 40 ++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 tests/rusqlite-compat/Cargo.toml create mode 100644 tests/rusqlite-compat/README.md create mode 100644 tests/rusqlite-compat/src/lib.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d63e4688d3..04ad5f8e4b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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: defaults: run: diff --git a/tests/rusqlite-compat/Cargo.toml b/tests/rusqlite-compat/Cargo.toml new file mode 100644 index 0000000000..a2be54dee1 --- /dev/null +++ b/tests/rusqlite-compat/Cargo.toml @@ -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"] } diff --git a/tests/rusqlite-compat/README.md b/tests/rusqlite-compat/README.md new file mode 100644 index 0000000000..c27412e60c --- /dev/null +++ b/tests/rusqlite-compat/README.md @@ -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. diff --git a/tests/rusqlite-compat/src/lib.rs b/tests/rusqlite-compat/src/lib.rs new file mode 100644 index 0000000000..4787de2fd8 --- /dev/null +++ b/tests/rusqlite-compat/src/lib.rs @@ -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 or the MIT license , 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 = 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(); + } +}