diff --git a/.github/workflows/hrn-integration.yml b/.github/workflows/hrn-integration.yml index 640da6fb2..732aad029 100644 --- a/.github/workflows/hrn-integration.yml +++ b/.github/workflows/hrn-integration.yml @@ -43,4 +43,4 @@ jobs: - name: Run HRN Integration Tests run: | RUSTFLAGS="--cfg no_download --cfg hrn_tests $RUSTFLAGS" cargo test --test integration_tests_hrn - RUSTFLAGS="--cfg no_download --cfg hrn_tests $RUSTFLAGS" cargo test --test integration_tests_hrn --features uniffi + RUSTFLAGS="--cfg no_download --cfg hrn_tests $RUSTFLAGS" cargo test --test integration_tests_hrn --no-default-features --features uniffi-default diff --git a/.github/workflows/postgres-integration.yml b/.github/workflows/postgres-integration.yml index 451bc7758..d27e3123e 100644 --- a/.github/workflows/postgres-integration.yml +++ b/.github/workflows/postgres-integration.yml @@ -32,6 +32,13 @@ jobs: - name: Install Rust stable toolchain run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain stable + - name: Check all features + run: cargo check --all-features --tests --verbose --color always + - name: Check sparse feature sets + run: | + cargo check --no-default-features --features uniffi,chain-bitcoind,storage-postgres --verbose --color always + cargo test --lib --no-run --no-default-features --features chain-electrum,storage-postgres --verbose --color always + cargo check --test integration_tests_postgres --no-default-features --features chain-electrum,storage-postgres --verbose --color always - name: Enable caching for bitcoind id: cache-bitcoind uses: actions/cache@v4 @@ -58,9 +65,9 @@ jobs: - name: Run PostgreSQL store tests env: TEST_POSTGRES_URL: "host=localhost user=postgres password=postgres" - run: cargo test --features postgres io::postgres_store + run: cargo test --features storage-postgres io::postgres_store - name: Run PostgreSQL integration tests env: TEST_POSTGRES_URL: "host=localhost user=postgres password=postgres" run: | - RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test --features postgres --test integration_tests_postgres + RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test --features storage-postgres --test integration_tests_postgres diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6871a3927..ea3df44b2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -76,12 +76,12 @@ jobs: run: cargo build --verbose --color always - name: Build with UniFFI support on Rust ${{ matrix.toolchain }} if: matrix.build-uniffi - run: cargo build --features uniffi --verbose --color always + run: cargo build --no-default-features --features uniffi-default --verbose --color always - name: Check release build on Rust ${{ matrix.toolchain }} run: cargo check --release --verbose --color always - name: Check release build with UniFFI support on Rust ${{ matrix.toolchain }} if: matrix.build-uniffi - run: cargo check --release --features uniffi --verbose --color always + run: cargo check --release --no-default-features --features uniffi-default --verbose --color always - name: Test on Rust ${{ matrix.toolchain }} if: "matrix.platform != 'windows-latest'" run: | @@ -89,7 +89,7 @@ jobs: - name: Test with UniFFI support on Rust ${{ matrix.toolchain }} if: "matrix.platform != 'windows-latest' && matrix.build-uniffi" run: | - RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test --features uniffi + RUSTFLAGS="--cfg no_download --cfg cycle_tests" cargo test --no-default-features --features uniffi-default linting: name: Linting @@ -106,7 +106,7 @@ jobs: - name: Ban `unwrap` in library code run: | cargo clippy --lib --verbose --color always -- -A warnings -D clippy::unwrap_used -A clippy::tabs_in_doc_comments - cargo clippy --lib --features uniffi --verbose --color always -- -A warnings -D clippy::unwrap_used -A clippy::tabs_in_doc_comments + cargo clippy --lib --no-default-features --features uniffi-default --verbose --color always -- -A warnings -D clippy::unwrap_used -A clippy::tabs_in_doc_comments doc: name: Documentation diff --git a/Cargo.toml b/Cargo.toml index fc1fe6c36..6c9a2eba8 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,48 @@ codegen-units = 1 # Reduce number of codegen units to increase optimizations. panic = 'abort' # Abort on panic [features] -default = [] -postgres = ["dep:tokio-postgres", "dep:native-tls", "dep:postgres-native-tls"] +default = [ + "chain-esplora", + "chain-electrum", + "chain-bitcoind", + "storage-sqlite", + "storage-filesystem", + "storage-vss", + "unified-payments", +] +chain-esplora = [ + "dep:bdk_esplora", + "dep:esplora-client", + "dep:ldk-esplora-client", + "dep:lightning-transaction-sync", + "lightning-transaction-sync/esplora-async-https", + "lightning-transaction-sync/time", +] +chain-electrum = [ + "dep:bdk_electrum", + "dep:electrum-client", + "dep:lightning-transaction-sync", + "lightning-transaction-sync/electrum-rustls-ring", +] +chain-bitcoind = ["dep:lightning-block-sync"] +storage-sqlite = ["dep:rusqlite"] +storage-filesystem = ["dep:lightning-persister"] +storage-vss = ["dep:vss-client", "dep:prost"] +storage-postgres = ["dep:tokio-postgres", "dep:native-tls", "dep:postgres-native-tls"] +unified-payments = [ + "dep:bip21", + "dep:bitcoin-payment-instructions", + "dep:lightning-dns-resolver", +] +uniffi = ["dep:uniffi"] +uniffi-default = [ + "uniffi", + "chain-esplora", + "chain-electrum", + "storage-sqlite", + "storage-vss", + "unified-payments", +] [dependencies] #lightning = { version = "0.2.0", features = ["std"] } @@ -45,35 +85,35 @@ lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = " lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb" } lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", features = ["std"] } lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb" } -lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", features = ["tokio"] } +lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", features = ["tokio"], optional = true } lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb" } lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb" } -lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } +lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", features = ["rest-client", "rpc-client", "tokio"], optional = true } +lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", optional = true } lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", features = ["std"] } lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb" } -lightning-dns-resolver = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb" } +lightning-dns-resolver = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "9174965af9437196c527a9aa0df36bbcf050c8bb", optional = true } bdk_chain = { version = "0.23.3", default-features = false, features = ["std"] } -bdk_esplora = { version = "0.22.2", default-features = false, features = ["async-https-rustls", "tokio"]} -bdk_electrum = { version = "0.24.0", default-features = false, features = ["use-rustls-ring"]} +bdk_esplora = { version = "0.22.2", default-features = false, features = ["async-https-rustls", "tokio"], optional = true } +bdk_electrum = { version = "0.24.0", default-features = false, features = ["use-rustls-ring"], optional = true } bdk_wallet = { version = "3.1.0", default-features = false, features = ["std", "keys-bip39"]} 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"] } +rusqlite = { version = "0.31.0", features = ["bundled"], optional = true } bitcoin = "0.32.7" bip39 = { version = "2.0.0", features = ["rand"] } -bip21 = { version = "0.5", features = ["std"], default-features = false } +bip21 = { version = "0.5", features = ["std"], default-features = false, optional = true } base64 = { version = "0.22.1", default-features = false, features = ["std"] } getrandom = { version = "0.3", default-features = false } chrono = { version = "0.4", default-features = false, features = ["clock"] } tokio = { version = "1.39", default-features = false, features = [ "rt-multi-thread", "time", "sync", "macros", "net" ] } tokio-util = { version = "0.7.10", default-features = false, features = ["rt"] } -esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"] } -ldk-esplora-client = { package = "esplora-client", version = "0.13", default-features = false, features = ["tokio", "async-https-rustls"] } -electrum-client = { version = "0.25", default-features = false, features = ["proxy", "use-rustls-ring"] } +esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"], optional = true } +ldk-esplora-client = { package = "esplora-client", version = "0.13", default-features = false, features = ["tokio", "async-https-rustls"], optional = true } +electrum-client = { version = "0.25", default-features = false, features = ["proxy", "use-rustls-ring"], optional = true } libc = "0.2" uniffi = { version = "0.29.5", features = ["build"], optional = true } serde = { version = "1.0.210", default-features = false, features = ["std", "derive"] } @@ -84,10 +124,10 @@ async-trait = { version = "0.1", default-features = false } tokio-postgres = { version = "0.7", default-features = false, features = ["runtime"], optional = true } native-tls = { version = "0.2", default-features = false, optional = true } postgres-native-tls = { version = "0.5", default-features = false, features = ["runtime"], optional = true } -vss-client = { package = "vss-client-ng", version = "0.6" } -prost = { version = "0.11.6", default-features = false} +vss-client = { package = "vss-client-ng", version = "0.6", optional = true } +prost = { version = "0.11.6", default-features = false, optional = true} #bitcoin-payment-instructions = { version = "0.6" } -bitcoin-payment-instructions = { git = "https://github.com/tnull/bitcoin-payment-instructions", rev = "1d6ffaa8962391ddc84aeef98bd7439e55ccae9d" } +bitcoin-payment-instructions = { git = "https://github.com/tnull/bitcoin-payment-instructions", rev = "1d6ffaa8962391ddc84aeef98bd7439e55ccae9d", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase"] } diff --git a/README.md b/README.md index 1cd2e3643..55c0f56ee 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,60 @@ LDK Node currently comes with a decidedly opinionated set of design choices: - On-chain data is handled by the integrated [BDK][bdk] wallet. - Chain data may currently be sourced from the Bitcoin Core RPC interface, or from an [Electrum][electrum] or [Esplora][esplora] server. -- Wallet and channel state may be persisted to an [SQLite][sqlite] or [PostgreSQL][postgresql] database, to file system, or to a custom back-end to be implemented by the user. +- Wallet and channel state may be persisted to an [SQLite][sqlite] or [PostgreSQL][postgresql] database, to the filesystem, to a VSS server, or to a custom back-end to be implemented by the user. - Gossip data may be sourced via Lightning's peer-to-peer network or the [Rapid Gossip Sync](https://docs.rs/lightning-rapid-gossip-sync/*/lightning_rapid_gossip_sync/) protocol. - Entropy for the Lightning and on-chain wallets may be sourced from raw bytes or a [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic. In addition, LDK Node offers the means to generate and persist the entropy bytes to disk. +### Cargo Features + +LDK Node's optional dependencies are grouped by the functionality they provide: + +| Feature | Functionality | +| --- | --- | +| `chain-esplora` | Esplora chain source | +| `chain-electrum` | Electrum chain source | +| `chain-bitcoind` | Bitcoin Core RPC and REST chain source | +| `storage-sqlite` | SQLite storage | +| `storage-filesystem` | Filesystem storage | +| `storage-vss` | Versioned Storage Service storage | +| `storage-postgres` | PostgreSQL storage | +| `unified-payments` | BIP 21 and human-readable-name payment support | +| `uniffi` | UniFFI language bindings | +| `uniffi-default` | The standard language-binding feature set | + +The `default` feature set preserves the native Rust API's previous behavior. It enables all three +chain sources, SQLite, filesystem and VSS storage, and unified payments. PostgreSQL and UniFFI +remain opt-in. Every build must enable at least one chain source feature. + +Disable the default features to select only the functionality and dependencies an application +needs. For example: + +```shell +cargo build --no-default-features --features chain-esplora,storage-sqlite +``` + +`uniffi-default` enables UniFFI, Esplora, Electrum, SQLite, VSS, and unified payments. It excludes +Bitcoin Core, filesystem storage, and PostgreSQL. Binding users can add any of those features: + +```shell +cargo build --no-default-features --features uniffi-default,chain-bitcoind +``` + +Use `uniffi` directly instead of `uniffi-default` to assemble a fully custom binding build. For +example, a Bitcoin Core and PostgreSQL-only binding build uses: + +```shell +cargo build --no-default-features --features uniffi,chain-bitcoind,storage-postgres +``` + +The binding generation scripts use `uniffi-default`. Set `LDK_NODE_EXTRA_FEATURES` to add features +to their builds: + +```shell +LDK_NODE_EXTRA_FEATURES=chain-bitcoind,storage-postgres \ + ./scripts/uniffi_bindgen_generate_python.sh +``` + ## Compatibility LDK Node does not provide a stable public API until v1.0. Persisted node state is backwards compatible: newer releases are guaranteed to load state written by older releases. Downgrades are not supported, so state written by a newer release may not load with an older release. diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 3829ddfb2..4c4c1a438 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -44,47 +44,6 @@ interface ProbingConfigBuilder { }; interface Builder { - constructor(); - [Name=from_config] - constructor(Config config); - void set_chain_source_esplora(string server_url, EsploraSyncConfig? config); - void set_chain_source_electrum(string server_url, ElectrumSyncConfig? config); - void set_chain_source_bitcoind_rpc(string rpc_host, u16 rpc_port, string rpc_user, string rpc_password, u32? wallet_rescan_from_height); - void set_chain_source_bitcoind_rest(string rest_host, u16 rest_port, string rpc_host, u16 rpc_port, string rpc_user, string rpc_password, u32? wallet_rescan_from_height); - void set_gossip_source_p2p(); - void set_gossip_source_rgs(string rgs_server_url); - void set_pathfinding_scores_source(string url); - void add_liquidity_source(PublicKey node_id, SocketAddress address, string? token, boolean trust_peer_0conf); - void set_storage_dir_path(string storage_dir_path); - void set_filesystem_logger(string? log_file_path, LogLevel? max_log_level); - void set_log_facade_logger(); - void set_custom_logger(LogWriter log_writer); - void set_network(Network network); - [Throws=BuildError] - void set_listening_addresses(sequence listening_addresses); - [Throws=BuildError] - void set_announcement_addresses(sequence announcement_addresses); - [Throws=BuildError] - void set_tor_config(TorConfig tor_config); - [Throws=BuildError] - void set_node_alias(string node_alias); - [Throws=BuildError] - void set_async_payments_role(AsyncPaymentsRole? role); - void set_probing_config(ProbingConfig config); - [Throws=BuildError] - Node build(NodeEntropy node_entropy); - [Throws=BuildError] - Node build_with_postgres_store(NodeEntropy node_entropy, string connection_string, string? db_name, string? kv_table_name, string? certificate_pem); - [Throws=BuildError] - Node build_with_fs_store(NodeEntropy node_entropy); - [Throws=BuildError] - Node build_with_vss_store(NodeEntropy node_entropy, string vss_url, string store_id, record fixed_headers); - [Throws=BuildError] - Node build_with_vss_store_and_lnurl_auth(NodeEntropy node_entropy, string vss_url, string store_id, string lnurl_auth_server_url, record fixed_headers); - [Throws=BuildError] - Node build_with_vss_store_and_fixed_headers(NodeEntropy node_entropy, string vss_url, string store_id, record fixed_headers); - [Throws=BuildError] - Node build_with_vss_store_and_header_provider(NodeEntropy node_entropy, string vss_url, string store_id, VssHeaderProvider header_provider); }; interface Node { @@ -108,7 +67,6 @@ interface Node { Bolt12Payment bolt12_payment(); SpontaneousPayment spontaneous_payment(); OnchainPayment onchain_payment(); - UnifiedPayment unified_payment(); Liquidity liquidity(); [Throws=NodeError] void lnurl_auth(string lnurl); @@ -179,8 +137,6 @@ interface FeeRate { u64 to_sat_per_vb_ceil(); }; -typedef interface UnifiedPayment; - typedef interface Liquidity; [Error] diff --git a/scripts/uniffi_bindgen_generate_kotlin.sh b/scripts/uniffi_bindgen_generate_kotlin.sh index f82d5c0d0..aca77a6e6 100755 --- a/scripts/uniffi_bindgen_generate_kotlin.sh +++ b/scripts/uniffi_bindgen_generate_kotlin.sh @@ -4,6 +4,11 @@ TARGET_DIR="target/bindings/kotlin" PROJECT_DIR="ldk-node-jvm" PACKAGE_DIR="org/lightningdevkit/ldknode" UNIFFI_BINDGEN_BIN="cargo run --manifest-path bindings/uniffi-bindgen/Cargo.toml" +UNIFFI_FEATURES="uniffi-default" + +if [[ -n "${LDK_NODE_EXTRA_FEATURES:-}" ]]; then + UNIFFI_FEATURES+=",$LDK_NODE_EXTRA_FEATURES" +fi case " ${RUSTFLAGS:-} " in *" --cfg tokio_unstable "*|*" --cfg=tokio_unstable "*) ;; @@ -12,21 +17,21 @@ esac if [[ "$OSTYPE" == "linux-gnu"* ]]; then rustup target add x86_64-unknown-linux-gnu || exit 1 - cargo build --release --target x86_64-unknown-linux-gnu --features uniffi || exit 1 + cargo build --release --target x86_64-unknown-linux-gnu --no-default-features --features "$UNIFFI_FEATURES" || exit 1 DYNAMIC_LIB_PATH="target/x86_64-unknown-linux-gnu/release/libldk_node.so" RES_DIR="$BINDINGS_DIR/$PROJECT_DIR/lib/src/main/resources/linux-x86-64/" mkdir -p $RES_DIR || exit 1 cp $DYNAMIC_LIB_PATH $RES_DIR || exit 1 else rustup target add x86_64-apple-darwin || exit 1 - cargo build --release --target x86_64-apple-darwin --features uniffi || exit 1 + cargo build --release --target x86_64-apple-darwin --no-default-features --features "$UNIFFI_FEATURES" || exit 1 DYNAMIC_LIB_PATH="target/x86_64-apple-darwin/release/libldk_node.dylib" RES_DIR="$BINDINGS_DIR/$PROJECT_DIR/lib/src/main/resources/darwin-x86-64/" mkdir -p $RES_DIR || exit 1 cp $DYNAMIC_LIB_PATH $RES_DIR || exit 1 rustup target add aarch64-apple-darwin || exit 1 - cargo build --release --target aarch64-apple-darwin --features uniffi || exit 1 + cargo build --release --target aarch64-apple-darwin --no-default-features --features "$UNIFFI_FEATURES" || exit 1 DYNAMIC_LIB_PATH="target/aarch64-apple-darwin/release/libldk_node.dylib" RES_DIR="$BINDINGS_DIR/$PROJECT_DIR/lib/src/main/resources/darwin-aarch64/" mkdir -p $RES_DIR || exit 1 diff --git a/scripts/uniffi_bindgen_generate_kotlin_android.sh b/scripts/uniffi_bindgen_generate_kotlin_android.sh index d0eb8654d..55de04bb6 100755 --- a/scripts/uniffi_bindgen_generate_kotlin_android.sh +++ b/scripts/uniffi_bindgen_generate_kotlin_android.sh @@ -4,6 +4,11 @@ BINDINGS_DIR="bindings/kotlin" TARGET_DIR="target" PROJECT_DIR="ldk-node-android" UNIFFI_BINDGEN_BIN="cargo run --manifest-path bindings/uniffi-bindgen/Cargo.toml" +UNIFFI_FEATURES="uniffi-default" + +if [[ -n "${LDK_NODE_EXTRA_FEATURES:-}" ]]; then + UNIFFI_FEATURES+=",$LDK_NODE_EXTRA_FEATURES" +fi case " ${RUSTFLAGS:-} " in *" --cfg tokio_unstable "*|*" --cfg=tokio_unstable "*) RUSTFLAGS_WITH_TOKIO_UNSTABLE="${RUSTFLAGS:-}" ;; @@ -40,9 +45,9 @@ case "$OSTYPE" in PATH="$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$LLVM_ARCH_PATH/bin:$PATH" rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi -RUSTFLAGS="$RUSTFLAGS_WITH_TOKIO_UNSTABLE -C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --profile release-smaller --features uniffi --target x86_64-linux-android || exit 1 -RUSTFLAGS="$RUSTFLAGS_WITH_TOKIO_UNSTABLE -C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --profile release-smaller --features uniffi --target armv7-linux-androideabi || exit 1 -RUSTFLAGS="$RUSTFLAGS_WITH_TOKIO_UNSTABLE -C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --profile release-smaller --features uniffi --target aarch64-linux-android || exit 1 +RUSTFLAGS="$RUSTFLAGS_WITH_TOKIO_UNSTABLE -C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" --target x86_64-linux-android || exit 1 +RUSTFLAGS="$RUSTFLAGS_WITH_TOKIO_UNSTABLE -C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" --target armv7-linux-androideabi || exit 1 +RUSTFLAGS="$RUSTFLAGS_WITH_TOKIO_UNSTABLE -C link-args=-Wl,-z,max-page-size=16384,-z,common-page-size=16384" CFLAGS="-D__ANDROID_MIN_SDK_VERSION__=21" AR=llvm-ar CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" --target aarch64-linux-android || exit 1 $UNIFFI_BINDGEN_BIN generate bindings/ldk_node.udl --lib-file "$TARGET_DIR"/x86_64-linux-android/release-smaller/libldk_node.so --language kotlin --config uniffi-android.toml -o "$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/kotlin || exit 1 JNI_LIB_DIR="$BINDINGS_DIR"/"$PROJECT_DIR"/lib/src/main/jniLibs/ diff --git a/scripts/uniffi_bindgen_generate_python.sh b/scripts/uniffi_bindgen_generate_python.sh index 8792d2bc2..21f910fac 100755 --- a/scripts/uniffi_bindgen_generate_python.sh +++ b/scripts/uniffi_bindgen_generate_python.sh @@ -7,6 +7,11 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" BINDINGS_DIR="$REPO_ROOT/bindings/python/src/ldk_node" TARGET_DIR="${CARGO_TARGET_DIR:-$REPO_ROOT/target}" CARGO_BUILD_ARGS=() +UNIFFI_FEATURES="uniffi-default" + +if [[ -n "${LDK_NODE_EXTRA_FEATURES:-}" ]]; then + UNIFFI_FEATURES+=",$LDK_NODE_EXTRA_FEATURES" +fi case " ${RUSTFLAGS:-} " in *" --cfg tokio_unstable "*|*" --cfg=tokio_unstable "*) ;; @@ -41,7 +46,8 @@ esac cd "$REPO_ROOT" mkdir -p "$BINDINGS_DIR" -cargo build "${CARGO_BUILD_ARGS[@]}" --profile release-smaller --features uniffi +cargo build "${CARGO_BUILD_ARGS[@]}" --profile release-smaller --no-default-features \ + --features "$UNIFFI_FEATURES" cargo run --manifest-path bindings/uniffi-bindgen/Cargo.toml -- \ generate bindings/ldk_node.udl \ --lib-file "$DYNAMIC_LIB_PATH" \ diff --git a/scripts/uniffi_bindgen_generate_swift.sh b/scripts/uniffi_bindgen_generate_swift.sh index d69ac1fbe..f2100ae09 100755 --- a/scripts/uniffi_bindgen_generate_swift.sh +++ b/scripts/uniffi_bindgen_generate_swift.sh @@ -3,6 +3,11 @@ set -eox pipefail BINDINGS_DIR="./bindings/swift" UNIFFI_BINDGEN_BIN="cargo run --manifest-path bindings/uniffi-bindgen/Cargo.toml" +UNIFFI_FEATURES="uniffi-default" + +if [[ -n "${LDK_NODE_EXTRA_FEATURES:-}" ]]; then + UNIFFI_FEATURES+=",$LDK_NODE_EXTRA_FEATURES" +fi case " ${RUSTFLAGS:-} " in *" --cfg tokio_unstable "*|*" --cfg=tokio_unstable "*) ;; @@ -19,12 +24,12 @@ rustup target add aarch64-apple-ios-sim --toolchain stable rustup target add aarch64-apple-darwin x86_64-apple-darwin --toolchain stable # Build rust target libs -cargo build --profile release-smaller --features uniffi || exit 1 -cargo build --profile release-smaller --features uniffi --target x86_64-apple-darwin || exit 1 -cargo build --profile release-smaller --features uniffi --target aarch64-apple-darwin || exit 1 -cargo build --profile release-smaller --features uniffi --target x86_64-apple-ios || exit 1 -cargo build --profile release-smaller --features uniffi --target aarch64-apple-ios || exit 1 -cargo +stable build --release --features uniffi --target aarch64-apple-ios-sim || exit 1 +cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" || exit 1 +cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" --target x86_64-apple-darwin || exit 1 +cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" --target aarch64-apple-darwin || exit 1 +cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" --target x86_64-apple-ios || exit 1 +cargo build --profile release-smaller --no-default-features --features "$UNIFFI_FEATURES" --target aarch64-apple-ios || exit 1 +cargo +stable build --release --no-default-features --features "$UNIFFI_FEATURES" --target aarch64-apple-ios-sim || exit 1 # Combine ios-sim and apple-darwin (macos) libs for x86_64 and aarch64 (m1) mkdir -p target/lipo-ios-sim/release-smaller || exit 1 diff --git a/src/builder.rs b/src/builder.rs index f0f38783f..d5c9310e0 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,10 +5,13 @@ // 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(any(feature = "chain-esplora", feature = "storage-vss"))] use std::collections::HashMap; use std::convert::TryInto; use std::default::Default; +#[cfg(feature = "unified-payments")] use std::net::ToSocketAddrs; +#[cfg(feature = "storage-filesystem")] use std::path::PathBuf; use std::sync::{Arc, Mutex, Once, RwLock}; use std::time::SystemTime; @@ -20,7 +23,9 @@ use bitcoin::bip32::{ChildNumber, Xpriv}; use bitcoin::key::Secp256k1; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; +#[cfg(feature = "unified-payments")] use bitcoin_payment_instructions::dns_resolver::DNSHrnResolver; +#[cfg(feature = "unified-payments")] use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver; use lightning::chain::{chainmonitor, BlockLocator}; use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs}; @@ -42,14 +47,18 @@ use lightning::util::persist::{ }; use lightning::util::ser::ReadableArgs; use lightning::util::sweep::OutputSweeper; +#[cfg(feature = "unified-payments")] use lightning_dns_resolver::OMDomainResolver; +#[cfg(feature = "storage-vss")] use vss_client::headers::VssHeaderProvider; use crate::chain::ChainSource; +#[cfg(feature = "chain-bitcoind")] +use crate::config::BitcoindRestClientConfig; use crate::config::{ - default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole, - BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, HRNResolverConfig, - TorConfig, DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, + default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole, Config, + ElectrumSyncConfig, EsploraSyncConfig, HRNResolverConfig, TorConfig, + DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, DEFAULT_MAX_PROBE_AMOUNT_MSAT, DEFAULT_MIN_PROBE_AMOUNT_MSAT, PAYMENT_CACHE_CAPACITY, PAYMENT_CACHE_WARMUP_COUNT, }; @@ -59,12 +68,16 @@ use crate::entropy::NodeEntropy; use crate::event::EventQueue; use crate::fee_estimator::OnchainFeeEstimator; use crate::gossip::GossipSource; +#[cfg(feature = "storage-filesystem")] +use crate::io::fs_store::open_or_migrate_fs_store; +#[cfg(feature = "storage-sqlite")] use crate::io::sqlite_store::SqliteStore; use crate::io::utils::{ - open_or_migrate_fs_store, read_all_objects, read_event_queue, - read_external_pathfinding_scores_from_cache, read_n_objects, read_network_graph, - read_node_metrics, read_output_sweeper, read_peer_info, read_scorer, + read_all_objects, read_event_queue, read_external_pathfinding_scores_from_cache, + read_n_objects, read_network_graph, read_node_metrics, read_output_sweeper, read_peer_info, + read_scorer, }; +#[cfg(feature = "storage-vss")] use crate::io::vss_store::VssStoreBuilder; use crate::io::{ self, PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, @@ -76,6 +89,8 @@ use crate::lnurl_auth::LnurlAuth; use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger}; use crate::message_handler::NodeCustomMessageHandler; use crate::payment::asynchronous::om_mailbox::OnionMessageMailbox; +#[cfg(feature = "unified-payments")] +use crate::payment::HRNResolver; use crate::peer_store::PeerStore; use crate::probing::{ HighDegreeStrategy, Prober, ProbingConfig, ProbingStrategy, ProbingStrategyKind, @@ -85,8 +100,8 @@ use crate::runtime::{Runtime, RuntimeSpawner}; use crate::tx_broadcaster::TransactionBroadcaster; use crate::types::{ AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper, - GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger, PaymentStore, - PeerManager, PendingPaymentStore, + GossipSync, Graph, KeysManager, MessageRouter, OnionMessenger, PaymentStore, PeerManager, + PendingPaymentStore, }; use crate::wallet::persist::{read_address_pool, KVStoreWalletPersister}; use crate::wallet::Wallet; @@ -97,15 +112,15 @@ const PERSISTER_MAX_PENDING_UPDATES: u64 = 100; #[derive(Debug, Clone)] enum ChainDataSourceConfig { + #[cfg(feature = "chain-esplora")] Esplora { server_url: String, headers: HashMap, sync_config: Option, }, - Electrum { - server_url: String, - sync_config: Option, - }, + #[cfg(feature = "chain-electrum")] + Electrum { server_url: String, sync_config: Option }, + #[cfg(feature = "chain-bitcoind")] Bitcoind { rpc_host: String, rpc_port: u16, @@ -316,6 +331,9 @@ pub struct NodeBuilder { probing_config: Option, } +#[cfg(not(feature = "uniffi"))] +pub use self::NodeBuilder as Builder; + impl NodeBuilder { /// Creates a new builder instance with the default configuration. pub fn new() -> Self { @@ -359,6 +377,7 @@ impl NodeBuilder { /// /// If no `sync_config` is given, default values are used. See [`EsploraSyncConfig`] for more /// information. + #[cfg(feature = "chain-esplora")] pub fn set_chain_source_esplora( &mut self, server_url: String, sync_config: Option, ) -> &mut Self { @@ -377,6 +396,7 @@ impl NodeBuilder { /// /// If no `sync_config` is given, default values are used. See [`EsploraSyncConfig`] for more /// information. + #[cfg(feature = "chain-esplora")] pub fn set_chain_source_esplora_with_headers( &mut self, server_url: String, headers: HashMap, sync_config: Option, @@ -390,6 +410,7 @@ impl NodeBuilder { /// /// If no `sync_config` is given, default values are used. See [`ElectrumSyncConfig`] for more /// information. + #[cfg(feature = "chain-electrum")] pub fn set_chain_source_electrum( &mut self, server_url: String, sync_config: Option, ) -> &mut Self { @@ -410,6 +431,7 @@ impl NodeBuilder { /// startup, before wallet state exists. Existing wallets are not rewound. The height must /// be at or below the current tip. Passing `Some(0)` rescans from genesis; passing `None` /// checkpoints at the current tip. + #[cfg(feature = "chain-bitcoind")] pub fn set_chain_source_bitcoind_rpc( &mut self, rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, wallet_rescan_from_height: Option, @@ -438,6 +460,7 @@ impl NodeBuilder { /// startup, before wallet state exists. Existing wallets are not rewound. The height must /// be at or below the current tip. Passing `Some(0)` rescans from genesis; passing `None` /// checkpoints at the current tip. + #[cfg(feature = "chain-bitcoind")] pub fn set_chain_source_bitcoind_rest( &mut self, rest_host: String, rest_port: u16, rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, wallet_rescan_from_height: Option, @@ -665,6 +688,7 @@ impl NodeBuilder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. + #[cfg(feature = "storage-sqlite")] pub fn build(&self, node_entropy: NodeEntropy) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; let storage_dir_path = self.config.storage_dir_path.clone(); @@ -712,7 +736,7 @@ impl NodeBuilder { /// will be unencrypted. /// /// [PostgreSQL]: https://www.postgresql.org - #[cfg(feature = "postgres")] + #[cfg(feature = "storage-postgres")] pub fn build_with_postgres_store( &self, node_entropy: NodeEntropy, connection_string: String, db_name: Option, kv_table_name: Option, certificate_pem: Option, @@ -741,6 +765,7 @@ impl NodeBuilder { /// automatically migrated to the v2 format. /// /// [`FilesystemStoreV2`]: lightning_persister::fs_store::v2::FilesystemStoreV2 + #[cfg(feature = "storage-filesystem")] pub fn build_with_fs_store(&self, node_entropy: NodeEntropy) -> Result { let logger = setup_logger(&self.log_writer_config, &self.config)?; let runtime = self.setup_runtime(&logger)?; @@ -768,6 +793,7 @@ impl NodeBuilder { /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + #[cfg(feature = "storage-vss")] pub fn build_with_vss_store( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, fixed_headers: HashMap, @@ -804,6 +830,7 @@ impl NodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md + #[cfg(feature = "storage-vss")] pub fn build_with_vss_store_and_lnurl_auth( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, lnurl_auth_server_url: String, fixed_headers: HashMap, @@ -832,6 +859,7 @@ impl NodeBuilder { /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md /// [`FixedHeaders`]: vss_client::headers::FixedHeaders + #[cfg(feature = "storage-vss")] pub fn build_with_vss_store_and_fixed_headers( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, fixed_headers: HashMap, @@ -857,6 +885,7 @@ impl NodeBuilder { /// unrecoverable, i.e., if they remain unresolved after internal retries are exhausted. /// /// [VSS]: https://github.com/lightningdevkit/vss-server/blob/main/README.md + #[cfg(feature = "storage-vss")] pub fn build_with_vss_store_and_header_provider( &self, node_entropy: NodeEntropy, vss_url: String, store_id: String, header_provider: Arc, @@ -966,19 +995,29 @@ pub struct ArcedNodeBuilder { } #[cfg(feature = "uniffi")] -impl ArcedNodeBuilder { +pub use self::ArcedNodeBuilder as Builder; + +#[cfg(feature = "uniffi")] +#[uniffi::export] +impl Builder { /// Creates a new builder instance with the default configuration. + #[uniffi::constructor] pub fn new() -> Self { let inner = RwLock::new(NodeBuilder::new()); Self { inner } } /// Creates a new builder instance from an [`Config`]. + #[uniffi::constructor] pub fn from_config(config: Config) -> Self { let inner = RwLock::new(NodeBuilder::from_config(config)); Self { inner } } +} +#[cfg(all(feature = "uniffi", feature = "chain-esplora"))] +#[uniffi::export] +impl Builder { /// Configures the [`Node`] instance to source its chain data from the given Esplora server. /// /// If no `sync_config` is given, default values are used. See [`EsploraSyncConfig`] for more @@ -988,7 +1027,10 @@ impl ArcedNodeBuilder { ) { self.inner.write().expect("lock").set_chain_source_esplora(server_url, sync_config); } +} +#[cfg(all(feature = "uniffi", feature = "chain-esplora"))] +impl ArcedNodeBuilder { /// Configures the [`Node`] instance to source its chain data from the given Esplora server. /// /// The given `headers` will be included in all requests to the Esplora server, typically used for @@ -1006,7 +1048,11 @@ impl ArcedNodeBuilder { sync_config, ); } +} +#[cfg(all(feature = "uniffi", feature = "chain-electrum"))] +#[uniffi::export] +impl Builder { /// Configures the [`Node`] instance to source its chain data from the given Electrum server. /// /// If no `sync_config` is given, default values are used. See [`ElectrumSyncConfig`] for more @@ -1016,7 +1062,11 @@ impl ArcedNodeBuilder { ) { self.inner.write().expect("lock").set_chain_source_electrum(server_url, sync_config); } +} +#[cfg(all(feature = "uniffi", feature = "chain-bitcoind"))] +#[uniffi::export] +impl Builder { /// Configures the [`Node`] instance to connect to a Bitcoin Core node via RPC. /// /// This method establishes an RPC connection that enables all essential chain operations including @@ -1069,7 +1119,11 @@ impl ArcedNodeBuilder { wallet_rescan_from_height, ); } +} +#[cfg(feature = "uniffi")] +#[uniffi::export] +impl Builder { /// Configures the [`Node`] instance to source its gossip data from the Lightning peer-to-peer /// network. pub fn set_gossip_source_p2p(&self) { @@ -1113,7 +1167,10 @@ impl ArcedNodeBuilder { trust_peer_0conf, ); } +} +#[cfg(feature = "uniffi")] +impl ArcedNodeBuilder { /// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time /// channels to clients. /// @@ -1123,7 +1180,11 @@ impl ArcedNodeBuilder { pub fn enable_liquidity_provider(&self, lsps2_service_config: LSPS2ServiceConfig) { self.inner.write().expect("lock").enable_liquidity_provider(lsps2_service_config); } +} +#[cfg(feature = "uniffi")] +#[uniffi::export] +impl Builder { /// Sets the used storage directory path. pub fn set_storage_dir_path(&self, storage_dir_path: String) { self.inner.write().expect("lock").set_storage_dir_path(storage_dir_path); @@ -1217,13 +1278,21 @@ impl ArcedNodeBuilder { pub fn set_probing_config(&self, config: Arc) { self.inner.write().expect("lock").set_probing_config((*config).clone()); } +} +#[cfg(all(feature = "uniffi", feature = "storage-sqlite"))] +#[uniffi::export] +impl Builder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. pub fn build(&self, node_entropy: Arc) -> Result, BuildError> { self.inner.read().expect("lock").build(*node_entropy).map(Arc::new) } +} +#[cfg(all(feature = "uniffi", feature = "storage-postgres"))] +#[uniffi::export] +impl Builder { /// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options /// previously configured. /// @@ -1254,7 +1323,6 @@ impl ArcedNodeBuilder { /// will be unencrypted. /// /// [PostgreSQL]: https://www.postgresql.org - #[cfg(feature = "postgres")] pub fn build_with_postgres_store( &self, node_entropy: Arc, connection_string: String, db_name: Option, kv_table_name: Option, certificate_pem: Option, @@ -1271,19 +1339,11 @@ impl ArcedNodeBuilder { ) .map(Arc::new) } +} - /// Builds a [`Node`] instance with a [PostgreSQL] backend and according to the options - /// previously configured. - /// - /// This requires the `postgres` crate feature. - #[cfg(not(feature = "postgres"))] - pub fn build_with_postgres_store( - &self, _node_entropy: Arc, _connection_string: String, - _db_name: Option, _kv_table_name: Option, _certificate_pem: Option, - ) -> Result, BuildError> { - Err(BuildError::KVStoreSetupFailed) - } - +#[cfg(all(feature = "uniffi", feature = "storage-filesystem"))] +#[uniffi::export] +impl Builder { /// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options /// previously configured. pub fn build_with_fs_store( @@ -1291,7 +1351,11 @@ impl ArcedNodeBuilder { ) -> Result, BuildError> { self.inner.read().expect("lock").build_with_fs_store(*node_entropy).map(Arc::new) } +} +#[cfg(all(feature = "uniffi", feature = "storage-vss"))] +#[uniffi::export] +impl Builder { /// Builds a [`Node`] instance with a [VSS] backend and according to the options /// previously configured. /// @@ -1404,7 +1468,10 @@ impl ArcedNodeBuilder { .build_with_vss_store_and_header_provider(*node_entropy, vss_url, store_id, adapter) .map(Arc::new) } +} +#[cfg(feature = "uniffi")] +impl ArcedNodeBuilder { /// Builds a [`Node`] instance according to the options previously configured. // Note that the generics here don't actually work for Uniffi, but we don't currently expose // this so its not needed. @@ -1510,6 +1577,7 @@ fn build_with_store_internal( }; let (chain_source, chain_tip_opt) = match chain_data_source_config { + #[cfg(feature = "chain-esplora")] Some(ChainDataSourceConfig::Esplora { server_url, headers, sync_config }) => { let sync_config = sync_config.unwrap_or(EsploraSyncConfig::default()); ChainSource::new_esplora( @@ -1525,6 +1593,7 @@ fn build_with_store_internal( ) .map_err(|()| BuildError::ChainSourceSetupFailed)? }, + #[cfg(feature = "chain-electrum")] Some(ChainDataSourceConfig::Electrum { server_url, sync_config }) => { let sync_config = sync_config.unwrap_or(ElectrumSyncConfig::default()); ChainSource::new_electrum( @@ -1538,6 +1607,7 @@ fn build_with_store_internal( Arc::clone(&node_metrics), ) }, + #[cfg(feature = "chain-bitcoind")] Some(ChainDataSourceConfig::Bitcoind { rpc_host, rpc_port, @@ -1579,6 +1649,7 @@ fn build_with_store_internal( }), }, + #[cfg(feature = "chain-esplora")] None => { // Default to Esplora client. let server_url = DEFAULT_ESPLORA_SERVER_URL.to_string(); @@ -1596,13 +1667,24 @@ fn build_with_store_internal( ) .map_err(|()| BuildError::ChainSourceSetupFailed)? }, + #[cfg(not(feature = "chain-esplora"))] + None => return Err(BuildError::ChainSourceSetupFailed), }; let chain_source = Arc::new(chain_source); - let wallet_rescan_from_height = match chain_data_source_config { - Some(ChainDataSourceConfig::Bitcoind { wallet_rescan_from_height, .. }) => { - *wallet_rescan_from_height - }, - _ => None, + let wallet_rescan_from_height = { + #[cfg(feature = "chain-bitcoind")] + { + match chain_data_source_config { + Some(ChainDataSourceConfig::Bitcoind { wallet_rescan_from_height, .. }) => { + *wallet_rescan_from_height + }, + _ => None, + } + } + #[cfg(not(feature = "chain-bitcoind"))] + { + None:: + } }; // Initialize the on-chain wallet and chain access @@ -1662,10 +1744,13 @@ fn build_with_store_internal( // Abort cleanly instead so the misconfiguration surfaces on the first startup. // Esplora/Electrum backends currently never return a tip at build time, so they // retain their existing behavior. - if wallet_rescan_from_height.is_none() - && chain_tip_opt.is_none() - && matches!(chain_data_source_config, Some(ChainDataSourceConfig::Bitcoind { .. })) - { + #[cfg(feature = "chain-bitcoind")] + let uses_bitcoind = + matches!(chain_data_source_config, Some(ChainDataSourceConfig::Bitcoind { .. })); + #[cfg(not(feature = "chain-bitcoind"))] + let uses_bitcoind = false; + + if wallet_rescan_from_height.is_none() && chain_tip_opt.is_none() && uses_bitcoind { log_error!( logger, "Failed to determine chain tip on first startup. Aborting to avoid pinning the wallet birthday to genesis." @@ -1690,6 +1775,7 @@ fn build_with_store_internal( // the checkpoint. Otherwise, use the current chain tip to avoid any rescan. let checkpoint_block = match wallet_rescan_from_height { None => chain_tip_opt, + #[cfg(feature = "chain-bitcoind")] Some(height) => { if let Some(chain_tip) = chain_tip_opt { if height > chain_tip.height { @@ -1730,6 +1816,8 @@ fn build_with_store_internal( }, } }, + #[cfg(not(feature = "chain-bitcoind"))] + Some(_) => unreachable!("wallet rescans require the chain-bitcoind feature"), }; if let Some(best_block) = checkpoint_block { @@ -2043,11 +2131,15 @@ fn build_with_store_internal( })?; } + #[cfg(feature = "unified-payments")] let hrn_resolver; + #[cfg(feature = "unified-payments")] let mut blip32_resolver = None; + #[cfg(feature = "unified-payments")] let runtime_handle = runtime.handle(); + #[cfg(feature = "unified-payments")] let om_resolver: Arc = match &config .hrn_config .resolution_config @@ -2093,6 +2185,9 @@ fn build_with_store_internal( } }, }; + #[cfg(not(feature = "unified-payments"))] + let om_resolver: Arc = + Arc::new(IgnoringMessageHandler {}); // Initialize the PeerManager let onion_messenger: Arc = @@ -2225,6 +2320,7 @@ fn build_with_store_internal( Arc::clone(&keys_manager), )); + #[cfg(feature = "unified-payments")] if let Some(res) = blip32_resolver { let pm_weak = Arc::downgrade(&peer_manager); res.register_post_queue_action(Box::new(move || { @@ -2391,6 +2487,7 @@ fn build_with_store_internal( node_metrics, om_mailbox, async_payments_role, + #[cfg(feature = "unified-payments")] hrn_resolver, prober, #[cfg(cycle_tests)] diff --git a/src/chain/mod.rs b/src/chain/mod.rs index ba7f798fa..f01c1c8cb 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -5,24 +5,35 @@ // 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(feature = "chain-bitcoind")] pub(crate) mod bitcoind; +#[cfg(feature = "chain-electrum")] mod electrum; +#[cfg(feature = "chain-esplora")] mod esplora; -use std::collections::{HashMap, HashSet}; +#[cfg(feature = "chain-esplora")] +use std::collections::HashMap; +use std::collections::HashSet; use std::sync::{Arc, Mutex}; use std::time::Duration; use bitcoin::{Script, Txid}; use lightning::chain::{BlockLocator, Filter}; +#[cfg(feature = "chain-bitcoind")] use crate::chain::bitcoind::{BitcoindChainSource, UtxoSourceClient}; +#[cfg(feature = "chain-electrum")] use crate::chain::electrum::ElectrumChainSource; +#[cfg(feature = "chain-esplora")] use crate::chain::esplora::EsploraChainSource; -use crate::config::{ - BackgroundSyncConfig, BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, - WALLET_SYNC_INTERVAL_MINIMUM_SECS, -}; +#[cfg(feature = "chain-bitcoind")] +use crate::config::BitcoindRestClientConfig; +#[cfg(feature = "chain-electrum")] +use crate::config::ElectrumSyncConfig; +#[cfg(feature = "chain-esplora")] +use crate::config::EsploraSyncConfig; +use crate::config::{BackgroundSyncConfig, Config, WALLET_SYNC_INTERVAL_MINIMUM_SECS}; use crate::fee_estimator::OnchainFeeEstimator; use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; use crate::runtime::Runtime; @@ -140,12 +151,16 @@ pub(crate) struct ChainSource { } enum ChainSourceKind { + #[cfg(feature = "chain-esplora")] Esplora(EsploraChainSource), + #[cfg(feature = "chain-electrum")] Electrum(ElectrumChainSource), + #[cfg(feature = "chain-bitcoind")] Bitcoind(BitcoindChainSource), } impl ChainSource { + #[cfg(feature = "chain-esplora")] pub(crate) fn new_esplora( server_url: String, headers: HashMap, sync_config: EsploraSyncConfig, fee_estimator: Arc, tx_broadcaster: Arc, @@ -167,6 +182,7 @@ impl ChainSource { Ok((Self { kind, registered_txids, tx_broadcaster, logger }, None)) } + #[cfg(feature = "chain-electrum")] pub(crate) fn new_electrum( server_url: String, sync_config: ElectrumSyncConfig, fee_estimator: Arc, tx_broadcaster: Arc, @@ -187,6 +203,7 @@ impl ChainSource { (Self { kind, registered_txids, tx_broadcaster, logger }, None) } + #[cfg(feature = "chain-bitcoind")] pub(crate) async fn new_bitcoind_rpc( rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, fee_estimator: Arc, tx_broadcaster: Arc, @@ -210,6 +227,7 @@ impl ChainSource { (Self { kind, registered_txids, tx_broadcaster, logger }, best_block) } + #[cfg(feature = "chain-bitcoind")] pub(crate) async fn new_bitcoind_rest( rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String, fee_estimator: Arc, tx_broadcaster: Arc, @@ -236,9 +254,8 @@ impl ChainSource { pub(crate) fn start(&self, runtime: Arc) -> Result<(), Error> { match &self.kind { - ChainSourceKind::Electrum(electrum_chain_source) => { - electrum_chain_source.start(runtime)? - }, + #[cfg(feature = "chain-electrum")] + ChainSourceKind::Electrum(electrum_chain_source) => electrum_chain_source.start(runtime)?, _ => { // Nothing to do for other chain sources. }, @@ -248,6 +265,7 @@ impl ChainSource { pub(crate) fn stop(&self) { match &self.kind { + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => electrum_chain_source.stop(), _ => { // Nothing to do for other chain sources. @@ -257,9 +275,8 @@ impl ChainSource { pub(crate) fn begin_shutdown(&self) { match &self.kind { - ChainSourceKind::Electrum(electrum_chain_source) => { - electrum_chain_source.begin_shutdown() - }, + #[cfg(feature = "chain-electrum")] + ChainSourceKind::Electrum(electrum_chain_source) => electrum_chain_source.begin_shutdown(), _ => { // Other chain sources don't leave synchronous callbacks running after their // driving future is cancelled. @@ -267,6 +284,7 @@ impl ChainSource { } } + #[cfg(feature = "chain-bitcoind")] pub(crate) fn as_utxo_source(&self) -> Option { match &self.kind { ChainSourceKind::Bitcoind(bitcoind_chain_source) => { @@ -282,8 +300,11 @@ impl ChainSource { pub(crate) fn is_transaction_based(&self) -> bool { match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(_) => true, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum { .. } => true, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind { .. } => false, } } @@ -294,6 +315,7 @@ impl ChainSource { output_sweeper: Arc, ) { match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(esplora_chain_source) => { if let Some(background_sync_config) = esplora_chain_source.sync_config.background_sync_config.as_ref() @@ -317,6 +339,7 @@ impl ChainSource { return; } }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { if let Some(background_sync_config) = electrum_chain_source.sync_config.background_sync_config.as_ref() @@ -340,6 +363,7 @@ impl ChainSource { return; } }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind(bitcoind_chain_source) => { bitcoind_chain_source .continuously_sync_wallets( @@ -419,12 +443,15 @@ impl ChainSource { &self, onchain_wallet: Arc, ) -> Result<(), Error> { match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(esplora_chain_source) => { esplora_chain_source.sync_onchain_wallet(onchain_wallet).await }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { electrum_chain_source.sync_onchain_wallet(onchain_wallet).await }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind { .. } => { // In BitcoindRpc mode we sync lightning and onchain wallet in one go via // `ChainPoller`. So nothing to do here. @@ -440,16 +467,19 @@ impl ChainSource { output_sweeper: Arc, ) -> Result<(), Error> { match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(esplora_chain_source) => { esplora_chain_source .sync_lightning_wallet(channel_manager, chain_monitor, output_sweeper) .await }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { electrum_chain_source .sync_lightning_wallet(channel_manager, chain_monitor, output_sweeper) .await }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind { .. } => { // In BitcoindRpc mode we sync lightning and onchain wallet in one go via // `ChainPoller`. So nothing to do here. @@ -462,17 +492,23 @@ impl ChainSource { &self, onchain_wallet: Arc, channel_manager: Arc, chain_monitor: Arc, output_sweeper: Arc, ) -> Result<(), Error> { + #[cfg(not(feature = "chain-bitcoind"))] + let _ = (&onchain_wallet, &channel_manager, &chain_monitor, &output_sweeper); + match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora { .. } => { // In Esplora mode we sync lightning and onchain wallets via // `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here. unreachable!("Listeners will be synced via transction-based syncing") }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum { .. } => { // In Electrum mode we sync lightning and onchain wallets via // `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here. unreachable!("Listeners will be synced via transction-based syncing") }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind(bitcoind_chain_source) => { bitcoind_chain_source .poll_and_update_listeners( @@ -488,12 +524,15 @@ impl ChainSource { pub(crate) async fn update_fee_rate_estimates(&self) -> Result<(), Error> { match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(esplora_chain_source) => { esplora_chain_source.update_fee_rate_estimates().await }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { electrum_chain_source.update_fee_rate_estimates().await }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind(bitcoind_chain_source) => { bitcoind_chain_source.update_fee_rate_estimates().await }, @@ -508,12 +547,15 @@ impl ChainSource { } match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(esplora_chain_source) => { esplora_chain_source.validate_zero_fee_commitments_support().await }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { electrum_chain_source.validate_zero_fee_commitments_support().await }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind(bitcoind_chain_source) => { bitcoind_chain_source.validate_zero_fee_commitments_support().await }, @@ -551,12 +593,15 @@ impl ChainSource { }; let package = package.into_sorted_transactions(); match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(esplora_chain_source) => { esplora_chain_source.process_transaction_broadcast(package).await }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { electrum_chain_source.process_transaction_broadcast(package).await }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind(bitcoind_chain_source) => { bitcoind_chain_source.process_transaction_broadcast(package).await }, @@ -571,23 +616,27 @@ impl Filter for ChainSource { fn register_tx(&self, txid: &Txid, script_pubkey: &Script) { self.registered_txids.lock().expect("lock").insert(*txid); match &self.kind { + #[cfg(feature = "chain-esplora")] ChainSourceKind::Esplora(esplora_chain_source) => { esplora_chain_source.register_tx(txid, script_pubkey) }, + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { electrum_chain_source.register_tx(txid, script_pubkey) }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind { .. } => (), } } fn register_output(&self, output: lightning::chain::WatchedOutput) { match &self.kind { - ChainSourceKind::Esplora(esplora_chain_source) => { - esplora_chain_source.register_output(output) - }, + #[cfg(feature = "chain-esplora")] + ChainSourceKind::Esplora(esplora_chain_source) => esplora_chain_source.register_output(output), + #[cfg(feature = "chain-electrum")] ChainSourceKind::Electrum(electrum_chain_source) => { electrum_chain_source.register_output(output) }, + #[cfg(feature = "chain-bitcoind")] ChainSourceKind::Bitcoind { .. } => (), } } diff --git a/src/error.rs b/src/error.rs index 107b8fe1b..485f944c2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -279,6 +279,7 @@ impl From for Error { } } +#[cfg(any(feature = "chain-esplora", feature = "chain-electrum"))] impl From for Error { fn from(_e: lightning_transaction_sync::TxSyncError) -> Self { Self::TxSyncFailed diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 4972c636d..d213f9d51 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -53,6 +53,7 @@ use lightning_types::features::{ }; pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; pub use lightning_types::string::UntrustedString; +#[cfg(feature = "storage-vss")] use vss_client::headers::{ VssHeaderProvider as VssClientHeaderProvider, VssHeaderProviderError as VssClientHeaderProviderError, @@ -100,6 +101,7 @@ impl std::fmt::Display for VssHeaderProviderError { impl std::error::Error for VssHeaderProviderError {} +#[cfg(feature = "storage-vss")] impl From for VssClientHeaderProviderError { fn from(e: VssHeaderProviderError) -> Self { match e { @@ -130,16 +132,19 @@ pub trait VssHeaderProvider: Send + Sync { /// An adapter that wraps the local [`VssHeaderProvider`] and implements the upstream /// [`VssClientHeaderProvider`] trait. +#[cfg(feature = "storage-vss")] pub(crate) struct VssHeaderProviderAdapter { inner: Arc, } +#[cfg(feature = "storage-vss")] impl VssHeaderProviderAdapter { pub(crate) fn new(inner: Arc) -> Self { Self { inner } } } +#[cfg(feature = "storage-vss")] #[async_trait::async_trait] impl VssClientHeaderProvider for VssHeaderProviderAdapter { async fn get_headers( @@ -150,8 +155,7 @@ impl VssClientHeaderProvider for VssHeaderProviderAdapter { } use crate::builder::sanitize_alias; -pub use crate::config::{default_config, ElectrumSyncConfig, EsploraSyncConfig, TorConfig}; -pub use crate::entropy::NodeEntropy; +pub use crate::config::default_config; use crate::error::Error; pub use crate::liquidity::LSPS1OrderStatus; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; diff --git a/src/gossip.rs b/src/gossip.rs index 4ef280273..41206dfa8 100644 --- a/src/gossip.rs +++ b/src/gossip.rs @@ -8,13 +8,16 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; +#[cfg(feature = "chain-bitcoind")] use lightning_block_sync::gossip::GossipVerifier; use crate::chain::ChainSource; use crate::config::{RGS_SNAPSHOT_MAX_SIZE, RGS_SYNC_TIMEOUT_SECS}; use crate::logger::{log_error, log_trace, LdkLogger, Logger}; -use crate::runtime::{Runtime, RuntimeSpawner}; -use crate::types::{GossipSync, Graph, P2PGossipSync, RapidGossipSync}; +use crate::runtime::Runtime; +#[cfg(feature = "chain-bitcoind")] +use crate::runtime::RuntimeSpawner; +use crate::types::{GossipSync, Graph, P2PGossipSync, RapidGossipSync, UtxoLookup}; use crate::Error; pub(crate) enum GossipSource { @@ -34,9 +37,16 @@ impl GossipSource { network_graph: Arc, chain_source: Arc, runtime: Arc, logger: Arc, ) -> Self { + #[cfg(feature = "chain-bitcoind")] let verifier = chain_source.as_utxo_source().map(|utxo_source| { Arc::new(GossipVerifier::new(Arc::new(utxo_source), RuntimeSpawner::new(runtime))) + as Arc }); + #[cfg(not(feature = "chain-bitcoind"))] + let verifier: Option> = { + let _ = (chain_source, runtime); + None + }; let gossip_sync = Arc::new(P2PGossipSync::new(network_graph, verifier, logger)); Self::P2PNetwork { gossip_sync } diff --git a/src/io/fs_store.rs b/src/io/fs_store.rs new file mode 100644 index 000000000..f0e28f878 --- /dev/null +++ b/src/io/fs_store.rs @@ -0,0 +1,280 @@ +// 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. + +use std::fs; +use std::path::{Path, PathBuf}; + +use lightning::util::persist::migrate_kv_store_data_async; +use lightning_persister::fs_store::v1::FilesystemStore; +use lightning_persister::fs_store::v2::{FilesystemStoreV2, FilesystemStoreV2Error}; + +use crate::BuildError; + +/// Opens a [`FilesystemStoreV2`], automatically migrating from v1 format if necessary. +/// +/// If the directory contains v1 data (files at the top level), the data is migrated to v2 format +/// in a temporary directory, the original is renamed to `fs_store_v1_backup`, and the migrated +/// directory is moved into place. +pub(crate) async fn open_or_migrate_fs_store( + storage_dir_path: PathBuf, +) -> Result { + let parent_dir = storage_dir_path.parent().ok_or(BuildError::StoragePathAccessFailed)?; + fs::create_dir_all(parent_dir).map_err(|_| BuildError::StoragePathAccessFailed)?; + recover_incomplete_fs_store_migration(&storage_dir_path)?; + if !storage_dir_path.exists() { + fs::create_dir_all(&storage_dir_path).map_err(|_| BuildError::StoragePathAccessFailed)?; + } + + match FilesystemStoreV2::new(storage_dir_path.clone()) { + Ok(store) => Ok(store), + Err(FilesystemStoreV2Error::V1DataDetected(_)) => { + // The directory contains v1 data, migrate to v2. + let v1_store = FilesystemStore::new(storage_dir_path.clone()); + + let v2_dir = fs_store_sibling_path(&storage_dir_path, "fs_store_v2_migrating"); + fs::create_dir_all(&v2_dir).map_err(|_| BuildError::StoragePathAccessFailed)?; + let v2_store = FilesystemStoreV2::new(v2_dir.clone()) + .map_err(|_| BuildError::KVStoreSetupFailed)?; + + migrate_kv_store_data_async(&v1_store, &v2_store) + .await + .map_err(|_| BuildError::KVStoreSetupFailed)?; + + // Swap directories: rename v1 out of the way, move v2 into place. + let backup_dir = fs_store_sibling_path(&storage_dir_path, "fs_store_v1_backup"); + fs::rename(&storage_dir_path, &backup_dir) + .map_err(|_| BuildError::KVStoreSetupFailed)?; + fs::rename(&v2_dir, &storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?; + + // fsync the renames + fs::File::open(parent_dir) + .and_then(|f| f.sync_all()) + .map_err(|_| BuildError::KVStoreSetupFailed)?; + + FilesystemStoreV2::new(storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed) + }, + Err(_) => Err(BuildError::KVStoreSetupFailed), + } +} + +fn fs_store_sibling_path(storage_dir_path: &Path, file_name: &str) -> PathBuf { + let mut sibling_path = storage_dir_path.to_path_buf(); + sibling_path.set_file_name(file_name); + sibling_path +} + +fn recover_incomplete_fs_store_migration(storage_dir_path: &Path) -> Result<(), BuildError> { + let v2_dir = fs_store_sibling_path(storage_dir_path, "fs_store_v2_migrating"); + let backup_dir = fs_store_sibling_path(storage_dir_path, "fs_store_v1_backup"); + + if storage_dir_path.exists() { + if v2_dir.exists() { + // The original store is still in place, so a temp migration dir is from a crash before + // the rename step and can be discarded before retrying migration. + fs::remove_dir_all(&v2_dir).map_err(|_| BuildError::KVStoreSetupFailed)?; + } + return Ok(()); + } + + if backup_dir.exists() { + if v2_dir.exists() { + // Prefer retrying from the v1 backup instead of deciding here whether the temp v2 dir is + // usable. open_or_migrate_fs_store owns the actual v1-to-v2 migration. + fs::remove_dir_all(&v2_dir).map_err(|_| BuildError::KVStoreSetupFailed)?; + } + // The crash happened after moving v1 aside; restore it so normal startup can migrate it. + fs::rename(&backup_dir, storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?; + return Ok(()); + } + + if v2_dir.exists() { + // There is no v1 backup to retry from. Move the temp dir into place and let + // open_or_migrate_fs_store decide whether it is a valid v2 store. + fs::rename(&v2_dir, storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::path::{Path, PathBuf}; + + use lightning::util::persist::{migrate_kv_store_data_async, KVStore}; + use lightning_persister::fs_store::v1::FilesystemStore; + use lightning_persister::fs_store::v2::FilesystemStoreV2; + + use super::open_or_migrate_fs_store; + use crate::io::test_utils::random_storage_path; + + const TEST_PRIMARY_NAMESPACE: &str = "test_primary_namespace"; + const TEST_SECONDARY_NAMESPACE: &str = "test_secondary_namespace"; + const TEST_KEY: &str = "test_key"; + const TEST_VALUE: &[u8] = b"test_value"; + + #[tokio::test] + async fn fs_store_migration_recovers_before_v1_backup_rename() { + let fs_store_path = fs_store_path(); + let v1_store = write_v1_test_data(&fs_store_path).await; + let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); + let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); + migrate_kv_store_data_async(&v1_store, &v2_store).await.unwrap(); + + let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); + assert_eq!( + KVStore::read( + &migrated_store, + TEST_PRIMARY_NAMESPACE, + TEST_SECONDARY_NAMESPACE, + TEST_KEY + ) + .await + .unwrap(), + TEST_VALUE + ); + assert!(fs_store_path.exists()); + assert!(!v2_migrating_path.exists()); + } + + #[tokio::test] + async fn fs_store_migration_recovers_after_v1_backup_rename() { + let fs_store_path = fs_store_path(); + let v1_store = write_v1_test_data(&fs_store_path).await; + let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); + let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); + migrate_kv_store_data_async(&v1_store, &v2_store).await.unwrap(); + + let backup_path = sibling_path(&fs_store_path, "fs_store_v1_backup"); + fs::rename(&fs_store_path, backup_path).unwrap(); + + let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); + assert_eq!( + KVStore::read( + &migrated_store, + TEST_PRIMARY_NAMESPACE, + TEST_SECONDARY_NAMESPACE, + TEST_KEY + ) + .await + .unwrap(), + TEST_VALUE + ); + assert!(fs_store_path.exists()); + assert!(!v2_migrating_path.exists()); + } + + #[tokio::test] + async fn fs_store_migration_recovers_after_v2_rename() { + let fs_store_path = fs_store_path(); + let v1_store = write_v1_test_data(&fs_store_path).await; + let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); + let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); + migrate_kv_store_data_async(&v1_store, &v2_store).await.unwrap(); + + let backup_path = sibling_path(&fs_store_path, "fs_store_v1_backup"); + fs::rename(&fs_store_path, &backup_path).unwrap(); + fs::rename(&v2_migrating_path, &fs_store_path).unwrap(); + + let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); + assert_eq!( + KVStore::read( + &migrated_store, + TEST_PRIMARY_NAMESPACE, + TEST_SECONDARY_NAMESPACE, + TEST_KEY + ) + .await + .unwrap(), + TEST_VALUE + ); + assert!(fs_store_path.exists()); + assert!(backup_path.exists()); + assert!(!v2_migrating_path.exists()); + } + + #[tokio::test] + async fn fs_store_migration_recovers_backup_without_migrating_dir() { + let fs_store_path = fs_store_path(); + write_v1_test_data(&fs_store_path).await; + + let backup_path = sibling_path(&fs_store_path, "fs_store_v1_backup"); + fs::rename(&fs_store_path, backup_path).unwrap(); + + let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); + assert_eq!( + KVStore::read( + &migrated_store, + TEST_PRIMARY_NAMESPACE, + TEST_SECONDARY_NAMESPACE, + TEST_KEY + ) + .await + .unwrap(), + TEST_VALUE + ); + assert!(fs_store_path.exists()); + assert!(!sibling_path(&fs_store_path, "fs_store_v1_backup").exists()); + } + + #[tokio::test] + async fn fs_store_migration_recovers_unexpected_migrating_dir_without_backup() { + let fs_store_path = fs_store_path(); + let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); + let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); + KVStore::write( + &v2_store, + TEST_PRIMARY_NAMESPACE, + TEST_SECONDARY_NAMESPACE, + TEST_KEY, + TEST_VALUE.to_vec(), + ) + .await + .unwrap(); + + let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); + assert_eq!( + KVStore::read( + &migrated_store, + TEST_PRIMARY_NAMESPACE, + TEST_SECONDARY_NAMESPACE, + TEST_KEY + ) + .await + .unwrap(), + TEST_VALUE + ); + assert!(fs_store_path.exists()); + assert!(!v2_migrating_path.exists()); + } + + fn fs_store_path() -> PathBuf { + let mut fs_store_path = random_storage_path(); + fs_store_path.push("fs_store"); + fs_store_path + } + + fn sibling_path(path: &Path, file_name: &str) -> PathBuf { + let mut sibling_path = path.to_path_buf(); + sibling_path.set_file_name(file_name); + sibling_path + } + + async fn write_v1_test_data(fs_store_path: &Path) -> FilesystemStore { + let v1_store = FilesystemStore::new(fs_store_path.to_path_buf()); + KVStore::write( + &v1_store, + TEST_PRIMARY_NAMESPACE, + TEST_SECONDARY_NAMESPACE, + TEST_KEY, + TEST_VALUE.to_vec(), + ) + .await + .unwrap(); + v1_store + } +} diff --git a/src/io/mod.rs b/src/io/mod.rs index c70c68d96..c11475c43 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -7,12 +7,16 @@ //! Objects and traits for data persistence. -#[cfg(feature = "postgres")] +#[cfg(feature = "storage-filesystem")] +pub(crate) mod fs_store; +#[cfg(feature = "storage-postgres")] pub mod postgres_store; +#[cfg(feature = "storage-sqlite")] pub mod sqlite_store; #[cfg(test)] pub(crate) mod test_utils; pub(crate) mod utils; +#[cfg(feature = "storage-vss")] pub mod vss_store; /// The event queue will be persisted under this key. diff --git a/src/io/utils.rs b/src/io/utils.rs index b9255120f..30fc0c62d 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -11,7 +11,7 @@ use std::num::NonZeroUsize; use std::ops::Deref; #[cfg(unix)] use std::os::unix::fs::OpenOptionsExt; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; use bdk_chain::indexer::keychain_txout::ChangeSet as BdkIndexerChangeSet; @@ -27,16 +27,14 @@ use lightning::routing::scoring::{ ChannelLiquidities, ProbabilisticScorer, ProbabilisticScoringDecayParameters, }; use lightning::util::persist::{ - migrate_kv_store_data_async, KVStore, PageToken, PaginatedKVStore, - KVSTORE_NAMESPACE_KEY_ALPHABET, KVSTORE_NAMESPACE_KEY_MAX_LEN, NETWORK_GRAPH_PERSISTENCE_KEY, + KVStore, PageToken, PaginatedKVStore, KVSTORE_NAMESPACE_KEY_ALPHABET, + KVSTORE_NAMESPACE_KEY_MAX_LEN, NETWORK_GRAPH_PERSISTENCE_KEY, NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE, OUTPUT_SWEEPER_PERSISTENCE_KEY, OUTPUT_SWEEPER_PERSISTENCE_PRIMARY_NAMESPACE, OUTPUT_SWEEPER_PERSISTENCE_SECONDARY_NAMESPACE, SCORER_PERSISTENCE_KEY, SCORER_PERSISTENCE_PRIMARY_NAMESPACE, SCORER_PERSISTENCE_SECONDARY_NAMESPACE, }; use lightning::util::ser::{Readable, ReadableArgs, Writeable}; -use lightning_persister::fs_store::v1::FilesystemStore; -use lightning_persister::fs_store::v2::{FilesystemStoreV2, FilesystemStoreV2Error}; use lightning_types::string::PrintableString; use super::*; @@ -50,7 +48,7 @@ use crate::logger::{log_error, LdkLogger, Logger}; use crate::peer_store::PeerStore; use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper}; use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper}; -use crate::{BuildError, Error, EventQueue, NodeMetrics, PersistedNodeMetrics}; +use crate::{Error, EventQueue, NodeMetrics, PersistedNodeMetrics}; pub const EXTERNAL_PATHFINDING_SCORES_CACHE_KEY: &str = "external_pathfinding_scores_cache"; @@ -761,109 +759,10 @@ pub(crate) async fn read_bdk_wallet_change_set( Ok(Some(change_set)) } -/// Opens a [`FilesystemStoreV2`], automatically migrating from v1 format if necessary. -/// -/// If the directory contains v1 data (files at the top level), the data is migrated to v2 format -/// in a temporary directory, the original is renamed to `fs_store_v1_backup`, and the migrated -/// directory is moved into place. -pub(crate) async fn open_or_migrate_fs_store( - storage_dir_path: PathBuf, -) -> Result { - let parent_dir = storage_dir_path.parent().ok_or(BuildError::StoragePathAccessFailed)?; - fs::create_dir_all(parent_dir).map_err(|_| BuildError::StoragePathAccessFailed)?; - recover_incomplete_fs_store_migration(&storage_dir_path)?; - if !storage_dir_path.exists() { - fs::create_dir_all(storage_dir_path.clone()) - .map_err(|_| BuildError::StoragePathAccessFailed)?; - } - - match FilesystemStoreV2::new(storage_dir_path.clone()) { - Ok(store) => Ok(store), - Err(FilesystemStoreV2Error::V1DataDetected(_)) => { - // The directory contains v1 data, migrate to v2. - let v1_store = FilesystemStore::new(storage_dir_path.clone()); - - let v2_dir = fs_store_sibling_path(&storage_dir_path, "fs_store_v2_migrating"); - fs::create_dir_all(v2_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?; - let v2_store = FilesystemStoreV2::new(v2_dir.clone()) - .map_err(|_| BuildError::KVStoreSetupFailed)?; - - migrate_kv_store_data_async(&v1_store, &v2_store) - .await - .map_err(|_| BuildError::KVStoreSetupFailed)?; - - // Swap directories: rename v1 out of the way, move v2 into place. - let backup_dir = fs_store_sibling_path(&storage_dir_path, "fs_store_v1_backup"); - fs::rename(&storage_dir_path, &backup_dir) - .map_err(|_| BuildError::KVStoreSetupFailed)?; - fs::rename(&v2_dir, &storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?; - - // fsync the renames - fs::File::open(parent_dir) - .and_then(|f| f.sync_all()) - .map_err(|_| BuildError::KVStoreSetupFailed)?; - - FilesystemStoreV2::new(storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed) - }, - Err(_) => Err(BuildError::KVStoreSetupFailed), - } -} - -fn fs_store_sibling_path(storage_dir_path: &Path, file_name: &str) -> PathBuf { - let mut sibling_path = storage_dir_path.to_path_buf(); - sibling_path.set_file_name(file_name); - sibling_path -} - -fn recover_incomplete_fs_store_migration(storage_dir_path: &Path) -> Result<(), BuildError> { - let v2_dir = fs_store_sibling_path(storage_dir_path, "fs_store_v2_migrating"); - let backup_dir = fs_store_sibling_path(storage_dir_path, "fs_store_v1_backup"); - - if storage_dir_path.exists() { - if v2_dir.exists() { - // The original store is still in place, so a temp migration dir is from a crash before - // the rename step and can be discarded before retrying migration. - fs::remove_dir_all(&v2_dir).map_err(|_| BuildError::KVStoreSetupFailed)?; - } - return Ok(()); - } - - if backup_dir.exists() { - if v2_dir.exists() { - // Prefer retrying from the v1 backup instead of deciding here whether the temp v2 dir is - // usable. open_or_migrate_fs_store owns the actual v1-to-v2 migration. - fs::remove_dir_all(&v2_dir).map_err(|_| BuildError::KVStoreSetupFailed)?; - } - // The crash happened after moving v1 aside; restore it so normal startup can migrate it. - fs::rename(&backup_dir, storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?; - return Ok(()); - } - - if v2_dir.exists() { - // There is no v1 backup to retry from. Move the temp dir into place and let - // open_or_migrate_fs_store decide whether it is a valid v2 store. - fs::rename(&v2_dir, storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?; - } - - Ok(()) -} - #[cfg(test)] mod tests { - use std::fs; - use std::path::{Path, PathBuf}; - - use lightning::util::persist::{migrate_kv_store_data_async, KVStore}; - use lightning_persister::fs_store::v1::FilesystemStore; - use lightning_persister::fs_store::v2::FilesystemStoreV2; - + use super::read_or_generate_seed_file; use super::test_utils::random_storage_path; - use super::{open_or_migrate_fs_store, read_or_generate_seed_file}; - - const TEST_PRIMARY_NAMESPACE: &str = "test_primary_namespace"; - const TEST_SECONDARY_NAMESPACE: &str = "test_secondary_namespace"; - const TEST_KEY: &str = "test_key"; - const TEST_VALUE: &[u8] = b"test_value"; #[test] fn generated_seed_is_readable() { @@ -873,167 +772,6 @@ mod tests { let read_seed_bytes = read_or_generate_seed_file(&rand_path.to_str().unwrap()).unwrap(); assert_eq!(expected_seed_bytes, read_seed_bytes); } - - #[tokio::test] - async fn fs_store_migration_recovers_before_v1_backup_rename() { - let fs_store_path = fs_store_path(); - let v1_store = write_v1_test_data(&fs_store_path).await; - let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); - let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); - migrate_kv_store_data_async(&v1_store, &v2_store).await.unwrap(); - - let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); - assert_eq!( - KVStore::read( - &migrated_store, - TEST_PRIMARY_NAMESPACE, - TEST_SECONDARY_NAMESPACE, - TEST_KEY - ) - .await - .unwrap(), - TEST_VALUE - ); - assert!(fs_store_path.exists()); - assert!(!v2_migrating_path.exists()); - } - - #[tokio::test] - async fn fs_store_migration_recovers_after_v1_backup_rename() { - let fs_store_path = fs_store_path(); - let v1_store = write_v1_test_data(&fs_store_path).await; - let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); - let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); - migrate_kv_store_data_async(&v1_store, &v2_store).await.unwrap(); - - let backup_path = sibling_path(&fs_store_path, "fs_store_v1_backup"); - fs::rename(&fs_store_path, backup_path).unwrap(); - - let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); - assert_eq!( - KVStore::read( - &migrated_store, - TEST_PRIMARY_NAMESPACE, - TEST_SECONDARY_NAMESPACE, - TEST_KEY - ) - .await - .unwrap(), - TEST_VALUE - ); - assert!(fs_store_path.exists()); - assert!(!v2_migrating_path.exists()); - } - - #[tokio::test] - async fn fs_store_migration_recovers_after_v2_rename() { - let fs_store_path = fs_store_path(); - let v1_store = write_v1_test_data(&fs_store_path).await; - let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); - let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); - migrate_kv_store_data_async(&v1_store, &v2_store).await.unwrap(); - - let backup_path = sibling_path(&fs_store_path, "fs_store_v1_backup"); - fs::rename(&fs_store_path, &backup_path).unwrap(); - fs::rename(&v2_migrating_path, &fs_store_path).unwrap(); - - let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); - assert_eq!( - KVStore::read( - &migrated_store, - TEST_PRIMARY_NAMESPACE, - TEST_SECONDARY_NAMESPACE, - TEST_KEY - ) - .await - .unwrap(), - TEST_VALUE - ); - assert!(fs_store_path.exists()); - assert!(backup_path.exists()); - assert!(!v2_migrating_path.exists()); - } - - #[tokio::test] - async fn fs_store_migration_recovers_backup_without_migrating_dir() { - let fs_store_path = fs_store_path(); - write_v1_test_data(&fs_store_path).await; - - let backup_path = sibling_path(&fs_store_path, "fs_store_v1_backup"); - fs::rename(&fs_store_path, backup_path).unwrap(); - - let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); - assert_eq!( - KVStore::read( - &migrated_store, - TEST_PRIMARY_NAMESPACE, - TEST_SECONDARY_NAMESPACE, - TEST_KEY - ) - .await - .unwrap(), - TEST_VALUE - ); - assert!(fs_store_path.exists()); - assert!(!sibling_path(&fs_store_path, "fs_store_v1_backup").exists()); - } - - #[tokio::test] - async fn fs_store_migration_recovers_unexpected_migrating_dir_without_backup() { - let fs_store_path = fs_store_path(); - let v2_migrating_path = sibling_path(&fs_store_path, "fs_store_v2_migrating"); - let v2_store = FilesystemStoreV2::new(v2_migrating_path.clone()).unwrap(); - KVStore::write( - &v2_store, - TEST_PRIMARY_NAMESPACE, - TEST_SECONDARY_NAMESPACE, - TEST_KEY, - TEST_VALUE.to_vec(), - ) - .await - .unwrap(); - - let migrated_store = open_or_migrate_fs_store(fs_store_path.clone()).await.unwrap(); - assert_eq!( - KVStore::read( - &migrated_store, - TEST_PRIMARY_NAMESPACE, - TEST_SECONDARY_NAMESPACE, - TEST_KEY - ) - .await - .unwrap(), - TEST_VALUE - ); - assert!(fs_store_path.exists()); - assert!(!v2_migrating_path.exists()); - } - - fn fs_store_path() -> PathBuf { - let mut fs_store_path = random_storage_path(); - fs_store_path.push("fs_store"); - fs_store_path - } - - fn sibling_path(path: &Path, file_name: &str) -> PathBuf { - let mut sibling_path = path.to_path_buf(); - sibling_path.set_file_name(file_name); - sibling_path - } - - async fn write_v1_test_data(fs_store_path: &Path) -> FilesystemStore { - let v1_store = FilesystemStore::new(fs_store_path.to_path_buf()); - KVStore::write( - &v1_store, - TEST_PRIMARY_NAMESPACE, - TEST_SECONDARY_NAMESPACE, - TEST_KEY, - TEST_VALUE.to_vec(), - ) - .await - .unwrap(); - v1_store - } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 18152b3a7..b6f790767 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,13 @@ #![allow(ellipsis_inclusive_range_patterns)] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(not(any( + feature = "chain-esplora", + feature = "chain-electrum", + feature = "chain-bitcoind" +)))] +compile_error!("at least one chain source feature must be enabled"); + mod balance; mod builder; mod chain; @@ -123,11 +130,7 @@ use bitcoin::secp256k1::PublicKey; #[cfg(feature = "uniffi")] pub use bitcoin::FeeRate; use bitcoin::{Address, Amount, BlockHash, Network}; -#[cfg(feature = "uniffi")] -pub use builder::ArcedNodeBuilder as Builder; -pub use builder::BuildError; -#[cfg(not(feature = "uniffi"))] -pub use builder::NodeBuilder as Builder; +pub use builder::{BuildError, Builder}; use chain::ChainSource; use config::{ default_user_config, may_announce_channel, AsyncPaymentsRole, ChannelConfig, Config, @@ -174,8 +177,10 @@ use payment::asynchronous::om_mailbox::OnionMessageMailbox; use payment::asynchronous::static_invoice_store::StaticInvoiceStore; use payment::{ Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, PaymentDetailsPage, - SpontaneousPayment, UnifiedPayment, + SpontaneousPayment, }; +#[cfg(feature = "unified-payments")] +use payment::{HRNResolver, UnifiedPayment}; use peer_store::{PeerInfo, PeerStore}; #[cfg(feature = "uniffi")] pub use probing::ArcedProbingConfigBuilder as ProbingConfigBuilder; @@ -184,12 +189,12 @@ use runtime::Runtime; pub use tokio; use types::{ Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph, - HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, - Wallet, + KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, Wallet, }; pub use types::{ ChannelCounterparty, ChannelDetails, CustomTlvRecord, PeerDetails, ReserveType, UserChannelId, }; +#[cfg(feature = "storage-vss")] pub use vss_client; use crate::config::{LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY, LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY}; @@ -271,6 +276,7 @@ pub struct Node { node_metrics: Arc, om_mailbox: Option>, async_payments_role: Option, + #[cfg(feature = "unified-payments")] hrn_resolver: HRNResolver, prober: Option>, #[cfg(cycle_tests)] @@ -1156,7 +1162,7 @@ impl Node { /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki - #[cfg(not(feature = "uniffi"))] + #[cfg(all(feature = "unified-payments", not(feature = "uniffi")))] pub fn unified_payment(&self) -> UnifiedPayment { UnifiedPayment::new( self.onchain_payment().into(), @@ -1167,7 +1173,11 @@ impl Node { self.hrn_resolver.clone(), ) } +} +#[cfg(all(feature = "unified-payments", feature = "uniffi"))] +#[uniffi::export] +impl Node { /// Returns a payment handler that supports creating and paying to [BIP 21] URIs with on-chain, /// [BOLT 11], and [BOLT 12] payment options. /// @@ -1177,7 +1187,6 @@ impl Node { /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki - #[cfg(feature = "uniffi")] pub fn unified_payment(&self) -> Arc { Arc::new(UnifiedPayment::new( self.onchain_payment(), @@ -1188,7 +1197,9 @@ impl Node { self.hrn_resolver.clone(), )) } +} +impl Node { /// Authenticates the user via [LNURL-auth] for the given LNURL string. /// /// [LNURL-auth]: https://github.com/lnurl/luds/blob/luds/04.md diff --git a/src/payment/hrn.rs b/src/payment/hrn.rs new file mode 100644 index 000000000..91856e678 --- /dev/null +++ b/src/payment/hrn.rs @@ -0,0 +1,53 @@ +// 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. + +use std::sync::Arc; + +use bitcoin_payment_instructions::amount::Amount as BPIAmount; +use bitcoin_payment_instructions::dns_resolver::DNSHrnResolver; +use bitcoin_payment_instructions::hrn_resolution::{ + HrnResolutionFuture, HrnResolver, HumanReadableName, LNURLResolutionFuture, +}; +use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver; + +use crate::logger::Logger; +use crate::types::Graph; + +#[derive(Clone)] +pub enum HRNResolver { + Onion(Arc, Arc>>), + Local(Arc), +} + +impl HrnResolver for HRNResolver { + fn resolve_hrn<'a>(&'a self, hrn: &'a HumanReadableName) -> HrnResolutionFuture<'a> { + match self { + HRNResolver::Onion(inner) => inner.resolve_hrn(hrn), + HRNResolver::Local(inner) => inner.resolve_hrn(hrn), + } + } + + fn resolve_lnurl<'a>(&'a self, url: &'a str) -> HrnResolutionFuture<'a> { + match self { + HRNResolver::Onion(inner) => inner.resolve_lnurl(url), + HRNResolver::Local(inner) => inner.resolve_lnurl(url), + } + } + + fn resolve_lnurl_to_invoice<'a>( + &'a self, callback_url: String, amount: BPIAmount, expected_description_hash: [u8; 32], + ) -> LNURLResolutionFuture<'a> { + match self { + HRNResolver::Onion(inner) => { + inner.resolve_lnurl_to_invoice(callback_url, amount, expected_description_hash) + }, + HRNResolver::Local(inner) => { + inner.resolve_lnurl_to_invoice(callback_url, amount, expected_description_hash) + }, + } + } +} diff --git a/src/payment/mod.rs b/src/payment/mod.rs index b0f4901a7..13dbe5106 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -10,15 +10,20 @@ pub(crate) mod asynchronous; mod bolt11; mod bolt12; +#[cfg(feature = "unified-payments")] +mod hrn; mod onchain; pub(crate) mod pending_payment_store; mod spontaneous; pub(crate) mod store; +#[cfg(feature = "unified-payments")] mod unified; pub use bolt11::Bolt11Payment; pub(crate) use bolt11::PaymentMetadata; pub use bolt12::{Bolt12Payment, PayerProofOptions}; +#[cfg(feature = "unified-payments")] +pub(crate) use hrn::HRNResolver; pub use onchain::OnchainPayment; pub(crate) use pending_payment_store::{FundingTxCandidate, PendingPaymentDetails}; pub use spontaneous::SpontaneousPayment; @@ -26,4 +31,5 @@ pub use store::{ Channel, ConfirmationStatus, LSPS2Parameters, PageToken, PaymentDetails, PaymentDetailsPage, PaymentDirection, PaymentKind, PaymentStatus, TransactionType, }; +#[cfg(feature = "unified-payments")] pub use unified::{UnifiedPayment, UnifiedPaymentResult}; diff --git a/src/payment/unified.rs b/src/payment/unified.rs index cb5117414..185b2e2da 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -33,8 +33,8 @@ use crate::config::HRN_RESOLUTION_TIMEOUT_SECS; use crate::error::Error; use crate::ffi::maybe_wrap; use crate::logger::{log_error, LdkLogger, Logger}; +use crate::payment::HRNResolver; use crate::payment::{Bolt11Payment, Bolt12Payment, OnchainPayment}; -use crate::types::HRNResolver; use crate::Config; type Uri<'a> = bip21::Uri<'a, NetworkChecked, Extras>; @@ -72,6 +72,7 @@ pub struct UnifiedPayment { onchain_payment: Arc, bolt11_invoice: Arc, bolt12_payment: Arc, + #[cfg(not(hrn_tests))] config: Arc, logger: Arc, hrn_resolver: HRNResolver, @@ -85,10 +86,14 @@ impl UnifiedPayment { bolt12_payment: Arc, config: Arc, logger: Arc, hrn_resolver: HRNResolver, ) -> Self { + #[cfg(hrn_tests)] + let _ = config; + Self { onchain_payment, bolt11_invoice, bolt12_payment, + #[cfg(not(hrn_tests))] config, logger, hrn_resolver, diff --git a/src/types.rs b/src/types.rs index 65156982e..1a61daa10 100644 --- a/src/types.rs +++ b/src/types.rs @@ -12,12 +12,6 @@ use std::sync::{Arc, Mutex}; use bitcoin::secp256k1::PublicKey; use bitcoin::{OutPoint, ScriptBuf}; -use bitcoin_payment_instructions::amount::Amount as BPIAmount; -use bitcoin_payment_instructions::dns_resolver::DNSHrnResolver; -use bitcoin_payment_instructions::hrn_resolution::{ - HrnResolutionFuture, HrnResolver, HumanReadableName, LNURLResolutionFuture, -}; -use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver; use lightning::chain::chainmonitor; use lightning::impl_writeable_tlv_based; use lightning::ln::channel_state::{ @@ -36,13 +30,11 @@ use lightning::util::persist::{ }; use lightning::util::ser::{Readable, Writeable, Writer}; use lightning::util::sweep::OutputSweeper; -use lightning_block_sync::gossip::GossipVerifier; use lightning_liquidity::utils::time::DefaultTimeProvider; use lightning_net_tokio::SocketDescriptor; #[cfg(not(feature = "uniffi"))] use lightning_types::features::ChannelTypeFeatures; -use crate::chain::bitcoind::UtxoSourceClient; use crate::chain::ChainSource; use crate::config::{AnchorChannelsConfig, ChannelConfig}; use crate::data_store::{DataStore, KeepAllEntries, KeepLeastRecentlyUsed}; @@ -289,7 +281,7 @@ pub(crate) type Scorer = CombinedScorer, Arc>; pub(crate) type Graph = gossip::NetworkGraph>; -pub(crate) type UtxoLookup = GossipVerifier>; +pub(crate) type UtxoLookup = dyn lightning::routing::utxo::UtxoLookup + Send + Sync; pub(crate) type P2PGossipSync = lightning::routing::gossip::P2PGossipSync, Arc, Arc>; @@ -316,41 +308,6 @@ pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMesse IgnoringMessageHandler, >; -#[derive(Clone)] -pub enum HRNResolver { - Onion(Arc, Arc>>), - Local(Arc), -} - -impl HrnResolver for HRNResolver { - fn resolve_hrn<'a>(&'a self, hrn: &'a HumanReadableName) -> HrnResolutionFuture<'a> { - match self { - HRNResolver::Onion(inner) => inner.resolve_hrn(hrn), - HRNResolver::Local(inner) => inner.resolve_hrn(hrn), - } - } - - fn resolve_lnurl<'a>(&'a self, url: &'a str) -> HrnResolutionFuture<'a> { - match self { - HRNResolver::Onion(inner) => inner.resolve_lnurl(url), - HRNResolver::Local(inner) => inner.resolve_lnurl(url), - } - } - - fn resolve_lnurl_to_invoice<'a>( - &'a self, callback_url: String, amount: BPIAmount, expected_description_hash: [u8; 32], - ) -> LNURLResolutionFuture<'a> { - match self { - HRNResolver::Onion(inner) => { - inner.resolve_lnurl_to_invoice(callback_url, amount, expected_description_hash) - }, - HRNResolver::Local(inner) => { - inner.resolve_lnurl_to_invoice(callback_url, amount, expected_description_hash) - }, - } - } -} - pub(crate) type MessageRouter = lightning::onion_message::messenger::DefaultMessageRouter< Arc, Arc, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 892f40914..b9c12b4a7 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -213,6 +213,7 @@ impl Wallet { self.inner.lock().expect("lock").tx_graph().full_txs().map(|tx_node| tx_node.tx).collect() } + #[cfg(feature = "chain-bitcoind")] pub(crate) fn get_unconfirmed_txids(&self) -> Vec { self.inner .lock() @@ -223,6 +224,7 @@ impl Wallet { .collect() } + #[cfg(feature = "chain-bitcoind")] pub(crate) fn current_best_block(&self) -> BlockLocator { let checkpoint = self.inner.lock().expect("lock").latest_checkpoint(); let mut current_block = Some(checkpoint.clone()); @@ -261,6 +263,7 @@ impl Wallet { Ok(()) } + #[cfg(feature = "chain-bitcoind")] pub(crate) async fn apply_mempool_txs( &self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>, ) -> Result<(), Error> { @@ -2682,7 +2685,7 @@ fn funding_reclassification_update( update } -#[cfg(test)] +#[cfg(all(test, any(feature = "chain-esplora", feature = "chain-electrum")))] mod tests { use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; @@ -2695,7 +2698,11 @@ mod tests { use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse}; use super::*; - use crate::config::{EsploraSyncConfig, PAYMENT_CACHE_CAPACITY}; + #[cfg(all(not(feature = "chain-esplora"), feature = "chain-electrum"))] + use crate::config::ElectrumSyncConfig; + #[cfg(feature = "chain-esplora")] + use crate::config::EsploraSyncConfig; + use crate::config::PAYMENT_CACHE_CAPACITY; use crate::io::test_utils::InMemoryStore; use crate::io::{ BDK_WALLET_ADDRESS_POOL_KEY, BDK_WALLET_ADDRESS_POOL_PRIMARY_NAMESPACE, @@ -2807,6 +2814,7 @@ mod tests { let fee_estimator = Arc::new(OnchainFeeEstimator::new()); let broadcaster = Arc::new(Broadcaster::new(Arc::clone(&logger))); let node_metrics = Arc::new(PersistedNodeMetrics::new(NodeMetrics::default())); + #[cfg(feature = "chain-esplora")] let (chain_source, _) = ChainSource::new_esplora( "http://localhost:1".to_string(), HashMap::new(), @@ -2819,6 +2827,17 @@ mod tests { node_metrics, ) .unwrap(); + #[cfg(all(not(feature = "chain-esplora"), feature = "chain-electrum"))] + let (chain_source, _) = ChainSource::new_electrum( + "tcp://localhost:1".to_string(), + ElectrumSyncConfig::default(), + Arc::clone(&fee_estimator), + Arc::clone(&broadcaster), + Arc::clone(&store), + Arc::clone(&config), + Arc::clone(&logger), + node_metrics, + ); let payment_store = Arc::new(PaymentStore::new( Vec::new(), KeepLeastRecentlyUsed::new(PAYMENT_CACHE_CAPACITY), diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 433fdd645..777bab700 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -43,6 +43,7 @@ use ldk_node::config::{ HumanReadableNamesConfig, }; use ldk_node::entropy::NodeEntropy; +#[cfg(feature = "storage-sqlite")] use ldk_node::io::sqlite_store::SqliteStore; use ldk_node::payment::{ PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, TransactionType, @@ -363,25 +364,52 @@ pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) { pub(crate) fn random_chain_source<'a>( bitcoind: &'a BitcoinD, electrsd: &'a ElectrsD, ) -> TestChainSource<'a> { - let r = rand::random_range(0..4); - match r { - 0 => { + #[cfg(not(feature = "chain-bitcoind"))] + let _ = bitcoind; + + let configured_sources = env::var("LDK_NODE_TEST_CHAIN_SOURCES").ok().map(|value| { + value + .split(|c: char| c == ',' || c.is_ascii_whitespace()) + .filter(|source| !source.is_empty()) + .map(|source| source.to_ascii_uppercase()) + .collect::>() + }); + let sources = configured_sources.unwrap_or_else(|| { + let mut sources = Vec::new(); + #[cfg(feature = "chain-esplora")] + sources.push("ESPLORA".to_string()); + #[cfg(feature = "chain-electrum")] + sources.push("ELECTRUM".to_string()); + #[cfg(feature = "chain-bitcoind")] + { + sources.push("BITCOIND_RPC".to_string()); + sources.push("BITCOIND_REST".to_string()); + } + sources + }); + let source = &sources[rand::random_range(0..sources.len())]; + match source.as_str() { + #[cfg(feature = "chain-esplora")] + "ESPLORA" => { println!("Randomly setting up Esplora chain syncing..."); TestChainSource::Esplora(electrsd) }, - 1 => { + #[cfg(feature = "chain-electrum")] + "ELECTRUM" => { println!("Randomly setting up Electrum chain syncing..."); TestChainSource::Electrum(electrsd) }, - 2 => { + #[cfg(feature = "chain-bitcoind")] + "BITCOIND_RPC" => { println!("Randomly setting up Bitcoind RPC chain syncing..."); TestChainSource::BitcoindRpcSync(bitcoind) }, - 3 => { + #[cfg(feature = "chain-bitcoind")] + "BITCOIND_REST" => { println!("Randomly setting up Bitcoind REST chain syncing..."); TestChainSource::BitcoindRestSync(bitcoind) }, - _ => unreachable!(), + _ => panic!("Unknown test chain source: {source}"), } } @@ -607,16 +635,22 @@ async fn settle_force_close_balance( #[derive(Clone)] pub(crate) enum TestChainSource<'a> { + #[cfg(feature = "chain-esplora")] Esplora(&'a ElectrsD), + #[cfg(feature = "chain-electrum")] Electrum(&'a ElectrsD), + #[cfg(feature = "chain-bitcoind")] BitcoindRpcSync(&'a BitcoinD), + #[cfg(feature = "chain-bitcoind")] BitcoindRestSync(&'a BitcoinD), } #[derive(Clone, Copy)] pub(crate) enum TestStoreType { TestSyncStore, + #[cfg(feature = "storage-sqlite")] Sqlite, + #[cfg(feature = "storage-filesystem")] FilesystemStore, } @@ -671,7 +705,7 @@ impl Default for TestConfig { macro_rules! setup_builder { ($builder:ident, $config:expr) => { #[cfg(feature = "uniffi")] - let $builder = Builder::from_config($config.clone()); + let mut $builder = Builder::from_config($config.clone()); #[cfg(not(feature = "uniffi"))] let mut $builder = Builder::from_config($config.clone()); }; @@ -679,6 +713,69 @@ macro_rules! setup_builder { pub(crate) use setup_builder; +pub(crate) fn configure_chain_source( + chain_source: &TestChainSource, builder: &mut Builder, config: &TestConfig, +) { + match chain_source { + #[cfg(feature = "chain-esplora")] + TestChainSource::Esplora(electrsd) => { + let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let mut sync_config = EsploraSyncConfig::default(); + sync_config.background_sync_config = None; + sync_config.force_wallet_full_scan = config.force_wallet_full_scan; + if let Some(full_scan_stop_gap) = config.full_scan_stop_gap { + sync_config.full_scan_stop_gap = full_scan_stop_gap; + } + builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); + }, + #[cfg(feature = "chain-electrum")] + TestChainSource::Electrum(electrsd) => { + let electrum_url = format!("tcp://{}", electrsd.electrum_url); + let mut sync_config = ElectrumSyncConfig::default(); + sync_config.background_sync_config = None; + sync_config.force_wallet_full_scan = config.force_wallet_full_scan; + if let Some(full_scan_stop_gap) = config.full_scan_stop_gap { + sync_config.full_scan_stop_gap = full_scan_stop_gap; + } + builder.set_chain_source_electrum(electrum_url.clone(), Some(sync_config)); + }, + #[cfg(feature = "chain-bitcoind")] + TestChainSource::BitcoindRpcSync(bitcoind) => { + let rpc_host = bitcoind.params.rpc_socket.ip().to_string(); + let rpc_port = bitcoind.params.rpc_socket.port(); + let values = bitcoind.params.get_cookie_values().unwrap().unwrap(); + let rpc_user = values.user; + let rpc_password = values.password; + builder.set_chain_source_bitcoind_rpc( + rpc_host, + rpc_port, + rpc_user, + rpc_password, + config.wallet_rescan_from_height, + ); + }, + #[cfg(feature = "chain-bitcoind")] + TestChainSource::BitcoindRestSync(bitcoind) => { + let rpc_host = bitcoind.params.rpc_socket.ip().to_string(); + let rpc_port = bitcoind.params.rpc_socket.port(); + let values = bitcoind.params.get_cookie_values().unwrap().unwrap(); + let rpc_user = values.user; + let rpc_password = values.password; + let rest_host = bitcoind.params.rpc_socket.ip().to_string(); + let rest_port = bitcoind.params.rpc_socket.port(); + builder.set_chain_source_bitcoind_rest( + rest_host, + rest_port, + rpc_host, + rpc_port, + rpc_user, + rpc_password, + config.wallet_rescan_from_height, + ); + }, + } +} + #[cfg(any(cln_test, lnd_test, eclair_test))] pub(crate) mod scenarios; @@ -734,60 +831,7 @@ pub(crate) fn setup_two_nodes_with_store( pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> TestNode { setup_builder!(builder, config.node_config); - match chain_source { - TestChainSource::Esplora(electrsd) => { - let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); - let mut sync_config = EsploraSyncConfig::default(); - sync_config.background_sync_config = None; - sync_config.force_wallet_full_scan = config.force_wallet_full_scan; - if let Some(full_scan_stop_gap) = config.full_scan_stop_gap { - sync_config.full_scan_stop_gap = full_scan_stop_gap; - } - builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config)); - }, - TestChainSource::Electrum(electrsd) => { - let electrum_url = format!("tcp://{}", electrsd.electrum_url); - let mut sync_config = ElectrumSyncConfig::default(); - sync_config.background_sync_config = None; - sync_config.force_wallet_full_scan = config.force_wallet_full_scan; - if let Some(full_scan_stop_gap) = config.full_scan_stop_gap { - sync_config.full_scan_stop_gap = full_scan_stop_gap; - } - builder.set_chain_source_electrum(electrum_url.clone(), Some(sync_config)); - }, - TestChainSource::BitcoindRpcSync(bitcoind) => { - let rpc_host = bitcoind.params.rpc_socket.ip().to_string(); - let rpc_port = bitcoind.params.rpc_socket.port(); - let values = bitcoind.params.get_cookie_values().unwrap().unwrap(); - let rpc_user = values.user; - let rpc_password = values.password; - builder.set_chain_source_bitcoind_rpc( - rpc_host, - rpc_port, - rpc_user, - rpc_password, - config.wallet_rescan_from_height, - ); - }, - TestChainSource::BitcoindRestSync(bitcoind) => { - let rpc_host = bitcoind.params.rpc_socket.ip().to_string(); - let rpc_port = bitcoind.params.rpc_socket.port(); - let values = bitcoind.params.get_cookie_values().unwrap().unwrap(); - let rpc_user = values.user; - let rpc_password = values.password; - let rest_host = bitcoind.params.rpc_socket.ip().to_string(); - let rest_port = bitcoind.params.rpc_socket.port(); - builder.set_chain_source_bitcoind_rest( - rest_host, - rest_port, - rpc_host, - rpc_port, - rpc_user, - rpc_password, - config.wallet_rescan_from_height, - ); - }, - } + configure_chain_source(chain_source, &mut builder, &config); match &config.log_writer { TestLogWriter::FileWriter => { @@ -812,7 +856,9 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> let kv_store = TestSyncStore::new(config.node_config.storage_dir_path.into()); builder.build_with_store(config.node_entropy.into(), kv_store).unwrap() }, + #[cfg(feature = "storage-sqlite")] TestStoreType::Sqlite => builder.build(config.node_entropy.into()).unwrap(), + #[cfg(feature = "storage-filesystem")] TestStoreType::FilesystemStore => { builder.build_with_fs_store(config.node_entropy.into()).unwrap() }, @@ -1906,6 +1952,7 @@ struct TestSyncStoreInner { serializer: tokio::sync::RwLock<()>, test_store: InMemoryStore, fs_store: FilesystemStore, + #[cfg(feature = "storage-sqlite")] sqlite_store: SqliteStore, } @@ -1915,8 +1962,11 @@ impl TestSyncStoreInner { let mut fs_dir = dest_dir.clone(); fs_dir.push("fs_store"); let fs_store = FilesystemStore::new(fs_dir); + #[cfg(feature = "storage-sqlite")] let mut sql_dir = dest_dir.clone(); + #[cfg(feature = "storage-sqlite")] sql_dir.push("sqlite_store"); + #[cfg(feature = "storage-sqlite")] let sqlite_store = SqliteStore::new( sql_dir, Some("test_sync_db".to_string()), @@ -1924,15 +1974,21 @@ impl TestSyncStoreInner { ) .unwrap(); let test_store = InMemoryStore::new(); - Self { serializer, fs_store, sqlite_store, test_store } + Self { + serializer, + fs_store, + #[cfg(feature = "storage-sqlite")] + sqlite_store, + test_store, + } } async fn do_list_async( &self, primary_namespace: &str, secondary_namespace: &str, ) -> lightning::io::Result> { let fs_res = KVStore::list(&self.fs_store, primary_namespace, secondary_namespace).await; - let sqlite_res = - KVStore::list(&self.sqlite_store, primary_namespace, secondary_namespace).await; + #[cfg(feature = "storage-sqlite")] + let sqlite_res = KVStore::list(&self.sqlite_store, primary_namespace, secondary_namespace).await; let test_res = KVStore::list(&self.test_store, primary_namespace, secondary_namespace).await; @@ -1940,9 +1996,12 @@ impl TestSyncStoreInner { Ok(mut list) => { list.sort(); - let mut sqlite_list = sqlite_res.unwrap(); - sqlite_list.sort(); - assert_eq!(list, sqlite_list); + #[cfg(feature = "storage-sqlite")] + { + let mut sqlite_list = sqlite_res.unwrap(); + sqlite_list.sort(); + assert_eq!(list, sqlite_list); + } let mut test_list = test_res.unwrap(); test_list.sort(); @@ -1951,6 +2010,7 @@ impl TestSyncStoreInner { Ok(list) }, Err(e) => { + #[cfg(feature = "storage-sqlite")] assert!(sqlite_res.is_err()); assert!(test_res.is_err()); Err(e) @@ -1969,6 +2029,7 @@ impl TestSyncStoreInner { &self, primary_namespace: &str, secondary_namespace: &str, page_token: Option, ) -> lightning::io::Result { let _guard = self.serializer.read().await; + #[cfg(feature = "storage-sqlite")] let sqlite_res = PaginatedKVStore::list_paginated( &self.sqlite_store, primary_namespace, @@ -1984,7 +2045,8 @@ impl TestSyncStoreInner { ) .await; - match sqlite_res { + #[cfg(feature = "storage-sqlite")] + return match sqlite_res { Ok(sqlite_response) => { assert_eq!(sqlite_response, test_res.unwrap()); Ok(sqlite_response) @@ -1993,7 +2055,10 @@ impl TestSyncStoreInner { assert!(test_res.is_err()); Err(e) }, - } + }; + + #[cfg(not(feature = "storage-sqlite"))] + test_res } async fn read_internal_async( @@ -2003,6 +2068,7 @@ impl TestSyncStoreInner { let fs_res = KVStore::read(&self.fs_store, primary_namespace, secondary_namespace, key).await; + #[cfg(feature = "storage-sqlite")] let sqlite_res = KVStore::read(&self.sqlite_store, primary_namespace, secondary_namespace, key).await; let test_res = @@ -2010,13 +2076,17 @@ impl TestSyncStoreInner { match fs_res { Ok(read) => { + #[cfg(feature = "storage-sqlite")] assert_eq!(read, sqlite_res.unwrap()); assert_eq!(read, test_res.unwrap()); Ok(read) }, Err(e) => { - assert!(sqlite_res.is_err()); - assert_eq!(e.kind(), unsafe { sqlite_res.unwrap_err_unchecked().kind() }); + #[cfg(feature = "storage-sqlite")] + { + assert!(sqlite_res.is_err()); + assert_eq!(e.kind(), unsafe { sqlite_res.unwrap_err_unchecked().kind() }); + } assert!(test_res.is_err()); assert_eq!(e.kind(), unsafe { test_res.unwrap_err_unchecked().kind() }); Err(e) @@ -2036,6 +2106,7 @@ impl TestSyncStoreInner { buf.clone(), ) .await; + #[cfg(feature = "storage-sqlite")] let sqlite_res = KVStore::write( &self.sqlite_store, primary_namespace, @@ -2061,11 +2132,13 @@ impl TestSyncStoreInner { match fs_res { Ok(()) => { + #[cfg(feature = "storage-sqlite")] assert!(sqlite_res.is_ok()); assert!(test_res.is_ok()); Ok(()) }, Err(e) => { + #[cfg(feature = "storage-sqlite")] assert!(sqlite_res.is_err()); assert!(test_res.is_err()); Err(e) @@ -2080,6 +2153,7 @@ impl TestSyncStoreInner { let fs_res = KVStore::remove(&self.fs_store, primary_namespace, secondary_namespace, key, lazy) .await; + #[cfg(feature = "storage-sqlite")] let sqlite_res = KVStore::remove(&self.sqlite_store, primary_namespace, secondary_namespace, key, lazy) .await; @@ -2095,11 +2169,13 @@ impl TestSyncStoreInner { match fs_res { Ok(()) => { + #[cfg(feature = "storage-sqlite")] assert!(sqlite_res.is_ok()); assert!(test_res.is_ok()); Ok(()) }, Err(e) => { + #[cfg(feature = "storage-sqlite")] assert!(sqlite_res.is_err()); assert!(test_res.is_err()); Err(e) @@ -2110,7 +2186,7 @@ impl TestSyncStoreInner { /// The PostgreSQL connection string used by the Postgres-backed tests, overridable via the /// `TEST_POSTGRES_URL` environment variable. -#[cfg(feature = "postgres")] +#[cfg(feature = "storage-postgres")] pub(crate) fn test_connection_string() -> String { std::env::var("TEST_POSTGRES_URL") .unwrap_or_else(|_| "host=localhost user=postgres password=postgres".to_string()) @@ -2118,7 +2194,7 @@ pub(crate) fn test_connection_string() -> String { /// Drops the given table from the `ldk_db` database, ignoring the case where the database doesn't /// exist yet. Used to ensure a clean slate before and after Postgres-backed tests. -#[cfg(feature = "postgres")] +#[cfg(feature = "storage-postgres")] pub(crate) async fn drop_table(table_name: &str) { let connection_string = format!("{} dbname=ldk_db", test_connection_string()); let Ok((client, connection)) = diff --git a/tests/integration_tests_hrn.rs b/tests/integration_tests_hrn.rs index d61604798..ecf92dcc2 100644 --- a/tests/integration_tests_hrn.rs +++ b/tests/integration_tests_hrn.rs @@ -13,7 +13,7 @@ use bitcoin::Amount; use common::{ expect_channel_ready_event, expect_payment_successful_event, generate_blocks_and_wait, open_channel, premine_and_distribute_funds, random_chain_source, setup_bitcoind_and_electrsd, - setup_two_nodes, TestChainSource, + setup_two_nodes, }; use ldk_node::payment::UnifiedPaymentResult; use ldk_node::Event; diff --git a/tests/integration_tests_migration.rs b/tests/integration_tests_migration.rs index 26f7d7c78..84e332bce 100644 --- a/tests/integration_tests_migration.rs +++ b/tests/integration_tests_migration.rs @@ -7,7 +7,7 @@ // The migration test exercises the filesystem, SQLite, and Postgres stores. It is gated on the // `postgres` feature because Postgres is the only one of the three that needs an external service. -#![cfg(feature = "postgres")] +#![cfg(feature = "storage-postgres")] mod common; diff --git a/tests/integration_tests_postgres.rs b/tests/integration_tests_postgres.rs index d972c6c7c..280c11de5 100644 --- a/tests/integration_tests_postgres.rs +++ b/tests/integration_tests_postgres.rs @@ -5,14 +5,12 @@ // 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(feature = "postgres")] +#![cfg(feature = "storage-postgres")] mod common; -use common::{drop_table, test_connection_string}; -use ldk_node::entropy::NodeEntropy; +use common::{configure_chain_source, drop_table, random_chain_source, test_connection_string}; use ldk_node::Builder; -use rand::RngCore; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn channel_full_cycle_with_postgres_store() { @@ -20,11 +18,11 @@ async fn channel_full_cycle_with_postgres_store() { drop_table("channel_cycle_b").await; let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); println!("== Node A =="); - let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config_a = common::random_config(); - let mut builder_a = Builder::from_config(config_a.node_config); - builder_a.set_chain_source_esplora(esplora_url.clone(), None); + let mut builder_a = Builder::from_config(config_a.node_config.clone()); + configure_chain_source(&chain_source, &mut builder_a, &config_a); let node_a = builder_a .build_with_postgres_store( config_a.node_entropy.into(), @@ -39,8 +37,8 @@ async fn channel_full_cycle_with_postgres_store() { println!("\n== Node B =="); let mut config_b = common::random_config(); config_b.node_config.manually_handle_unknown_bolt11_payments = true; - let mut builder_b = Builder::from_config(config_b.node_config); - builder_b.set_chain_source_esplora(esplora_url.clone(), None); + let mut builder_b = Builder::from_config(config_b.node_config.clone()); + configure_chain_source(&chain_source, &mut builder_b, &config_b); let node_b = builder_b .build_with_postgres_store( config_b.node_entropy.into(), @@ -73,23 +71,18 @@ async fn postgres_node_restart() { drop_table("restart_test").await; let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd(); - let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let chain_source = random_chain_source(&bitcoind, &electrsd); let connection_string = test_connection_string(); let storage_path = common::random_storage_path().to_str().unwrap().to_owned(); - let mut seed_bytes = [42u8; 64]; - rand::rng().fill_bytes(&mut seed_bytes); - #[cfg(feature = "uniffi")] - let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes.to_vec()).unwrap(); - #[cfg(not(feature = "uniffi"))] - let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes); + let mut config = common::random_config(); + config.node_config.storage_dir_path = storage_path; + let node_entropy = config.node_entropy; // Setup initial node and fund it. let (expected_balance_sats, expected_node_id) = { - let mut builder = Builder::new(); - builder.set_network(bitcoin::Network::Regtest); - builder.set_storage_dir_path(storage_path.clone()); - builder.set_chain_source_esplora(esplora_url.clone(), None); + let mut builder = Builder::from_config(config.node_config.clone()); + configure_chain_source(&chain_source, &mut builder, &config); let node = builder .build_with_postgres_store( node_entropy.into(), @@ -120,10 +113,8 @@ async fn postgres_node_restart() { }; // Verify node can be restarted from PostgreSQL backend. - let mut builder = Builder::new(); - builder.set_network(bitcoin::Network::Regtest); - builder.set_storage_dir_path(storage_path); - builder.set_chain_source_esplora(esplora_url, None); + let mut builder = Builder::from_config(config.node_config.clone()); + configure_chain_source(&chain_source, &mut builder, &config); let node = builder .build_with_postgres_store( diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index fd247f74c..b99d0781e 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -439,6 +439,7 @@ async fn address_pool_is_reloaded_on_restart() { expect_channel_ready_event!(node_b, node_a.node_id()); } +#[cfg(feature = "chain-bitcoind")] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); @@ -1554,6 +1555,7 @@ async fn do_onchain_wallet_full_scan_stop_gap_recovers_far_funds( ); } +#[cfg(feature = "chain-bitcoind")] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn onchain_wallet_recovery_rescans_from_birthday_height() { // End-to-end test for `wallet_rescan_from_height` against a bitcoind chain source. The @@ -1649,6 +1651,7 @@ async fn onchain_wallet_recovery_rescans_from_birthday_height() { ); } +#[cfg(feature = "chain-bitcoind")] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn build_fails_when_wallet_rescan_height_is_above_tip() { let (bitcoind, _electrsd) = setup_bitcoind_and_electrsd(); @@ -1682,6 +1685,7 @@ async fn build_fails_when_wallet_rescan_height_is_above_tip() { } } +#[cfg(feature = "chain-bitcoind")] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn build_aborts_on_first_startup_bitcoind_tip_fetch_failure() { // A fresh node pointed at an unreachable bitcoind RPC endpoint must not silently @@ -1713,11 +1717,13 @@ async fn build_aborts_on_first_startup_bitcoind_tip_fetch_failure() { } } +#[cfg(all(feature = "chain-esplora", feature = "chain-electrum", feature = "chain-bitcoind"))] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_rbf_via_mempool() { run_rbf_test(false).await; } +#[cfg(all(feature = "chain-esplora", feature = "chain-electrum", feature = "chain-bitcoind"))] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_rbf_via_direct_block_insertion() { run_rbf_test(true).await; @@ -1726,6 +1732,7 @@ async fn test_rbf_via_direct_block_insertion() { // `is_insert_block`: // - `true`: transaction is mined immediately (no mempool), testing confirmed-Tx handling. // - `false`: transaction stays in mempool until confirmation, testing unconfirmed-Tx handling. +#[cfg(all(feature = "chain-esplora", feature = "chain-electrum", feature = "chain-bitcoind"))] async fn run_rbf_test(is_insert_block: bool) { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source_bitcoind = TestChainSource::BitcoindRpcSync(&bitcoind); @@ -4227,7 +4234,9 @@ async fn build_0_7_0_node( builder_old.set_entropy_seed_bytes(seed_bytes); builder_old.set_chain_source_esplora(esplora_url, None); let node_old = match config.store_type { + #[cfg(feature = "storage-filesystem")] TestStoreType::FilesystemStore => builder_old.build_with_fs_store().unwrap(), + #[cfg(feature = "storage-sqlite")] TestStoreType::Sqlite => builder_old.build().unwrap(), TestStoreType::TestSyncStore => panic!("TestSyncStore not supported in v0.7.0 builder"), }; @@ -4311,6 +4320,7 @@ async fn persistence_backwards_compatibility() { do_persistence_backwards_compatibility(OldLdkVersion::V0_7_0).await; } +#[cfg(feature = "storage-filesystem")] #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn fs_store_persistence_backwards_compatibility() { let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd(); diff --git a/tests/reorg_test.rs b/tests/reorg_test.rs index efab1480e..aa17f072c 100644 --- a/tests/reorg_test.rs +++ b/tests/reorg_test.rs @@ -16,6 +16,7 @@ use crate::common::{ TestChainSource, }; +#[cfg(feature = "chain-bitcoind")] #[test] fn bitcoind_rest_follows_valid_reorg() { let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();