diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a55abfe5..2aeba7d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# 0.7.0-rc.7 (Synonym Fork) + +## Synonym Fork Additions +- Added **Light Mode** build-time configuration for resource-constrained environments (iOS NSE, Android background): + - `Builder::set_light_mode_minimal()` - Ultra-minimal mode for iOS Notification Service Extension (~24MB memory limit) + - `single_threaded_runtime` flag - Saves ~10-15MB by using single-threaded Tokio runtime + - `disable_liquidity_handler` flag - Disables LSPS1/LSPS2 background processing + - Combined with existing flags (`disable_listening`, `disable_peer_reconnection`, `disable_rgs_sync`, etc.) +- Added **Runtime Sync Intervals** (`RuntimeSyncIntervals`) to adjust background task intervals at runtime for battery saving: + - `Node::update_sync_intervals()` - Update intervals without restarting the node + - `battery_saving_sync_intervals()` - Convenience function with preset battery-saving values + - Configurable intervals for: peer reconnection, RGS sync, pathfinding scores, node announcements, + on-chain wallet sync, Lightning wallet sync, and fee rate cache updates +- Documentation improvements for mobile developer usage (Android battery saving, iOS NSE integration) + # 0.7.0-rc.6 (Synonym Fork) ## Synonym Fork Additions diff --git a/Cargo.toml b/Cargo.toml index 1291ed3d8..2f45e7a8f 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ldk-node" -version = "0.7.0-rc.6" +version = "0.7.0-rc.7" authors = ["Elias Rohrer "] homepage = "https://lightningdevkit.org/" license = "MIT OR Apache-2.0" diff --git a/Package.swift b/Package.swift index 550824a6d..92dfa14b9 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.7.0-rc.6" -let checksum = "4ea23aedbf918a1c93539168f34e626cbe867c1d5e827b7b7fd0e84225970b91" +let tag = "v0.7.0-rc.7" +let checksum = "a90003bce59a5625d6276d027f73dd37db1048bc2ee7727352988274717180ed" let url = "https://github.com/synonymdev/ldk-node/releases/download/\(tag)/LDKNodeFFI.xcframework.zip" let package = Package( diff --git a/bindings/kotlin/ldk-node-android/gradle.properties b/bindings/kotlin/ldk-node-android/gradle.properties index c8d14b96a..ededbded7 100644 --- a/bindings/kotlin/ldk-node-android/gradle.properties +++ b/bindings/kotlin/ldk-node-android/gradle.properties @@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m android.useAndroidX=true android.enableJetifier=true kotlin.code.style=official -libraryVersion=0.7.0-rc.6 +libraryVersion=0.7.0-rc.7 diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so index aa68863ca..9e4b5ff4e 100755 Binary files a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so and b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so differ diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so index f11c119f8..fc2f3edbc 100755 Binary files a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so and b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so differ diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so index 30cab16a8..3b15cc06d 100755 Binary files a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so and b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so differ diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt index 7ed347b0b..4986fe4ad 100644 --- a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt +++ b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt @@ -1479,6 +1479,18 @@ internal typealias UniffiVTableCallbackInterfaceVssHeaderProviderUniffiByValue = + + + + + + + + + + + + @@ -2024,6 +2036,15 @@ internal interface UniffiLib : Library { `rgsServerUrl`: RustBufferByValue, uniffiCallStatus: UniffiRustCallStatus, ): Unit + fun uniffi_ldk_node_fn_method_builder_set_light_mode( + `ptr`: Pointer?, + `config`: RustBufferByValue, + uniffiCallStatus: UniffiRustCallStatus, + ): Unit + fun uniffi_ldk_node_fn_method_builder_set_light_mode_minimal( + `ptr`: Pointer?, + uniffiCallStatus: UniffiRustCallStatus, + ): Unit fun uniffi_ldk_node_fn_method_builder_set_liquidity_source_lsps1( `ptr`: Pointer?, `nodeId`: RustBufferByValue, @@ -2195,6 +2216,10 @@ internal interface UniffiLib : Library { `persist`: Byte, uniffiCallStatus: UniffiRustCallStatus, ): Unit + fun uniffi_ldk_node_fn_method_node_current_sync_intervals( + `ptr`: Pointer?, + uniffiCallStatus: UniffiRustCallStatus, + ): RustBufferByValue fun uniffi_ldk_node_fn_method_node_disconnect( `ptr`: Pointer?, `nodeId`: RustBufferByValue, @@ -2225,6 +2250,10 @@ internal interface UniffiLib : Library { `txid`: RustBufferByValue, uniffiCallStatus: UniffiRustCallStatus, ): RustBufferByValue + fun uniffi_ldk_node_fn_method_node_is_light_mode( + `ptr`: Pointer?, + uniffiCallStatus: UniffiRustCallStatus, + ): Byte fun uniffi_ldk_node_fn_method_node_list_balances( `ptr`: Pointer?, uniffiCallStatus: UniffiRustCallStatus, @@ -2351,6 +2380,11 @@ internal interface UniffiLib : Library { `channelConfig`: RustBufferByValue, uniffiCallStatus: UniffiRustCallStatus, ): Unit + fun uniffi_ldk_node_fn_method_node_update_sync_intervals( + `ptr`: Pointer?, + `intervals`: RustBufferByValue, + uniffiCallStatus: UniffiRustCallStatus, + ): Unit fun uniffi_ldk_node_fn_method_node_verify_signature( `ptr`: Pointer?, `msg`: RustBufferByValue, @@ -2657,6 +2691,9 @@ internal interface UniffiLib : Library { `ptr`: Pointer?, `request`: RustBufferByValue, ): Long + fun uniffi_ldk_node_fn_func_battery_saving_sync_intervals( + uniffiCallStatus: UniffiRustCallStatus, + ): RustBufferByValue fun uniffi_ldk_node_fn_func_default_config( uniffiCallStatus: UniffiRustCallStatus, ): RustBufferByValue @@ -2881,6 +2918,8 @@ internal interface UniffiLib : Library { `handle`: Long, uniffiCallStatus: UniffiRustCallStatus, ): Unit + fun uniffi_ldk_node_checksum_func_battery_saving_sync_intervals( + ): Short fun uniffi_ldk_node_checksum_func_default_config( ): Short fun uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic( @@ -3047,6 +3086,10 @@ internal interface UniffiLib : Library { ): Short fun uniffi_ldk_node_checksum_method_builder_set_gossip_source_rgs( ): Short + fun uniffi_ldk_node_checksum_method_builder_set_light_mode( + ): Short + fun uniffi_ldk_node_checksum_method_builder_set_light_mode_minimal( + ): Short fun uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps1( ): Short fun uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps2( @@ -3095,6 +3138,8 @@ internal interface UniffiLib : Library { ): Short fun uniffi_ldk_node_checksum_method_node_connect( ): Short + fun uniffi_ldk_node_checksum_method_node_current_sync_intervals( + ): Short fun uniffi_ldk_node_checksum_method_node_disconnect( ): Short fun uniffi_ldk_node_checksum_method_node_event_handled( @@ -3107,6 +3152,8 @@ internal interface UniffiLib : Library { ): Short fun uniffi_ldk_node_checksum_method_node_get_transaction_details( ): Short + fun uniffi_ldk_node_checksum_method_node_is_light_mode( + ): Short fun uniffi_ldk_node_checksum_method_node_list_balances( ): Short fun uniffi_ldk_node_checksum_method_node_list_channels( @@ -3159,6 +3206,8 @@ internal interface UniffiLib : Library { ): Short fun uniffi_ldk_node_checksum_method_node_update_channel_config( ): Short + fun uniffi_ldk_node_checksum_method_node_update_sync_intervals( + ): Short fun uniffi_ldk_node_checksum_method_node_verify_signature( ): Short fun uniffi_ldk_node_checksum_method_node_wait_next_event( @@ -3274,6 +3323,9 @@ private fun uniffiCheckContractApiVersion(lib: UniffiLib) { private fun uniffiCheckApiChecksums(lib: UniffiLib) { + if (lib.uniffi_ldk_node_checksum_func_battery_saving_sync_intervals() != 25473.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_ldk_node_checksum_func_default_config() != 55381.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -3523,6 +3575,12 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_ldk_node_checksum_method_builder_set_gossip_source_rgs() != 64312.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_ldk_node_checksum_method_builder_set_light_mode() != 13621.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_ldk_node_checksum_method_builder_set_light_mode_minimal() != 8340.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps1() != 51527.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -3595,6 +3653,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_ldk_node_checksum_method_node_connect() != 34120.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_ldk_node_checksum_method_node_current_sync_intervals() != 51918.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_ldk_node_checksum_method_node_disconnect() != 43538.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -3613,6 +3674,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_ldk_node_checksum_method_node_get_transaction_details() != 65000.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_ldk_node_checksum_method_node_is_light_mode() != 56992.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_ldk_node_checksum_method_node_list_balances() != 57528.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -3691,6 +3755,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_ldk_node_checksum_method_node_update_channel_config() != 37852.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_ldk_node_checksum_method_node_update_sync_intervals() != 6071.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_ldk_node_checksum_method_node_verify_signature() != 20486.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -5790,6 +5857,29 @@ open class Builder: Disposable, BuilderInterface { } } + override fun `setLightMode`(`config`: LightModeConfig) { + callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_builder_set_light_mode( + it, + FfiConverterTypeLightModeConfig.lower(`config`), + uniffiRustCallStatus, + ) + } + } + } + + override fun `setLightModeMinimal`() { + callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_builder_set_light_mode_minimal( + it, + uniffiRustCallStatus, + ) + } + } + } + override fun `setLiquiditySourceLsps1`(`nodeId`: PublicKey, `address`: SocketAddress, `token`: kotlin.String?) { callWithPointer { uniffiRustCall { uniffiRustCallStatus -> @@ -6844,6 +6934,17 @@ open class Node: Disposable, NodeInterface { } } + override fun `currentSyncIntervals`(): RuntimeSyncIntervals { + return FfiConverterTypeRuntimeSyncIntervals.lift(callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_node_current_sync_intervals( + it, + uniffiRustCallStatus, + ) + } + }) + } + @Throws(NodeException::class) override fun `disconnect`(`nodeId`: PublicKey) { callWithPointer { @@ -6921,6 +7022,17 @@ open class Node: Disposable, NodeInterface { }) } + override fun `isLightMode`(): kotlin.Boolean { + return FfiConverterBoolean.lift(callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_node_is_light_mode( + it, + uniffiRustCallStatus, + ) + } + }) + } + override fun `listBalances`(): BalanceDetails { return FfiConverterTypeBalanceDetails.lift(callWithPointer { uniffiRustCall { uniffiRustCallStatus -> @@ -7246,6 +7358,18 @@ open class Node: Disposable, NodeInterface { } } + override fun `updateSyncIntervals`(`intervals`: RuntimeSyncIntervals) { + callWithPointer { + uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_node_update_sync_intervals( + it, + FfiConverterTypeRuntimeSyncIntervals.lower(`intervals`), + uniffiRustCallStatus, + ) + } + } + } + override fun `verifySignature`(`msg`: List, `sig`: kotlin.String, `pkey`: PublicKey): kotlin.Boolean { return FfiConverterBoolean.lift(callWithPointer { uniffiRustCall { uniffiRustCallStatus -> @@ -9370,6 +9494,43 @@ object FfiConverterTypeLSPS2ServiceConfig: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): LightModeConfig { + return LightModeConfig( + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + FfiConverterBoolean.read(buf), + ) + } + + override fun allocationSize(value: LightModeConfig) = ( + FfiConverterBoolean.allocationSize(value.`singleThreadedRuntime`) + + FfiConverterBoolean.allocationSize(value.`disableListening`) + + FfiConverterBoolean.allocationSize(value.`disablePeerReconnection`) + + FfiConverterBoolean.allocationSize(value.`disableNodeAnnouncements`) + + FfiConverterBoolean.allocationSize(value.`disableRgsSync`) + + FfiConverterBoolean.allocationSize(value.`disablePathfindingScoresSync`) + + FfiConverterBoolean.allocationSize(value.`disableLiquidityHandler`) + ) + + override fun write(value: LightModeConfig, buf: ByteBuffer) { + FfiConverterBoolean.write(value.`singleThreadedRuntime`, buf) + FfiConverterBoolean.write(value.`disableListening`, buf) + FfiConverterBoolean.write(value.`disablePeerReconnection`, buf) + FfiConverterBoolean.write(value.`disableNodeAnnouncements`, buf) + FfiConverterBoolean.write(value.`disableRgsSync`, buf) + FfiConverterBoolean.write(value.`disablePathfindingScoresSync`, buf) + FfiConverterBoolean.write(value.`disableLiquidityHandler`, buf) + } +} + + + + object FfiConverterTypeLogRecord: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): LogRecord { return LogRecord( @@ -9659,6 +9820,43 @@ object FfiConverterTypeRoutingFees: FfiConverterRustBuffer { +object FfiConverterTypeRuntimeSyncIntervals: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RuntimeSyncIntervals { + return RuntimeSyncIntervals( + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: RuntimeSyncIntervals) = ( + FfiConverterULong.allocationSize(value.`peerReconnectionIntervalSecs`) + + FfiConverterULong.allocationSize(value.`rgsSyncIntervalSecs`) + + FfiConverterULong.allocationSize(value.`pathfindingScoresSyncIntervalSecs`) + + FfiConverterULong.allocationSize(value.`nodeAnnouncementIntervalSecs`) + + FfiConverterULong.allocationSize(value.`onchainWalletSyncIntervalSecs`) + + FfiConverterULong.allocationSize(value.`lightningWalletSyncIntervalSecs`) + + FfiConverterULong.allocationSize(value.`feeRateCacheUpdateIntervalSecs`) + ) + + override fun write(value: RuntimeSyncIntervals, buf: ByteBuffer) { + FfiConverterULong.write(value.`peerReconnectionIntervalSecs`, buf) + FfiConverterULong.write(value.`rgsSyncIntervalSecs`, buf) + FfiConverterULong.write(value.`pathfindingScoresSyncIntervalSecs`, buf) + FfiConverterULong.write(value.`nodeAnnouncementIntervalSecs`, buf) + FfiConverterULong.write(value.`onchainWalletSyncIntervalSecs`, buf) + FfiConverterULong.write(value.`lightningWalletSyncIntervalSecs`, buf) + FfiConverterULong.write(value.`feeRateCacheUpdateIntervalSecs`, buf) + } +} + + + + object FfiConverterTypeSpendableUtxo: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): SpendableUtxo { return SpendableUtxo( @@ -11067,6 +11265,7 @@ object FfiConverterTypeNodeError : FfiConverterRustBuffer { 60 -> NodeException.NoSpendableOutputs(FfiConverterString.read(buf)) 61 -> NodeException.CoinSelectionFailed(FfiConverterString.read(buf)) 62 -> NodeException.InvalidMnemonic(FfiConverterString.read(buf)) + 63 -> NodeException.BackgroundSyncNotEnabled(FfiConverterString.read(buf)) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } } @@ -11325,6 +11524,10 @@ object FfiConverterTypeNodeError : FfiConverterRustBuffer { buf.putInt(62) Unit } + is NodeException.BackgroundSyncNotEnabled -> { + buf.putInt(63) + Unit + } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } } @@ -13781,6 +13984,14 @@ typealias FfiConverterTypeUserChannelId = FfiConverterString +fun `batterySavingSyncIntervals`(): RuntimeSyncIntervals { + return FfiConverterTypeRuntimeSyncIntervals.lift(uniffiRustCall { uniffiRustCallStatus -> + UniffiLib.INSTANCE.uniffi_ldk_node_fn_func_battery_saving_sync_intervals( + uniffiRustCallStatus, + ) + }) +} + fun `defaultConfig`(): Config { return FfiConverterTypeConfig.lift(uniffiRustCall { uniffiRustCallStatus -> UniffiLib.INSTANCE.uniffi_ldk_node_fn_func_default_config( diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt index f3a2273c0..f8c866b61 100644 --- a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt +++ b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt @@ -330,6 +330,10 @@ interface BuilderInterface { fun `setGossipSourceRgs`(`rgsServerUrl`: kotlin.String) + fun `setLightMode`(`config`: LightModeConfig) + + fun `setLightModeMinimal`() + fun `setLiquiditySourceLsps1`(`nodeId`: PublicKey, `address`: SocketAddress, `token`: kotlin.String?) fun `setLiquiditySourceLsps2`(`nodeId`: PublicKey, `address`: SocketAddress, `token`: kotlin.String?) @@ -424,6 +428,8 @@ interface NodeInterface { @Throws(NodeException::class) fun `connect`(`nodeId`: PublicKey, `address`: SocketAddress, `persist`: kotlin.Boolean) + fun `currentSyncIntervals`(): RuntimeSyncIntervals + @Throws(NodeException::class) fun `disconnect`(`nodeId`: PublicKey) @@ -441,6 +447,8 @@ interface NodeInterface { fun `getTransactionDetails`(`txid`: Txid): TransactionDetails? + fun `isLightMode`(): kotlin.Boolean + fun `listBalances`(): BalanceDetails fun `listChannels`(): List @@ -502,6 +510,8 @@ interface NodeInterface { @Throws(NodeException::class) fun `updateChannelConfig`(`userChannelId`: UserChannelId, `counterpartyNodeId`: PublicKey, `channelConfig`: ChannelConfig) + fun `updateSyncIntervals`(`intervals`: RuntimeSyncIntervals) + fun `verifySignature`(`msg`: List, `sig`: kotlin.String, `pkey`: PublicKey): kotlin.Boolean fun `waitNextEvent`(): Event @@ -976,6 +986,21 @@ data class Lsps2ServiceConfig ( +@kotlinx.serialization.Serializable +data class LightModeConfig ( + val `singleThreadedRuntime`: kotlin.Boolean, + val `disableListening`: kotlin.Boolean, + val `disablePeerReconnection`: kotlin.Boolean, + val `disableNodeAnnouncements`: kotlin.Boolean, + val `disableRgsSync`: kotlin.Boolean, + val `disablePathfindingScoresSync`: kotlin.Boolean, + val `disableLiquidityHandler`: kotlin.Boolean +) { + companion object +} + + + @kotlinx.serialization.Serializable data class LogRecord ( val `level`: LogLevel, @@ -1099,6 +1124,21 @@ data class RoutingFees ( +@kotlinx.serialization.Serializable +data class RuntimeSyncIntervals ( + val `peerReconnectionIntervalSecs`: kotlin.ULong, + val `rgsSyncIntervalSecs`: kotlin.ULong, + val `pathfindingScoresSyncIntervalSecs`: kotlin.ULong, + val `nodeAnnouncementIntervalSecs`: kotlin.ULong, + val `onchainWalletSyncIntervalSecs`: kotlin.ULong, + val `lightningWalletSyncIntervalSecs`: kotlin.ULong, + val `feeRateCacheUpdateIntervalSecs`: kotlin.ULong +) { + companion object +} + + + @kotlinx.serialization.Serializable data class SpendableUtxo ( val `outpoint`: OutPoint, @@ -1783,6 +1823,8 @@ sealed class NodeException(message: String): kotlin.Exception(message) { class InvalidMnemonic(message: String) : NodeException(message) + class BackgroundSyncNotEnabled(message: String) : NodeException(message) + } diff --git a/bindings/kotlin/ldk-node-jvm/gradle.properties b/bindings/kotlin/ldk-node-jvm/gradle.properties index f50e6bac1..08d43fde2 100644 --- a/bindings/kotlin/ldk-node-jvm/gradle.properties +++ b/bindings/kotlin/ldk-node-jvm/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs=-Xmx1536m kotlin.code.style=official -libraryVersion=0.7.0-rc.6 +libraryVersion=0.7.0-rc.7 diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 04a5d56bb..820ec89ba 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -1,6 +1,7 @@ namespace ldk_node { Mnemonic generate_entropy_mnemonic(WordCount? word_count); Config default_config(); + RuntimeSyncIntervals battery_saving_sync_intervals(); [Throws=NodeError] sequence derive_node_secret_from_mnemonic(string mnemonic, string? passphrase); }; @@ -37,6 +38,26 @@ dictionary ElectrumSyncConfig { BackgroundSyncConfig? background_sync_config; }; +dictionary LightModeConfig { + boolean single_threaded_runtime; + boolean disable_listening; + boolean disable_peer_reconnection; + boolean disable_node_announcements; + boolean disable_rgs_sync; + boolean disable_pathfinding_scores_sync; + boolean disable_liquidity_handler; +}; + +dictionary RuntimeSyncIntervals { + u64 peer_reconnection_interval_secs; + u64 rgs_sync_interval_secs; + u64 pathfinding_scores_sync_interval_secs; + u64 node_announcement_interval_secs; + u64 onchain_wallet_sync_interval_secs; + u64 lightning_wallet_sync_interval_secs; + u64 fee_rate_cache_update_interval_secs; +}; + dictionary LSPS2ServiceConfig { string? require_token; boolean advertise_service; @@ -115,6 +136,8 @@ interface Builder { void set_node_alias(string node_alias); [Throws=BuildError] void set_async_payments_role(AsyncPaymentsRole? role); + void set_light_mode(LightModeConfig config); + void set_light_mode_minimal(); [Throws=BuildError] Node build(); [Throws=BuildError] @@ -171,6 +194,9 @@ interface Node { void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason); [Throws=NodeError] void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config); + void update_sync_intervals(RuntimeSyncIntervals intervals); + RuntimeSyncIntervals current_sync_intervals(); + boolean is_light_mode(); [Throws=NodeError] void sync_wallets(); PaymentDetails? payment([ByRef]PaymentId payment_id); @@ -383,6 +409,7 @@ enum NodeError { "NoSpendableOutputs", "CoinSelectionFailed", "InvalidMnemonic", + "BackgroundSyncNotEnabled", }; dictionary NodeStatus { diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 1deb18204..cc5473978 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ldk_node" -version = "0.7.0-rc.6" +version = "0.7.0-rc.7" authors = [ { name="Elias Rohrer", email="dev@tnull.de" }, ] diff --git a/bindings/python/src/ldk_node/ldk_node.py b/bindings/python/src/ldk_node/ldk_node.py index f357f2ed0..1fa93edc0 100644 --- a/bindings/python/src/ldk_node/ldk_node.py +++ b/bindings/python/src/ldk_node/ldk_node.py @@ -461,6 +461,8 @@ def _uniffi_check_contract_api_version(lib): raise InternalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") def _uniffi_check_api_checksums(lib): + if lib.uniffi_ldk_node_checksum_func_battery_saving_sync_intervals() != 25473: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_func_default_config() != 55381: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic() != 15067: @@ -627,6 +629,10 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_builder_set_gossip_source_rgs() != 64312: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_ldk_node_checksum_method_builder_set_light_mode() != 13621: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_ldk_node_checksum_method_builder_set_light_mode_minimal() != 8340: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps1() != 51527: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps2() != 14430: @@ -675,6 +681,8 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_connect() != 34120: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_ldk_node_checksum_method_node_current_sync_intervals() != 51918: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_disconnect() != 43538: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_event_handled() != 38712: @@ -687,6 +695,8 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_get_transaction_details() != 65000: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_ldk_node_checksum_method_node_is_light_mode() != 56992: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_list_balances() != 57528: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_list_channels() != 7954: @@ -739,6 +749,8 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_update_channel_config() != 37852: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_ldk_node_checksum_method_node_update_sync_intervals() != 6071: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_verify_signature() != 20486: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_node_wait_next_event() != 55101: @@ -1553,6 +1565,17 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): ctypes.POINTER(_UniffiRustCallStatus), ) _UniffiLib.uniffi_ldk_node_fn_method_builder_set_gossip_source_rgs.restype = None +_UniffiLib.uniffi_ldk_node_fn_method_builder_set_light_mode.argtypes = ( + ctypes.c_void_p, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_ldk_node_fn_method_builder_set_light_mode.restype = None +_UniffiLib.uniffi_ldk_node_fn_method_builder_set_light_mode_minimal.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_ldk_node_fn_method_builder_set_light_mode_minimal.restype = None _UniffiLib.uniffi_ldk_node_fn_method_builder_set_liquidity_source_lsps1.argtypes = ( ctypes.c_void_p, _UniffiRustBuffer, @@ -1761,6 +1784,11 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): ctypes.POINTER(_UniffiRustCallStatus), ) _UniffiLib.uniffi_ldk_node_fn_method_node_connect.restype = None +_UniffiLib.uniffi_ldk_node_fn_method_node_current_sync_intervals.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_ldk_node_fn_method_node_current_sync_intervals.restype = _UniffiRustBuffer _UniffiLib.uniffi_ldk_node_fn_method_node_disconnect.argtypes = ( ctypes.c_void_p, _UniffiRustBuffer, @@ -1797,6 +1825,11 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): ctypes.POINTER(_UniffiRustCallStatus), ) _UniffiLib.uniffi_ldk_node_fn_method_node_get_transaction_details.restype = _UniffiRustBuffer +_UniffiLib.uniffi_ldk_node_fn_method_node_is_light_mode.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_ldk_node_fn_method_node_is_light_mode.restype = ctypes.c_int8 _UniffiLib.uniffi_ldk_node_fn_method_node_list_balances.argtypes = ( ctypes.c_void_p, ctypes.POINTER(_UniffiRustCallStatus), @@ -1949,6 +1982,12 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): ctypes.POINTER(_UniffiRustCallStatus), ) _UniffiLib.uniffi_ldk_node_fn_method_node_update_channel_config.restype = None +_UniffiLib.uniffi_ldk_node_fn_method_node_update_sync_intervals.argtypes = ( + ctypes.c_void_p, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_ldk_node_fn_method_node_update_sync_intervals.restype = None _UniffiLib.uniffi_ldk_node_fn_method_node_verify_signature.argtypes = ( ctypes.c_void_p, _UniffiRustBuffer, @@ -2318,6 +2357,10 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): _UniffiRustBuffer, ) _UniffiLib.uniffi_ldk_node_fn_method_vssheaderprovider_get_headers.restype = ctypes.c_uint64 +_UniffiLib.uniffi_ldk_node_fn_func_battery_saving_sync_intervals.argtypes = ( + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_ldk_node_fn_func_battery_saving_sync_intervals.restype = _UniffiRustBuffer _UniffiLib.uniffi_ldk_node_fn_func_default_config.argtypes = ( ctypes.POINTER(_UniffiRustCallStatus), ) @@ -2601,6 +2644,9 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): ctypes.POINTER(_UniffiRustCallStatus), ) _UniffiLib.ffi_ldk_node_rust_future_complete_void.restype = None +_UniffiLib.uniffi_ldk_node_checksum_func_battery_saving_sync_intervals.argtypes = ( +) +_UniffiLib.uniffi_ldk_node_checksum_func_battery_saving_sync_intervals.restype = ctypes.c_uint16 _UniffiLib.uniffi_ldk_node_checksum_func_default_config.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_func_default_config.restype = ctypes.c_uint16 @@ -2850,6 +2896,12 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): _UniffiLib.uniffi_ldk_node_checksum_method_builder_set_gossip_source_rgs.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_builder_set_gossip_source_rgs.restype = ctypes.c_uint16 +_UniffiLib.uniffi_ldk_node_checksum_method_builder_set_light_mode.argtypes = ( +) +_UniffiLib.uniffi_ldk_node_checksum_method_builder_set_light_mode.restype = ctypes.c_uint16 +_UniffiLib.uniffi_ldk_node_checksum_method_builder_set_light_mode_minimal.argtypes = ( +) +_UniffiLib.uniffi_ldk_node_checksum_method_builder_set_light_mode_minimal.restype = ctypes.c_uint16 _UniffiLib.uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps1.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps1.restype = ctypes.c_uint16 @@ -2922,6 +2974,9 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): _UniffiLib.uniffi_ldk_node_checksum_method_node_connect.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_node_connect.restype = ctypes.c_uint16 +_UniffiLib.uniffi_ldk_node_checksum_method_node_current_sync_intervals.argtypes = ( +) +_UniffiLib.uniffi_ldk_node_checksum_method_node_current_sync_intervals.restype = ctypes.c_uint16 _UniffiLib.uniffi_ldk_node_checksum_method_node_disconnect.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_node_disconnect.restype = ctypes.c_uint16 @@ -2940,6 +2995,9 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): _UniffiLib.uniffi_ldk_node_checksum_method_node_get_transaction_details.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_node_get_transaction_details.restype = ctypes.c_uint16 +_UniffiLib.uniffi_ldk_node_checksum_method_node_is_light_mode.argtypes = ( +) +_UniffiLib.uniffi_ldk_node_checksum_method_node_is_light_mode.restype = ctypes.c_uint16 _UniffiLib.uniffi_ldk_node_checksum_method_node_list_balances.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_node_list_balances.restype = ctypes.c_uint16 @@ -3018,6 +3076,9 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): _UniffiLib.uniffi_ldk_node_checksum_method_node_update_channel_config.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_node_update_channel_config.restype = ctypes.c_uint16 +_UniffiLib.uniffi_ldk_node_checksum_method_node_update_sync_intervals.argtypes = ( +) +_UniffiLib.uniffi_ldk_node_checksum_method_node_update_sync_intervals.restype = ctypes.c_uint16 _UniffiLib.uniffi_ldk_node_checksum_method_node_verify_signature.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_method_node_verify_signature.restype = ctypes.c_uint16 @@ -4512,6 +4573,10 @@ def set_gossip_source_p2p(self, ): raise NotImplementedError def set_gossip_source_rgs(self, rgs_server_url: "str"): raise NotImplementedError + def set_light_mode(self, config: "LightModeConfig"): + raise NotImplementedError + def set_light_mode_minimal(self, ): + raise NotImplementedError def set_liquidity_source_lsps1(self, node_id: "PublicKey",address: "SocketAddress",token: "typing.Optional[str]"): raise NotImplementedError def set_liquidity_source_lsps2(self, node_id: "PublicKey",address: "SocketAddress",token: "typing.Optional[str]"): @@ -4825,6 +4890,25 @@ def set_gossip_source_rgs(self, rgs_server_url: "str") -> None: + def set_light_mode(self, config: "LightModeConfig") -> None: + _UniffiConverterTypeLightModeConfig.check_lower(config) + + _uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_method_builder_set_light_mode,self._uniffi_clone_pointer(), + _UniffiConverterTypeLightModeConfig.lower(config)) + + + + + + + def set_light_mode_minimal(self, ) -> None: + _uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_method_builder_set_light_mode_minimal,self._uniffi_clone_pointer(),) + + + + + + def set_liquidity_source_lsps1(self, node_id: "PublicKey",address: "SocketAddress",token: "typing.Optional[str]") -> None: _UniffiConverterTypePublicKey.check_lower(node_id) @@ -5421,6 +5505,8 @@ def config(self, ): raise NotImplementedError def connect(self, node_id: "PublicKey",address: "SocketAddress",persist: "bool"): raise NotImplementedError + def current_sync_intervals(self, ): + raise NotImplementedError def disconnect(self, node_id: "PublicKey"): raise NotImplementedError def event_handled(self, ): @@ -5433,6 +5519,8 @@ def get_address_balance(self, address_str: "str"): raise NotImplementedError def get_transaction_details(self, txid: "Txid"): raise NotImplementedError + def is_light_mode(self, ): + raise NotImplementedError def list_balances(self, ): raise NotImplementedError def list_channels(self, ): @@ -5485,6 +5573,8 @@ def unified_qr_payment(self, ): raise NotImplementedError def update_channel_config(self, user_channel_id: "UserChannelId",counterparty_node_id: "PublicKey",channel_config: "ChannelConfig"): raise NotImplementedError + def update_sync_intervals(self, intervals: "RuntimeSyncIntervals"): + raise NotImplementedError def verify_signature(self, msg: "typing.List[int]",sig: "str",pkey: "PublicKey"): raise NotImplementedError def wait_next_event(self, ): @@ -5583,6 +5673,15 @@ def connect(self, node_id: "PublicKey",address: "SocketAddress",persist: "bool") + def current_sync_intervals(self, ) -> "RuntimeSyncIntervals": + return _UniffiConverterTypeRuntimeSyncIntervals.lift( + _uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_method_node_current_sync_intervals,self._uniffi_clone_pointer(),) + ) + + + + + def disconnect(self, node_id: "PublicKey") -> None: _UniffiConverterTypePublicKey.check_lower(node_id) @@ -5652,6 +5751,15 @@ def get_transaction_details(self, txid: "Txid") -> "typing.Optional[TransactionD + def is_light_mode(self, ) -> "bool": + return _UniffiConverterBool.lift( + _uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_method_node_is_light_mode,self._uniffi_clone_pointer(),) + ) + + + + + def list_balances(self, ) -> "BalanceDetails": return _UniffiConverterTypeBalanceDetails.lift( _uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_method_node_list_balances,self._uniffi_clone_pointer(),) @@ -5958,6 +6066,17 @@ def update_channel_config(self, user_channel_id: "UserChannelId",counterparty_no + def update_sync_intervals(self, intervals: "RuntimeSyncIntervals") -> None: + _UniffiConverterTypeRuntimeSyncIntervals.check_lower(intervals) + + _uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_method_node_update_sync_intervals,self._uniffi_clone_pointer(), + _UniffiConverterTypeRuntimeSyncIntervals.lower(intervals)) + + + + + + def verify_signature(self, msg: "typing.List[int]",sig: "str",pkey: "PublicKey") -> "bool": _UniffiConverterSequenceUInt8.check_lower(msg) @@ -7855,6 +7974,77 @@ def write(value, buf): _UniffiConverterOptionalTypeBackgroundSyncConfig.write(value.background_sync_config, buf) +class LightModeConfig: + single_threaded_runtime: "bool" + disable_listening: "bool" + disable_peer_reconnection: "bool" + disable_node_announcements: "bool" + disable_rgs_sync: "bool" + disable_pathfinding_scores_sync: "bool" + disable_liquidity_handler: "bool" + def __init__(self, *, single_threaded_runtime: "bool", disable_listening: "bool", disable_peer_reconnection: "bool", disable_node_announcements: "bool", disable_rgs_sync: "bool", disable_pathfinding_scores_sync: "bool", disable_liquidity_handler: "bool"): + self.single_threaded_runtime = single_threaded_runtime + self.disable_listening = disable_listening + self.disable_peer_reconnection = disable_peer_reconnection + self.disable_node_announcements = disable_node_announcements + self.disable_rgs_sync = disable_rgs_sync + self.disable_pathfinding_scores_sync = disable_pathfinding_scores_sync + self.disable_liquidity_handler = disable_liquidity_handler + + def __str__(self): + return "LightModeConfig(single_threaded_runtime={}, disable_listening={}, disable_peer_reconnection={}, disable_node_announcements={}, disable_rgs_sync={}, disable_pathfinding_scores_sync={}, disable_liquidity_handler={})".format(self.single_threaded_runtime, self.disable_listening, self.disable_peer_reconnection, self.disable_node_announcements, self.disable_rgs_sync, self.disable_pathfinding_scores_sync, self.disable_liquidity_handler) + + def __eq__(self, other): + if self.single_threaded_runtime != other.single_threaded_runtime: + return False + if self.disable_listening != other.disable_listening: + return False + if self.disable_peer_reconnection != other.disable_peer_reconnection: + return False + if self.disable_node_announcements != other.disable_node_announcements: + return False + if self.disable_rgs_sync != other.disable_rgs_sync: + return False + if self.disable_pathfinding_scores_sync != other.disable_pathfinding_scores_sync: + return False + if self.disable_liquidity_handler != other.disable_liquidity_handler: + return False + return True + +class _UniffiConverterTypeLightModeConfig(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return LightModeConfig( + single_threaded_runtime=_UniffiConverterBool.read(buf), + disable_listening=_UniffiConverterBool.read(buf), + disable_peer_reconnection=_UniffiConverterBool.read(buf), + disable_node_announcements=_UniffiConverterBool.read(buf), + disable_rgs_sync=_UniffiConverterBool.read(buf), + disable_pathfinding_scores_sync=_UniffiConverterBool.read(buf), + disable_liquidity_handler=_UniffiConverterBool.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterBool.check_lower(value.single_threaded_runtime) + _UniffiConverterBool.check_lower(value.disable_listening) + _UniffiConverterBool.check_lower(value.disable_peer_reconnection) + _UniffiConverterBool.check_lower(value.disable_node_announcements) + _UniffiConverterBool.check_lower(value.disable_rgs_sync) + _UniffiConverterBool.check_lower(value.disable_pathfinding_scores_sync) + _UniffiConverterBool.check_lower(value.disable_liquidity_handler) + + @staticmethod + def write(value, buf): + _UniffiConverterBool.write(value.single_threaded_runtime, buf) + _UniffiConverterBool.write(value.disable_listening, buf) + _UniffiConverterBool.write(value.disable_peer_reconnection, buf) + _UniffiConverterBool.write(value.disable_node_announcements, buf) + _UniffiConverterBool.write(value.disable_rgs_sync, buf) + _UniffiConverterBool.write(value.disable_pathfinding_scores_sync, buf) + _UniffiConverterBool.write(value.disable_liquidity_handler, buf) + + class LogRecord: level: "LogLevel" args: "str" @@ -8839,6 +9029,77 @@ def write(value, buf): _UniffiConverterUInt32.write(value.proportional_millionths, buf) +class RuntimeSyncIntervals: + peer_reconnection_interval_secs: "int" + rgs_sync_interval_secs: "int" + pathfinding_scores_sync_interval_secs: "int" + node_announcement_interval_secs: "int" + onchain_wallet_sync_interval_secs: "int" + lightning_wallet_sync_interval_secs: "int" + fee_rate_cache_update_interval_secs: "int" + def __init__(self, *, peer_reconnection_interval_secs: "int", rgs_sync_interval_secs: "int", pathfinding_scores_sync_interval_secs: "int", node_announcement_interval_secs: "int", onchain_wallet_sync_interval_secs: "int", lightning_wallet_sync_interval_secs: "int", fee_rate_cache_update_interval_secs: "int"): + self.peer_reconnection_interval_secs = peer_reconnection_interval_secs + self.rgs_sync_interval_secs = rgs_sync_interval_secs + self.pathfinding_scores_sync_interval_secs = pathfinding_scores_sync_interval_secs + self.node_announcement_interval_secs = node_announcement_interval_secs + self.onchain_wallet_sync_interval_secs = onchain_wallet_sync_interval_secs + self.lightning_wallet_sync_interval_secs = lightning_wallet_sync_interval_secs + self.fee_rate_cache_update_interval_secs = fee_rate_cache_update_interval_secs + + def __str__(self): + return "RuntimeSyncIntervals(peer_reconnection_interval_secs={}, rgs_sync_interval_secs={}, pathfinding_scores_sync_interval_secs={}, node_announcement_interval_secs={}, onchain_wallet_sync_interval_secs={}, lightning_wallet_sync_interval_secs={}, fee_rate_cache_update_interval_secs={})".format(self.peer_reconnection_interval_secs, self.rgs_sync_interval_secs, self.pathfinding_scores_sync_interval_secs, self.node_announcement_interval_secs, self.onchain_wallet_sync_interval_secs, self.lightning_wallet_sync_interval_secs, self.fee_rate_cache_update_interval_secs) + + def __eq__(self, other): + if self.peer_reconnection_interval_secs != other.peer_reconnection_interval_secs: + return False + if self.rgs_sync_interval_secs != other.rgs_sync_interval_secs: + return False + if self.pathfinding_scores_sync_interval_secs != other.pathfinding_scores_sync_interval_secs: + return False + if self.node_announcement_interval_secs != other.node_announcement_interval_secs: + return False + if self.onchain_wallet_sync_interval_secs != other.onchain_wallet_sync_interval_secs: + return False + if self.lightning_wallet_sync_interval_secs != other.lightning_wallet_sync_interval_secs: + return False + if self.fee_rate_cache_update_interval_secs != other.fee_rate_cache_update_interval_secs: + return False + return True + +class _UniffiConverterTypeRuntimeSyncIntervals(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return RuntimeSyncIntervals( + peer_reconnection_interval_secs=_UniffiConverterUInt64.read(buf), + rgs_sync_interval_secs=_UniffiConverterUInt64.read(buf), + pathfinding_scores_sync_interval_secs=_UniffiConverterUInt64.read(buf), + node_announcement_interval_secs=_UniffiConverterUInt64.read(buf), + onchain_wallet_sync_interval_secs=_UniffiConverterUInt64.read(buf), + lightning_wallet_sync_interval_secs=_UniffiConverterUInt64.read(buf), + fee_rate_cache_update_interval_secs=_UniffiConverterUInt64.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterUInt64.check_lower(value.peer_reconnection_interval_secs) + _UniffiConverterUInt64.check_lower(value.rgs_sync_interval_secs) + _UniffiConverterUInt64.check_lower(value.pathfinding_scores_sync_interval_secs) + _UniffiConverterUInt64.check_lower(value.node_announcement_interval_secs) + _UniffiConverterUInt64.check_lower(value.onchain_wallet_sync_interval_secs) + _UniffiConverterUInt64.check_lower(value.lightning_wallet_sync_interval_secs) + _UniffiConverterUInt64.check_lower(value.fee_rate_cache_update_interval_secs) + + @staticmethod + def write(value, buf): + _UniffiConverterUInt64.write(value.peer_reconnection_interval_secs, buf) + _UniffiConverterUInt64.write(value.rgs_sync_interval_secs, buf) + _UniffiConverterUInt64.write(value.pathfinding_scores_sync_interval_secs, buf) + _UniffiConverterUInt64.write(value.node_announcement_interval_secs, buf) + _UniffiConverterUInt64.write(value.onchain_wallet_sync_interval_secs, buf) + _UniffiConverterUInt64.write(value.lightning_wallet_sync_interval_secs, buf) + _UniffiConverterUInt64.write(value.fee_rate_cache_update_interval_secs, buf) + + class SpendableUtxo: outpoint: "OutPoint" value_sats: "int" @@ -12012,6 +12273,11 @@ class InvalidMnemonic(_UniffiTempNodeError): def __repr__(self): return "NodeError.InvalidMnemonic({})".format(repr(str(self))) _UniffiTempNodeError.InvalidMnemonic = InvalidMnemonic # type: ignore + class BackgroundSyncNotEnabled(_UniffiTempNodeError): + + def __repr__(self): + return "NodeError.BackgroundSyncNotEnabled({})".format(repr(str(self))) + _UniffiTempNodeError.BackgroundSyncNotEnabled = BackgroundSyncNotEnabled # type: ignore NodeError = _UniffiTempNodeError # type: ignore del _UniffiTempNodeError @@ -12269,6 +12535,10 @@ def read(buf): return NodeError.InvalidMnemonic( _UniffiConverterString.read(buf), ) + if variant == 63: + return NodeError.BackgroundSyncNotEnabled( + _UniffiConverterString.read(buf), + ) raise InternalError("Raw enum value doesn't match any cases") @staticmethod @@ -12397,6 +12667,8 @@ def check_lower(value): return if isinstance(value, NodeError.InvalidMnemonic): return + if isinstance(value, NodeError.BackgroundSyncNotEnabled): + return @staticmethod def write(value, buf): @@ -12524,6 +12796,8 @@ def write(value, buf): buf.write_i32(61) if isinstance(value, NodeError.InvalidMnemonic): buf.write_i32(62) + if isinstance(value, NodeError.BackgroundSyncNotEnabled): + buf.write_i32(63) @@ -15889,6 +16163,10 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, finally: ffi_free(rust_future) +def battery_saving_sync_intervals() -> "RuntimeSyncIntervals": + return _UniffiConverterTypeRuntimeSyncIntervals.lift(_uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_func_battery_saving_sync_intervals,)) + + def default_config() -> "Config": return _UniffiConverterTypeConfig.lift(_uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_func_default_config,)) @@ -15950,6 +16228,7 @@ def generate_entropy_mnemonic(word_count: "typing.Optional[WordCount]") -> "Mnem "CustomTlvRecord", "ElectrumSyncConfig", "EsploraSyncConfig", + "LightModeConfig", "LogRecord", "LspFeeLimits", "Lsps1Bolt11PaymentInfo", @@ -15968,10 +16247,12 @@ def generate_entropy_mnemonic(word_count: "typing.Optional[WordCount]") -> "Mnem "RouteHintHop", "RouteParametersConfig", "RoutingFees", + "RuntimeSyncIntervals", "SpendableUtxo", "TransactionDetails", "TxInput", "TxOutput", + "battery_saving_sync_intervals", "default_config", "derive_node_secret_from_mnemonic", "generate_entropy_mnemonic", diff --git a/bindings/swift/Sources/LDKNode/LDKNode.swift b/bindings/swift/Sources/LDKNode/LDKNode.swift index 3b0f56064..831eee574 100644 --- a/bindings/swift/Sources/LDKNode/LDKNode.swift +++ b/bindings/swift/Sources/LDKNode/LDKNode.swift @@ -1590,6 +1590,10 @@ public protocol BuilderProtocol: AnyObject { func setGossipSourceRgs(rgsServerUrl: String) + func setLightMode(config: LightModeConfig) + + func setLightModeMinimal() + func setLiquiditySourceLsps1(nodeId: PublicKey, address: SocketAddress, token: String?) func setLiquiditySourceLsps2(nodeId: PublicKey, address: SocketAddress, token: String?) @@ -1806,6 +1810,17 @@ open class Builder: } } + open func setLightMode(config: LightModeConfig) { try! rustCall { + uniffi_ldk_node_fn_method_builder_set_light_mode(self.uniffiClonePointer(), + FfiConverterTypeLightModeConfig.lower(config), $0) + } + } + + open func setLightModeMinimal() { try! rustCall { + uniffi_ldk_node_fn_method_builder_set_light_mode_minimal(self.uniffiClonePointer(), $0) + } + } + open func setLiquiditySourceLsps1(nodeId: PublicKey, address: SocketAddress, token: String?) { try! rustCall { uniffi_ldk_node_fn_method_builder_set_liquidity_source_lsps1(self.uniffiClonePointer(), FfiConverterTypePublicKey.lower(nodeId), @@ -2471,6 +2486,8 @@ public protocol NodeProtocol: AnyObject { func connect(nodeId: PublicKey, address: SocketAddress, persist: Bool) throws + func currentSyncIntervals() -> RuntimeSyncIntervals + func disconnect(nodeId: PublicKey) throws func eventHandled() throws @@ -2483,6 +2500,8 @@ public protocol NodeProtocol: AnyObject { func getTransactionDetails(txid: Txid) -> TransactionDetails? + func isLightMode() -> Bool + func listBalances() -> BalanceDetails func listChannels() -> [ChannelDetails] @@ -2535,6 +2554,8 @@ public protocol NodeProtocol: AnyObject { func updateChannelConfig(userChannelId: UserChannelId, counterpartyNodeId: PublicKey, channelConfig: ChannelConfig) throws + func updateSyncIntervals(intervals: RuntimeSyncIntervals) + func verifySignature(msg: [UInt8], sig: String, pkey: PublicKey) -> Bool func waitNextEvent() -> Event @@ -2628,6 +2649,12 @@ open class Node: } } + open func currentSyncIntervals() -> RuntimeSyncIntervals { + return try! FfiConverterTypeRuntimeSyncIntervals.lift(try! rustCall { + uniffi_ldk_node_fn_method_node_current_sync_intervals(self.uniffiClonePointer(), $0) + }) + } + open func disconnect(nodeId: PublicKey) throws { try rustCallWithError(FfiConverterTypeNodeError.lift) { uniffi_ldk_node_fn_method_node_disconnect(self.uniffiClonePointer(), FfiConverterTypePublicKey.lower(nodeId), $0) @@ -2667,6 +2694,12 @@ open class Node: }) } + open func isLightMode() -> Bool { + return try! FfiConverterBool.lift(try! rustCall { + uniffi_ldk_node_fn_method_node_is_light_mode(self.uniffiClonePointer(), $0) + }) + } + open func listBalances() -> BalanceDetails { return try! FfiConverterTypeBalanceDetails.lift(try! rustCall { uniffi_ldk_node_fn_method_node_list_balances(self.uniffiClonePointer(), $0) @@ -2849,6 +2882,12 @@ open class Node: } } + open func updateSyncIntervals(intervals: RuntimeSyncIntervals) { try! rustCall { + uniffi_ldk_node_fn_method_node_update_sync_intervals(self.uniffiClonePointer(), + FfiConverterTypeRuntimeSyncIntervals.lower(intervals), $0) + } + } + open func verifySignature(msg: [UInt8], sig: String, pkey: PublicKey) -> Bool { return try! FfiConverterBool.lift(try! rustCall { uniffi_ldk_node_fn_method_node_verify_signature(self.uniffiClonePointer(), @@ -5724,6 +5763,107 @@ public func FfiConverterTypeLSPS2ServiceConfig_lower(_ value: Lsps2ServiceConfig return FfiConverterTypeLSPS2ServiceConfig.lower(value) } +public struct LightModeConfig { + public var singleThreadedRuntime: Bool + public var disableListening: Bool + public var disablePeerReconnection: Bool + public var disableNodeAnnouncements: Bool + public var disableRgsSync: Bool + public var disablePathfindingScoresSync: Bool + public var disableLiquidityHandler: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(singleThreadedRuntime: Bool, disableListening: Bool, disablePeerReconnection: Bool, disableNodeAnnouncements: Bool, disableRgsSync: Bool, disablePathfindingScoresSync: Bool, disableLiquidityHandler: Bool) { + self.singleThreadedRuntime = singleThreadedRuntime + self.disableListening = disableListening + self.disablePeerReconnection = disablePeerReconnection + self.disableNodeAnnouncements = disableNodeAnnouncements + self.disableRgsSync = disableRgsSync + self.disablePathfindingScoresSync = disablePathfindingScoresSync + self.disableLiquidityHandler = disableLiquidityHandler + } +} + +extension LightModeConfig: Equatable, Hashable { + public static func == (lhs: LightModeConfig, rhs: LightModeConfig) -> Bool { + if lhs.singleThreadedRuntime != rhs.singleThreadedRuntime { + return false + } + if lhs.disableListening != rhs.disableListening { + return false + } + if lhs.disablePeerReconnection != rhs.disablePeerReconnection { + return false + } + if lhs.disableNodeAnnouncements != rhs.disableNodeAnnouncements { + return false + } + if lhs.disableRgsSync != rhs.disableRgsSync { + return false + } + if lhs.disablePathfindingScoresSync != rhs.disablePathfindingScoresSync { + return false + } + if lhs.disableLiquidityHandler != rhs.disableLiquidityHandler { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(singleThreadedRuntime) + hasher.combine(disableListening) + hasher.combine(disablePeerReconnection) + hasher.combine(disableNodeAnnouncements) + hasher.combine(disableRgsSync) + hasher.combine(disablePathfindingScoresSync) + hasher.combine(disableLiquidityHandler) + } +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public struct FfiConverterTypeLightModeConfig: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LightModeConfig { + return + try LightModeConfig( + singleThreadedRuntime: FfiConverterBool.read(from: &buf), + disableListening: FfiConverterBool.read(from: &buf), + disablePeerReconnection: FfiConverterBool.read(from: &buf), + disableNodeAnnouncements: FfiConverterBool.read(from: &buf), + disableRgsSync: FfiConverterBool.read(from: &buf), + disablePathfindingScoresSync: FfiConverterBool.read(from: &buf), + disableLiquidityHandler: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: LightModeConfig, into buf: inout [UInt8]) { + FfiConverterBool.write(value.singleThreadedRuntime, into: &buf) + FfiConverterBool.write(value.disableListening, into: &buf) + FfiConverterBool.write(value.disablePeerReconnection, into: &buf) + FfiConverterBool.write(value.disableNodeAnnouncements, into: &buf) + FfiConverterBool.write(value.disableRgsSync, into: &buf) + FfiConverterBool.write(value.disablePathfindingScoresSync, into: &buf) + FfiConverterBool.write(value.disableLiquidityHandler, into: &buf) + } +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public func FfiConverterTypeLightModeConfig_lift(_ buf: RustBuffer) throws -> LightModeConfig { + return try FfiConverterTypeLightModeConfig.lift(buf) +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public func FfiConverterTypeLightModeConfig_lower(_ value: LightModeConfig) -> RustBuffer { + return FfiConverterTypeLightModeConfig.lower(value) +} + public struct LogRecord { public var level: LogLevel public var args: String @@ -6518,6 +6658,107 @@ public func FfiConverterTypeRoutingFees_lower(_ value: RoutingFees) -> RustBuffe return FfiConverterTypeRoutingFees.lower(value) } +public struct RuntimeSyncIntervals { + public var peerReconnectionIntervalSecs: UInt64 + public var rgsSyncIntervalSecs: UInt64 + public var pathfindingScoresSyncIntervalSecs: UInt64 + public var nodeAnnouncementIntervalSecs: UInt64 + public var onchainWalletSyncIntervalSecs: UInt64 + public var lightningWalletSyncIntervalSecs: UInt64 + public var feeRateCacheUpdateIntervalSecs: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(peerReconnectionIntervalSecs: UInt64, rgsSyncIntervalSecs: UInt64, pathfindingScoresSyncIntervalSecs: UInt64, nodeAnnouncementIntervalSecs: UInt64, onchainWalletSyncIntervalSecs: UInt64, lightningWalletSyncIntervalSecs: UInt64, feeRateCacheUpdateIntervalSecs: UInt64) { + self.peerReconnectionIntervalSecs = peerReconnectionIntervalSecs + self.rgsSyncIntervalSecs = rgsSyncIntervalSecs + self.pathfindingScoresSyncIntervalSecs = pathfindingScoresSyncIntervalSecs + self.nodeAnnouncementIntervalSecs = nodeAnnouncementIntervalSecs + self.onchainWalletSyncIntervalSecs = onchainWalletSyncIntervalSecs + self.lightningWalletSyncIntervalSecs = lightningWalletSyncIntervalSecs + self.feeRateCacheUpdateIntervalSecs = feeRateCacheUpdateIntervalSecs + } +} + +extension RuntimeSyncIntervals: Equatable, Hashable { + public static func == (lhs: RuntimeSyncIntervals, rhs: RuntimeSyncIntervals) -> Bool { + if lhs.peerReconnectionIntervalSecs != rhs.peerReconnectionIntervalSecs { + return false + } + if lhs.rgsSyncIntervalSecs != rhs.rgsSyncIntervalSecs { + return false + } + if lhs.pathfindingScoresSyncIntervalSecs != rhs.pathfindingScoresSyncIntervalSecs { + return false + } + if lhs.nodeAnnouncementIntervalSecs != rhs.nodeAnnouncementIntervalSecs { + return false + } + if lhs.onchainWalletSyncIntervalSecs != rhs.onchainWalletSyncIntervalSecs { + return false + } + if lhs.lightningWalletSyncIntervalSecs != rhs.lightningWalletSyncIntervalSecs { + return false + } + if lhs.feeRateCacheUpdateIntervalSecs != rhs.feeRateCacheUpdateIntervalSecs { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(peerReconnectionIntervalSecs) + hasher.combine(rgsSyncIntervalSecs) + hasher.combine(pathfindingScoresSyncIntervalSecs) + hasher.combine(nodeAnnouncementIntervalSecs) + hasher.combine(onchainWalletSyncIntervalSecs) + hasher.combine(lightningWalletSyncIntervalSecs) + hasher.combine(feeRateCacheUpdateIntervalSecs) + } +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public struct FfiConverterTypeRuntimeSyncIntervals: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RuntimeSyncIntervals { + return + try RuntimeSyncIntervals( + peerReconnectionIntervalSecs: FfiConverterUInt64.read(from: &buf), + rgsSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), + pathfindingScoresSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), + nodeAnnouncementIntervalSecs: FfiConverterUInt64.read(from: &buf), + onchainWalletSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), + lightningWalletSyncIntervalSecs: FfiConverterUInt64.read(from: &buf), + feeRateCacheUpdateIntervalSecs: FfiConverterUInt64.read(from: &buf) + ) + } + + public static func write(_ value: RuntimeSyncIntervals, into buf: inout [UInt8]) { + FfiConverterUInt64.write(value.peerReconnectionIntervalSecs, into: &buf) + FfiConverterUInt64.write(value.rgsSyncIntervalSecs, into: &buf) + FfiConverterUInt64.write(value.pathfindingScoresSyncIntervalSecs, into: &buf) + FfiConverterUInt64.write(value.nodeAnnouncementIntervalSecs, into: &buf) + FfiConverterUInt64.write(value.onchainWalletSyncIntervalSecs, into: &buf) + FfiConverterUInt64.write(value.lightningWalletSyncIntervalSecs, into: &buf) + FfiConverterUInt64.write(value.feeRateCacheUpdateIntervalSecs, into: &buf) + } +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public func FfiConverterTypeRuntimeSyncIntervals_lift(_ buf: RustBuffer) throws -> RuntimeSyncIntervals { + return try FfiConverterTypeRuntimeSyncIntervals.lift(buf) +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public func FfiConverterTypeRuntimeSyncIntervals_lower(_ value: RuntimeSyncIntervals) -> RustBuffer { + return FfiConverterTypeRuntimeSyncIntervals.lower(value) +} + public struct SpendableUtxo { public var outpoint: OutPoint public var valueSats: UInt64 @@ -8192,6 +8433,8 @@ public enum NodeError { case CoinSelectionFailed(message: String) case InvalidMnemonic(message: String) + + case BackgroundSyncNotEnabled(message: String) } #if swift(>=5.8) @@ -8451,6 +8694,10 @@ public struct FfiConverterTypeNodeError: FfiConverterRustBuffer { message: FfiConverterString.read(from: &buf) ) + case 63: return try .BackgroundSyncNotEnabled( + message: FfiConverterString.read(from: &buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase } } @@ -8581,6 +8828,8 @@ public struct FfiConverterTypeNodeError: FfiConverterRustBuffer { writeInt(&buf, Int32(61)) case .InvalidMnemonic(_ /* message is ignored*/ ): writeInt(&buf, Int32(62)) + case .BackgroundSyncNotEnabled(_ /* message is ignored*/ ): + writeInt(&buf, Int32(63)) } } } @@ -11680,6 +11929,13 @@ private func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) } } +public func batterySavingSyncIntervals() -> RuntimeSyncIntervals { + return try! FfiConverterTypeRuntimeSyncIntervals.lift(try! rustCall { + uniffi_ldk_node_fn_func_battery_saving_sync_intervals($0 + ) + }) +} + public func defaultConfig() -> Config { return try! FfiConverterTypeConfig.lift(try! rustCall { uniffi_ldk_node_fn_func_default_config($0 @@ -11720,6 +11976,9 @@ private var initializationResult: InitializationResult = { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } + if uniffi_ldk_node_checksum_func_battery_saving_sync_intervals() != 25473 { + return InitializationResult.apiChecksumMismatch + } if uniffi_ldk_node_checksum_func_default_config() != 55381 { return InitializationResult.apiChecksumMismatch } @@ -11969,6 +12228,12 @@ private var initializationResult: InitializationResult = { if uniffi_ldk_node_checksum_method_builder_set_gossip_source_rgs() != 64312 { return InitializationResult.apiChecksumMismatch } + if uniffi_ldk_node_checksum_method_builder_set_light_mode() != 13621 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_ldk_node_checksum_method_builder_set_light_mode_minimal() != 8340 { + return InitializationResult.apiChecksumMismatch + } if uniffi_ldk_node_checksum_method_builder_set_liquidity_source_lsps1() != 51527 { return InitializationResult.apiChecksumMismatch } @@ -12041,6 +12306,9 @@ private var initializationResult: InitializationResult = { if uniffi_ldk_node_checksum_method_node_connect() != 34120 { return InitializationResult.apiChecksumMismatch } + if uniffi_ldk_node_checksum_method_node_current_sync_intervals() != 51918 { + return InitializationResult.apiChecksumMismatch + } if uniffi_ldk_node_checksum_method_node_disconnect() != 43538 { return InitializationResult.apiChecksumMismatch } @@ -12059,6 +12327,9 @@ private var initializationResult: InitializationResult = { if uniffi_ldk_node_checksum_method_node_get_transaction_details() != 65000 { return InitializationResult.apiChecksumMismatch } + if uniffi_ldk_node_checksum_method_node_is_light_mode() != 56992 { + return InitializationResult.apiChecksumMismatch + } if uniffi_ldk_node_checksum_method_node_list_balances() != 57528 { return InitializationResult.apiChecksumMismatch } @@ -12137,6 +12408,9 @@ private var initializationResult: InitializationResult = { if uniffi_ldk_node_checksum_method_node_update_channel_config() != 37852 { return InitializationResult.apiChecksumMismatch } + if uniffi_ldk_node_checksum_method_node_update_sync_intervals() != 6071 { + return InitializationResult.apiChecksumMismatch + } if uniffi_ldk_node_checksum_method_node_verify_signature() != 20486 { return InitializationResult.apiChecksumMismatch } diff --git a/src/builder.rs b/src/builder.rs index d1abdc9bf..567d84c2f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -46,8 +46,9 @@ use vss_client::headers::{FixedHeaders, LnurlAuthToJwtProvider, VssHeaderProvide use crate::chain::ChainSource; use crate::config::{ default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole, - BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, - DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN, + BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, LightModeConfig, + RuntimeSyncIntervals, DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, + WALLET_KEYS_SEED_LEN, }; use crate::connection::ConnectionManager; use crate::event::EventQueue; @@ -269,6 +270,7 @@ pub struct NodeBuilder { runtime_handle: Option, pathfinding_scores_sync_config: Option, channel_data_migration: Option, + light_mode_config: Option, } impl NodeBuilder { @@ -288,6 +290,7 @@ impl NodeBuilder { let runtime_handle = None; let pathfinding_scores_sync_config = None; let channel_data_migration = None; + let light_mode_config = None; Self { config, entropy_source_config, @@ -299,6 +302,7 @@ impl NodeBuilder { async_payments_role: None, pathfinding_scores_sync_config, channel_data_migration, + light_mode_config, } } @@ -612,6 +616,36 @@ impl NodeBuilder { Ok(self) } + /// Enables light mode with the given configuration. + /// + /// Light mode reduces memory footprint and resource usage for constrained environments + /// like iOS Notification Service Extensions and Android background services. + /// + /// See [`LightModeConfig`] for available options. + pub fn set_light_mode(&mut self, config: LightModeConfig) -> &mut Self { + self.light_mode_config = Some(config); + self + } + + /// Enables light mode with minimal configuration for iOS Notification Service Extensions. + /// + /// This is equivalent to calling `set_light_mode(LightModeConfig::minimal())` and enables: + /// + /// - Single-threaded Tokio runtime + /// - All optional background tasks disabled + /// + /// The node can still: + /// - Start and stop + /// - Connect to peers manually + /// - Send and receive payments + /// - Sync wallets manually via [`Node::sync_wallets`] + /// + /// See [`LightModeConfig::minimal`] for the full configuration. + pub fn set_light_mode_minimal(&mut self) -> &mut Self { + self.light_mode_config = Some(LightModeConfig::minimal()); + self + } + /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. pub fn build(&self) -> Result { @@ -738,7 +772,9 @@ impl NodeBuilder { let runtime = if let Some(handle) = self.runtime_handle.as_ref() { Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(&logger))) } else { - Arc::new(Runtime::new(Arc::clone(&logger)).map_err(|e| { + let use_single_threaded = + self.light_mode_config.as_ref().map_or(false, |c| c.single_threaded_runtime); + Arc::new(Runtime::new(Arc::clone(&logger), use_single_threaded).map_err(|e| { log_error!(logger, "Failed to setup tokio runtime: {}", e); BuildError::RuntimeSetupFailed })?) @@ -774,6 +810,7 @@ impl NodeBuilder { self.liquidity_source_config.as_ref(), self.pathfinding_scores_sync_config.as_ref(), self.async_payments_role, + self.light_mode_config.clone(), seed_bytes, runtime, logger, @@ -789,7 +826,9 @@ impl NodeBuilder { let runtime = if let Some(handle) = self.runtime_handle.as_ref() { Arc::new(Runtime::with_handle(handle.clone(), Arc::clone(&logger))) } else { - Arc::new(Runtime::new(Arc::clone(&logger)).map_err(|e| { + let use_single_threaded = + self.light_mode_config.as_ref().map_or(false, |c| c.single_threaded_runtime); + Arc::new(Runtime::new(Arc::clone(&logger), use_single_threaded).map_err(|e| { log_error!(logger, "Failed to setup tokio runtime: {}", e); BuildError::RuntimeSetupFailed })?) @@ -809,6 +848,7 @@ impl NodeBuilder { self.liquidity_source_config.as_ref(), self.pathfinding_scores_sync_config.as_ref(), self.async_payments_role, + self.light_mode_config.clone(), seed_bytes, runtime, logger, @@ -1088,6 +1128,34 @@ impl ArcedNodeBuilder { self.inner.write().unwrap().set_async_payments_role(role).map(|_| ()) } + /// Enables light mode with the given configuration. + /// + /// Light mode reduces memory footprint and resource usage for constrained environments + /// like iOS Notification Service Extensions and Android background services. + /// + /// See [`LightModeConfig`] for available options. + pub fn set_light_mode(&self, config: LightModeConfig) { + self.inner.write().unwrap().set_light_mode(config); + } + + /// Enables light mode with minimal configuration for iOS Notification Service Extensions. + /// + /// This is equivalent to calling `set_light_mode(LightModeConfig::minimal())` and enables: + /// + /// - Single-threaded Tokio runtime + /// - All optional background tasks disabled + /// + /// The node can still: + /// - Start and stop + /// - Connect to peers manually + /// - Send and receive payments + /// - Sync wallets manually via [`Node::sync_wallets`] + /// + /// See [`LightModeConfig::minimal`] for the full configuration. + pub fn set_light_mode_minimal(&self) { + self.inner.write().unwrap().set_light_mode_minimal(); + } + /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. pub fn build(&self) -> Result, BuildError> { @@ -1183,8 +1251,8 @@ fn build_with_store_internal( gossip_source_config: Option<&GossipSourceConfig>, liquidity_source_config: Option<&LiquiditySourceConfig>, pathfinding_scores_sync_config: Option<&PathfindingScoresSyncConfig>, - async_payments_role: Option, seed_bytes: [u8; 64], runtime: Arc, - logger: Arc, kv_store: Arc, + async_payments_role: Option, light_mode_config: Option, + seed_bytes: [u8; 64], runtime: Arc, logger: Arc, kv_store: Arc, channel_data_migration: Option<&ChannelDataMigration>, ) -> Result { optionally_install_rustls_cryptoprovider(); @@ -1881,6 +1949,8 @@ fn build_with_store_internal( let pathfinding_scores_sync_url = pathfinding_scores_sync_config.map(|c| c.url.clone()); + let runtime_sync_intervals = Arc::new(RwLock::new(RuntimeSyncIntervals::default())); + Ok(Node { runtime, stop_sender, @@ -1901,6 +1971,8 @@ fn build_with_store_internal( network_graph, gossip_source, pathfinding_scores_sync_url, + light_mode_config, + runtime_sync_intervals, liquidity_source, kv_store, logger, diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 20416b422..6ef7d7f81 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -93,6 +93,7 @@ pub(crate) struct ChainSource { logger: Arc, onchain_wallet: Arc>>>, event_queue: Arc>>>>>, + sync_config_sender: Option>, } enum ChainSourceKind { @@ -285,6 +286,12 @@ impl ChainSource { kv_store: Arc, config: Arc, logger: Arc, node_metrics: Arc>, ) -> (Self, Option) { + // Create watch channel for runtime sync config updates if background sync is enabled + let sync_config_sender = sync_config.background_sync_config.as_ref().map(|cfg| { + let (tx, _) = tokio::sync::watch::channel(cfg.clone()); + tx + }); + let esplora_chain_source = EsploraChainSource::new( server_url, headers, @@ -303,6 +310,7 @@ impl ChainSource { logger, onchain_wallet: Arc::new(Mutex::new(None)), event_queue: Arc::new(Mutex::new(None)), + sync_config_sender, }, None, ) @@ -314,6 +322,12 @@ impl ChainSource { kv_store: Arc, config: Arc, logger: Arc, node_metrics: Arc>, ) -> (Self, Option) { + // Create watch channel for runtime sync config updates if background sync is enabled + let sync_config_sender = sync_config.background_sync_config.as_ref().map(|cfg| { + let (tx, _) = tokio::sync::watch::channel(cfg.clone()); + tx + }); + let electrum_chain_source = ElectrumChainSource::new( server_url, sync_config, @@ -331,6 +345,7 @@ impl ChainSource { logger, onchain_wallet: Arc::new(Mutex::new(None)), event_queue: Arc::new(Mutex::new(None)), + sync_config_sender, }, None, ) @@ -362,6 +377,7 @@ impl ChainSource { logger, onchain_wallet: Arc::new(Mutex::new(None)), event_queue: Arc::new(Mutex::new(None)), + sync_config_sender: None, }, best_block, ) @@ -394,6 +410,7 @@ impl ChainSource { logger, onchain_wallet: Arc::new(Mutex::new(None)), event_queue: Arc::new(Mutex::new(None)), + sync_config_sender: None, }, best_block, ) @@ -449,8 +466,16 @@ impl ChainSource { if let Some(background_sync_config) = esplora_chain_source.sync_config.background_sync_config.as_ref() { + // Get config receiver for runtime updates + let config_receiver = self + .sync_config_sender + .as_ref() + .expect("sync_config_sender should be set when background_sync_config is Some") + .subscribe(); + self.start_tx_based_sync_loop( stop_sync_receiver, + config_receiver, channel_manager, chain_monitor, output_sweeper, @@ -471,8 +496,16 @@ impl ChainSource { if let Some(background_sync_config) = electrum_chain_source.sync_config.background_sync_config.as_ref() { + // Get config receiver for runtime updates + let config_receiver = self + .sync_config_sender + .as_ref() + .expect("sync_config_sender should be set when background_sync_config is Some") + .subscribe(); + self.start_tx_based_sync_loop( stop_sync_receiver, + config_receiver, channel_manager, chain_monitor, output_sweeper, @@ -548,6 +581,7 @@ impl ChainSource { async fn start_tx_based_sync_loop( &self, mut stop_sync_receiver: tokio::sync::watch::Receiver<()>, + mut config_receiver: tokio::sync::watch::Receiver, channel_manager: Arc, chain_monitor: Arc, output_sweeper: Arc, background_sync_config: &BackgroundSyncConfig, logger: Arc, @@ -588,6 +622,41 @@ impl ChainSource { ); return; } + Ok(()) = config_receiver.changed() => { + let new_config = config_receiver.borrow().clone(); + log_info!( + logger, + "Background sync intervals updated: onchain={}s, lightning={}s, fee_rate={}s", + new_config.onchain_wallet_sync_interval_secs, + new_config.lightning_wallet_sync_interval_secs, + new_config.fee_rate_cache_update_interval_secs, + ); + + // Reset intervals with new durations (enforce minimum) + let new_onchain_secs = new_config + .onchain_wallet_sync_interval_secs + .max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); + onchain_wallet_sync_interval = + tokio::time::interval(Duration::from_secs(new_onchain_secs)); + onchain_wallet_sync_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + let new_fee_rate_secs = new_config + .fee_rate_cache_update_interval_secs + .max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); + fee_rate_update_interval = + tokio::time::interval(Duration::from_secs(new_fee_rate_secs)); + fee_rate_update_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + let new_lightning_secs = new_config + .lightning_wallet_sync_interval_secs + .max(WALLET_SYNC_INTERVAL_MINIMUM_SECS); + lightning_wallet_sync_interval = + tokio::time::interval(Duration::from_secs(new_lightning_secs)); + lightning_wallet_sync_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + } _ = onchain_wallet_sync_interval.tick() => { // Access event_queue from struct for event emission let event_queue = self.event_queue.lock().unwrap().clone(); @@ -620,6 +689,23 @@ impl ChainSource { *self.event_queue.lock().unwrap() = Some(event_queue); } + /// Update the background sync configuration at runtime. + /// + /// This allows changing sync intervals while the node is running. + /// Returns an error if background syncing was disabled at build time. + pub(crate) fn set_background_sync_config( + &self, config: BackgroundSyncConfig, + ) -> Result<(), Error> { + if let Some(ref sender) = self.sync_config_sender { + // Send will only fail if there are no receivers, which shouldn't happen + // while the sync loop is running + let _ = sender.send(config); + Ok(()) + } else { + Err(Error::BackgroundSyncNotEnabled) + } + } + // Synchronize the onchain wallet via transaction-based protocols (i.e., Esplora, Electrum, // etc.) with event emission support. async fn sync_onchain_wallet_with_events( diff --git a/src/config.rs b/src/config.rs index 5d77a69e7..2e2aeed0d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,9 +23,9 @@ use crate::logger::LogLevel; // Config defaults const DEFAULT_NETWORK: Network = Network::Bitcoin; -const DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS: u64 = 80; -const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30; -const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10; +pub(crate) const DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS: u64 = 80; +pub(crate) const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30; +pub(crate) const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10; const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3; const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000; @@ -57,17 +57,11 @@ pub(crate) const LDK_PAYMENT_RETRY_TIMEOUT: Duration = Duration::from_secs(10); // The interval (in block height) after which we retry archiving fully resolved channel monitors. pub(crate) const RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL: u32 = 6; -// The time in-between peer reconnection attempts. -pub(crate) const PEER_RECONNECTION_INTERVAL: Duration = Duration::from_secs(60); - -// The time in-between RGS sync attempts. -pub(crate) const RGS_SYNC_INTERVAL: Duration = Duration::from_secs(60 * 60); - -// The time in-between external scores sync attempts. -pub(crate) const EXTERNAL_PATHFINDING_SCORES_SYNC_INTERVAL: Duration = Duration::from_secs(60 * 60); - -// The time in-between node announcement broadcast attempts. -pub(crate) const NODE_ANN_BCAST_INTERVAL: Duration = Duration::from_secs(60 * 60); +// Default sync intervals (now configurable via RuntimeSyncIntervals) +pub(crate) const DEFAULT_PEER_RECONNECTION_INTERVAL_SECS: u64 = 60; +pub(crate) const DEFAULT_RGS_SYNC_INTERVAL_SECS: u64 = 60 * 60; +pub(crate) const DEFAULT_PATHFINDING_SCORES_SYNC_INTERVAL_SECS: u64 = 60 * 60; +pub(crate) const DEFAULT_NODE_ANN_BCAST_INTERVAL_SECS: u64 = 60 * 60; // The lower limit which we apply to any configured wallet sync intervals. pub(crate) const WALLET_SYNC_INTERVAL_MINIMUM_SECS: u64 = 10; @@ -567,6 +561,213 @@ pub enum AsyncPaymentsRole { Server, } +/// Configuration for light mode operation, optimized for constrained environments +/// like iOS Notification Service Extensions. +/// +/// Light mode reduces memory footprint and resource usage by disabling optional +/// background tasks and using a single-threaded runtime. This is useful for +/// environments with hard memory limits (e.g., iOS NSE's ~24MB limit). +/// +/// ### Defaults +/// +/// | Parameter | Value | +/// |-----------------------------------|-------| +/// | `single_threaded_runtime` | false | +/// | `disable_listening` | false | +/// | `disable_peer_reconnection` | false | +/// | `disable_node_announcements` | false | +/// | `disable_rgs_sync` | false | +/// | `disable_pathfinding_scores_sync` | false | +/// | `disable_liquidity_handler` | false | +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LightModeConfig { + /// Use single-threaded Tokio runtime instead of multi-threaded. + /// + /// This significantly reduces memory footprint (saves ~10-15MB) but may affect + /// concurrent task handling. Recommended for constrained environments. + pub single_threaded_runtime: bool, + + /// Disable network listeners to prevent accepting incoming connections. + /// + /// When enabled, the node will not bind to listening addresses and will only + /// be able to make outbound connections. This saves memory and is suitable + /// for mobile clients that don't need to accept inbound connections. + pub disable_listening: bool, + + /// Disable the background peer reconnection task. + /// + /// When enabled, the node will not automatically reconnect to persisted peers. + /// Peers must be connected manually via [`Node::connect`]. + /// + /// [`Node::connect`]: crate::Node::connect + pub disable_peer_reconnection: bool, + + /// Disable node announcement broadcasting. + /// + /// When enabled, the node will not broadcast node announcements to the gossip + /// network. This is suitable for private/mobile nodes that don't need to be + /// discoverable. + pub disable_node_announcements: bool, + + /// Disable Rapid Gossip Sync (RGS) background updates. + /// + /// When enabled, the node will use cached gossip data and not fetch new RGS + /// snapshots. This saves memory and network bandwidth but may result in + /// slightly stale routing information. + pub disable_rgs_sync: bool, + + /// Disable external pathfinding scores sync. + /// + /// When enabled, the node will not fetch and merge external pathfinding scores. + /// The node will use locally-computed scores only. + pub disable_pathfinding_scores_sync: bool, + + /// Disable the liquidity source handler. + /// + /// When enabled, the node will not process LSPS1/LSPS2 liquidity events in + /// the background. This is suitable when liquidity services are not needed. + pub disable_liquidity_handler: bool, +} + +impl Default for LightModeConfig { + fn default() -> Self { + Self { + single_threaded_runtime: false, + disable_listening: false, + disable_peer_reconnection: false, + disable_node_announcements: false, + disable_rgs_sync: false, + disable_pathfinding_scores_sync: false, + disable_liquidity_handler: false, + } + } +} + +impl LightModeConfig { + /// Returns a configuration optimized for minimal memory footprint. + /// + /// This preset is designed for iOS Notification Service Extensions and other + /// environments with strict memory constraints (~24MB limit). It enables: + /// + /// - Single-threaded Tokio runtime + /// - All optional background tasks disabled + /// + /// The node can still: + /// - Start and stop + /// - Connect to peers manually + /// - Send and receive payments + /// - Sync wallets manually via [`Node::sync_wallets`] + /// + /// [`Node::sync_wallets`]: crate::Node::sync_wallets + pub fn minimal() -> Self { + Self { + single_threaded_runtime: true, + disable_listening: true, + disable_peer_reconnection: true, + disable_node_announcements: true, + disable_rgs_sync: true, + disable_pathfinding_scores_sync: true, + disable_liquidity_handler: true, + } + } +} + +/// Runtime-adjustable sync intervals for background tasks. +/// +/// These intervals can be changed at runtime via [`Node::update_sync_intervals`] +/// to adjust resource usage based on application state (e.g., foreground vs background). +/// +/// ### Defaults +/// +/// | Parameter | Value (secs) | +/// |----------------------------------------|--------------| +/// | `peer_reconnection_interval_secs` | 60 | +/// | `rgs_sync_interval_secs` | 3600 | +/// | `pathfinding_scores_sync_interval_secs`| 3600 | +/// | `node_announcement_interval_secs` | 3600 | +/// | `onchain_wallet_sync_interval_secs` | 80 | +/// | `lightning_wallet_sync_interval_secs` | 30 | +/// | `fee_rate_cache_update_interval_secs` | 600 | +/// +/// [`Node::update_sync_intervals`]: crate::Node::update_sync_intervals +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RuntimeSyncIntervals { + /// Interval for peer reconnection attempts, in seconds. + pub peer_reconnection_interval_secs: u64, + + /// Interval for RGS gossip sync updates, in seconds. + pub rgs_sync_interval_secs: u64, + + /// Interval for external pathfinding scores sync, in seconds. + pub pathfinding_scores_sync_interval_secs: u64, + + /// Interval for node announcement broadcasts, in seconds. + pub node_announcement_interval_secs: u64, + + /// Interval for on-chain wallet sync, in seconds. + /// + /// **Note:** A minimum of 10 seconds is enforced. + pub onchain_wallet_sync_interval_secs: u64, + + /// Interval for Lightning wallet sync, in seconds. + /// + /// **Note:** A minimum of 10 seconds is enforced. + pub lightning_wallet_sync_interval_secs: u64, + + /// Interval for fee rate cache updates, in seconds. + /// + /// **Note:** A minimum of 10 seconds is enforced. + pub fee_rate_cache_update_interval_secs: u64, +} + +impl Default for RuntimeSyncIntervals { + fn default() -> Self { + Self { + peer_reconnection_interval_secs: DEFAULT_PEER_RECONNECTION_INTERVAL_SECS, + rgs_sync_interval_secs: DEFAULT_RGS_SYNC_INTERVAL_SECS, + pathfinding_scores_sync_interval_secs: DEFAULT_PATHFINDING_SCORES_SYNC_INTERVAL_SECS, + node_announcement_interval_secs: DEFAULT_NODE_ANN_BCAST_INTERVAL_SECS, + onchain_wallet_sync_interval_secs: DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS, + lightning_wallet_sync_interval_secs: DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS, + fee_rate_cache_update_interval_secs: DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS, + } + } +} + +impl RuntimeSyncIntervals { + /// Returns intervals optimized for battery saving in background operation. + /// + /// This preset uses longer intervals to reduce CPU and network usage: + /// - Peer reconnection: 5 minutes (was 1 minute) + /// - RGS sync: 2 hours (was 1 hour) + /// - Pathfinding scores: 4 hours (was 1 hour) + /// - Node announcements: 2 hours (was 1 hour) + /// - On-chain wallet sync: 5 minutes (was 80 seconds) + /// - Lightning wallet sync: 2 minutes (was 30 seconds) + /// - Fee rate cache: 30 minutes (was 10 minutes) + pub fn battery_saving() -> Self { + Self { + peer_reconnection_interval_secs: 300, // 5 minutes + rgs_sync_interval_secs: 7200, // 2 hours + pathfinding_scores_sync_interval_secs: 14400, // 4 hours + node_announcement_interval_secs: 7200, // 2 hours + onchain_wallet_sync_interval_secs: 300, // 5 minutes + lightning_wallet_sync_interval_secs: 120, // 2 minutes + fee_rate_cache_update_interval_secs: 1800, // 30 minutes + } + } +} + +/// Returns a [`RuntimeSyncIntervals`] object with battery-saving presets. +/// +/// See the documentation of [`RuntimeSyncIntervals::battery_saving`] for more information. +/// +/// This is mostly meant for use in bindings, in Rust this is synonymous with +/// [`RuntimeSyncIntervals::battery_saving()`]. +pub fn battery_saving_sync_intervals() -> RuntimeSyncIntervals { + RuntimeSyncIntervals::battery_saving() +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/src/error.rs b/src/error.rs index fbd44d9a2..16c602e9d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -141,6 +141,11 @@ pub enum Error { CoinSelectionFailed, /// The given mnemonic is invalid. InvalidMnemonic, + /// Background syncing is not enabled. + /// + /// This error is returned when attempting to update sync intervals but background + /// syncing was disabled at build time by setting `background_sync_config` to `None`. + BackgroundSyncNotEnabled, } impl fmt::Display for Error { @@ -230,6 +235,9 @@ impl fmt::Display for Error { Self::NoSpendableOutputs => write!(f, "The transaction has no spendable outputs."), Self::CoinSelectionFailed => write!(f, "Coin selection failed to find suitable UTXOs."), Self::InvalidMnemonic => write!(f, "The given mnemonic is invalid."), + Self::BackgroundSyncNotEnabled => { + write!(f, "Background syncing is not enabled.") + }, } } } diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 90b29d70b..4db49adc6 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -44,8 +44,8 @@ pub use vss_client::headers::{VssHeaderProvider, VssHeaderProviderError}; use crate::builder::sanitize_alias; pub use crate::config::{ - default_config, AnchorChannelsConfig, BackgroundSyncConfig, ElectrumSyncConfig, - EsploraSyncConfig, MaxDustHTLCExposure, + battery_saving_sync_intervals, default_config, AnchorChannelsConfig, BackgroundSyncConfig, + ElectrumSyncConfig, EsploraSyncConfig, MaxDustHTLCExposure, }; use crate::error::Error; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; diff --git a/src/lib.rs b/src/lib.rs index 04e9df283..75a75536c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,7 +119,7 @@ pub use builder::NodeBuilder as Builder; use chain::ChainSource; use config::{ default_user_config, may_announce_channel, AsyncPaymentsRole, ChannelConfig, Config, - NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL, + LightModeConfig, RuntimeSyncIntervals, }; use connection::ConnectionManager; pub use error::Error as NodeError; @@ -197,6 +197,8 @@ pub struct Node { network_graph: Arc, gossip_source: Arc, pathfinding_scores_sync_url: Option, + light_mode_config: Option, + runtime_sync_intervals: Arc>, liquidity_source: Option>>>, kv_store: Arc, logger: Arc, @@ -265,15 +267,19 @@ impl Node { .await; }); - if self.gossip_source.is_rgs() { + let skip_rgs_sync = + self.light_mode_config.as_ref().map_or(false, |c| c.disable_rgs_sync); + if self.gossip_source.is_rgs() && !skip_rgs_sync { let gossip_source = Arc::clone(&self.gossip_source); let gossip_sync_store = Arc::clone(&self.kv_store); let gossip_sync_logger = Arc::clone(&self.logger); let gossip_node_metrics = Arc::clone(&self.node_metrics); + let gossip_sync_intervals = Arc::clone(&self.runtime_sync_intervals); let mut stop_gossip_sync = self.stop_sender.subscribe(); self.runtime.spawn_cancellable_background_task(async move { - let mut interval = tokio::time::interval(RGS_SYNC_INTERVAL); loop { + let interval_secs = + gossip_sync_intervals.read().unwrap().rgs_sync_interval_secs; tokio::select! { _ = stop_gossip_sync.changed() => { log_debug!( @@ -282,7 +288,7 @@ impl Node { ); return; } - _ = interval.tick() => { + _ = tokio::time::sleep(Duration::from_secs(interval_secs)) => { let now = Instant::now(); match gossip_source.update_rgs_snapshot().await { Ok(updated_timestamp) => { @@ -314,145 +320,164 @@ impl Node { }); } + let skip_pathfinding_scores_sync = + self.light_mode_config.as_ref().map_or(false, |c| c.disable_pathfinding_scores_sync); if let Some(pathfinding_scores_sync_url) = self.pathfinding_scores_sync_url.as_ref() { - setup_background_pathfinding_scores_sync( - pathfinding_scores_sync_url.clone(), - Arc::clone(&self.scorer), - Arc::clone(&self.node_metrics), - Arc::clone(&self.kv_store), - Arc::clone(&self.logger), - Arc::clone(&self.runtime), - self.stop_sender.subscribe(), - ); + if !skip_pathfinding_scores_sync { + setup_background_pathfinding_scores_sync( + pathfinding_scores_sync_url.clone(), + Arc::clone(&self.scorer), + Arc::clone(&self.node_metrics), + Arc::clone(&self.kv_store), + Arc::clone(&self.logger), + Arc::clone(&self.runtime), + Arc::clone(&self.runtime_sync_intervals), + self.stop_sender.subscribe(), + ); + } } + let skip_listening = + self.light_mode_config.as_ref().map_or(false, |c| c.disable_listening); if let Some(listening_addresses) = &self.config.listening_addresses { - // Setup networking - let peer_manager_connection_handler = Arc::clone(&self.peer_manager); - let listening_logger = Arc::clone(&self.logger); - - let mut bind_addrs = Vec::with_capacity(listening_addresses.len()); - - for listening_addr in listening_addresses { - let resolved_address = listening_addr.to_socket_addrs().map_err(|e| { - log_error!( - self.logger, - "Unable to resolve listening address: {:?}. Error details: {}", - listening_addr, - e, - ); - Error::InvalidSocketAddress - })?; - - bind_addrs.extend(resolved_address); - } + if skip_listening { + log_info!(self.logger, "Skipping network listeners in light mode."); + } else { + // Setup networking + let peer_manager_connection_handler = Arc::clone(&self.peer_manager); + let listening_logger = Arc::clone(&self.logger); + + let mut bind_addrs = Vec::with_capacity(listening_addresses.len()); + + for listening_addr in listening_addresses { + let resolved_address = listening_addr.to_socket_addrs().map_err(|e| { + log_error!( + self.logger, + "Unable to resolve listening address: {:?}. Error details: {}", + listening_addr, + e, + ); + Error::InvalidSocketAddress + })?; - let logger = Arc::clone(&listening_logger); - let listeners = self.runtime.block_on(async move { - let mut listeners = Vec::new(); - - // Try to bind to all addresses - for addr in &*bind_addrs { - match tokio::net::TcpListener::bind(addr).await { - Ok(listener) => { - log_trace!(logger, "Listener bound to {}", addr); - listeners.push(listener); - }, - Err(e) => { - log_error!( - logger, - "Failed to bind to {}: {} - is something else already listening?", - addr, - e - ); - return Err(Error::InvalidSocketAddress); - }, - } + bind_addrs.extend(resolved_address); } - Ok(listeners) - })?; - - for listener in listeners { let logger = Arc::clone(&listening_logger); - let peer_mgr = Arc::clone(&peer_manager_connection_handler); - let mut stop_listen = self.stop_sender.subscribe(); - let runtime = Arc::clone(&self.runtime); - self.runtime.spawn_cancellable_background_task(async move { - loop { - tokio::select! { - _ = stop_listen.changed() => { - log_debug!( + let listeners = self.runtime.block_on(async move { + let mut listeners = Vec::new(); + + // Try to bind to all addresses + for addr in &*bind_addrs { + match tokio::net::TcpListener::bind(addr).await { + Ok(listener) => { + log_trace!(logger, "Listener bound to {}", addr); + listeners.push(listener); + }, + Err(e) => { + log_error!( logger, - "Stopping listening to inbound connections." + "Failed to bind to {}: {} - is something else already listening?", + addr, + e ); - break; - } - res = listener.accept() => { - let tcp_stream = res.unwrap().0; - let peer_mgr = Arc::clone(&peer_mgr); - runtime.spawn_cancellable_background_task(async move { - lightning_net_tokio::setup_inbound( - Arc::clone(&peer_mgr), - tcp_stream.into_std().unwrap(), - ) - .await; - }); - } + return Err(Error::InvalidSocketAddress); + }, } } - }); + + Ok(listeners) + })?; + + for listener in listeners { + let logger = Arc::clone(&listening_logger); + let peer_mgr = Arc::clone(&peer_manager_connection_handler); + let mut stop_listen = self.stop_sender.subscribe(); + let runtime = Arc::clone(&self.runtime); + self.runtime.spawn_cancellable_background_task(async move { + loop { + tokio::select! { + _ = stop_listen.changed() => { + log_debug!( + logger, + "Stopping listening to inbound connections." + ); + break; + } + res = listener.accept() => { + let tcp_stream = res.unwrap().0; + let peer_mgr = Arc::clone(&peer_mgr); + runtime.spawn_cancellable_background_task(async move { + lightning_net_tokio::setup_inbound( + Arc::clone(&peer_mgr), + tcp_stream.into_std().unwrap(), + ) + .await; + }); + } + } + } + }); + } } } // Regularly reconnect to persisted peers. - let connect_cm = Arc::clone(&self.connection_manager); - let connect_pm = Arc::clone(&self.peer_manager); - let connect_logger = Arc::clone(&self.logger); - let connect_peer_store = Arc::clone(&self.peer_store); - let mut stop_connect = self.stop_sender.subscribe(); - self.runtime.spawn_cancellable_background_task(async move { - let mut interval = tokio::time::interval(PEER_RECONNECTION_INTERVAL); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - loop { - tokio::select! { - _ = stop_connect.changed() => { - log_debug!( - connect_logger, - "Stopping reconnecting known peers." - ); - return; - } - _ = interval.tick() => { - let pm_peers = connect_pm - .list_peers() - .iter() - .map(|peer| peer.counterparty_node_id) - .collect::>(); - - for peer_info in connect_peer_store.list_peers().iter().filter(|info| !pm_peers.contains(&info.node_id)) { - let _ = connect_cm.do_connect_peer( - peer_info.node_id, - peer_info.address.clone(), - ).await; + let skip_peer_reconnection = + self.light_mode_config.as_ref().map_or(false, |c| c.disable_peer_reconnection); + if !skip_peer_reconnection { + let connect_cm = Arc::clone(&self.connection_manager); + let connect_pm = Arc::clone(&self.peer_manager); + let connect_logger = Arc::clone(&self.logger); + let connect_peer_store = Arc::clone(&self.peer_store); + let connect_sync_intervals = Arc::clone(&self.runtime_sync_intervals); + let mut stop_connect = self.stop_sender.subscribe(); + self.runtime.spawn_cancellable_background_task(async move { + loop { + let interval_secs = + connect_sync_intervals.read().unwrap().peer_reconnection_interval_secs; + tokio::select! { + _ = stop_connect.changed() => { + log_debug!( + connect_logger, + "Stopping reconnecting known peers." + ); + return; } - } + _ = tokio::time::sleep(Duration::from_secs(interval_secs)) => { + let pm_peers = connect_pm + .list_peers() + .iter() + .map(|peer| peer.counterparty_node_id) + .collect::>(); + + for peer_info in connect_peer_store.list_peers().iter().filter(|info| !pm_peers.contains(&info.node_id)) { + let _ = connect_cm.do_connect_peer( + peer_info.node_id, + peer_info.address.clone(), + ).await; + } + } + } } - } - }); + }); + } // Regularly broadcast node announcements. - let bcast_cm = Arc::clone(&self.channel_manager); - let bcast_pm = Arc::clone(&self.peer_manager); - let bcast_config = Arc::clone(&self.config); - let bcast_store = Arc::clone(&self.kv_store); - let bcast_logger = Arc::clone(&self.logger); - let bcast_node_metrics = Arc::clone(&self.node_metrics); - let mut stop_bcast = self.stop_sender.subscribe(); - let node_alias = self.config.node_alias.clone(); - if may_announce_channel(&self.config).is_ok() { + let skip_node_announcements = + self.light_mode_config.as_ref().map_or(false, |c| c.disable_node_announcements); + if may_announce_channel(&self.config).is_ok() && !skip_node_announcements { + let bcast_cm = Arc::clone(&self.channel_manager); + let bcast_pm = Arc::clone(&self.peer_manager); + let bcast_config = Arc::clone(&self.config); + let bcast_store = Arc::clone(&self.kv_store); + let bcast_logger = Arc::clone(&self.logger); + let bcast_node_metrics = Arc::clone(&self.node_metrics); + let bcast_sync_intervals = Arc::clone(&self.runtime_sync_intervals); + let mut stop_bcast = self.stop_sender.subscribe(); + let node_alias = self.config.node_alias.clone(); self.runtime.spawn_cancellable_background_task(async move { - // We check every 30 secs whether our last broadcast is NODE_ANN_BCAST_INTERVAL away. + // We check every 30 secs whether our last broadcast interval has elapsed. #[cfg(not(test))] let mut interval = tokio::time::interval(Duration::from_secs(30)); #[cfg(test)] @@ -467,10 +492,14 @@ impl Node { return; } _ = interval.tick() => { + let node_ann_interval_secs = + bcast_sync_intervals.read().unwrap().node_announcement_interval_secs; let skip_broadcast = match bcast_node_metrics.read().unwrap().latest_node_announcement_broadcast_timestamp { Some(latest_bcast_time_secs) => { // Skip if the time hasn't elapsed yet. - let next_bcast_unix_time = SystemTime::UNIX_EPOCH + Duration::from_secs(latest_bcast_time_secs) + NODE_ANN_BCAST_INTERVAL; + let next_bcast_unix_time = SystemTime::UNIX_EPOCH + + Duration::from_secs(latest_bcast_time_secs) + + Duration::from_secs(node_ann_interval_secs); next_bcast_unix_time.elapsed().is_err() } None => { @@ -623,24 +652,28 @@ impl Node { }); }); + let skip_liquidity_handler = + self.light_mode_config.as_ref().map_or(false, |c| c.disable_liquidity_handler); if let Some(liquidity_source) = self.liquidity_source.as_ref() { - let mut stop_liquidity_handler = self.stop_sender.subscribe(); - let liquidity_handler = Arc::clone(&liquidity_source); - let liquidity_logger = Arc::clone(&self.logger); - self.runtime.spawn_background_task(async move { - loop { - tokio::select! { - _ = stop_liquidity_handler.changed() => { - log_debug!( - liquidity_logger, - "Stopping processing liquidity events.", - ); - return; + if !skip_liquidity_handler { + let mut stop_liquidity_handler = self.stop_sender.subscribe(); + let liquidity_handler = Arc::clone(&liquidity_source); + let liquidity_logger = Arc::clone(&self.logger); + self.runtime.spawn_background_task(async move { + loop { + tokio::select! { + _ = stop_liquidity_handler.changed() => { + log_debug!( + liquidity_logger, + "Stopping processing liquidity events.", + ); + return; + } + _ = liquidity_handler.handle_next_event() => {} } - _ = liquidity_handler.handle_next_event() => {} } - } - }); + }); + } } log_info!(self.logger, "Startup complete."); @@ -1605,6 +1638,66 @@ impl Node { } } + /// Updates the intervals for background sync tasks at runtime. + /// + /// This allows changing sync intervals while the node is running, which is useful + /// for mobile apps that want to reduce battery usage when in the background. + /// + /// See [`RuntimeSyncIntervals`] for available interval settings and the + /// [`RuntimeSyncIntervals::battery_saving`] preset. + /// + /// **Note:** Changes take effect on the next sync cycle. Currently running sync operations + /// will complete with their original interval. + /// + /// **Note:** A minimum of 10 seconds is enforced for wallet sync intervals. + /// Values below this minimum will be silently raised to 10 seconds. + /// + /// **Note:** If `background_sync_config` was set to `None` at build time (e.g., via + /// [`EsploraSyncConfig`] or [`ElectrumSyncConfig`]), the wallet sync intervals + /// (`onchain_wallet_sync_interval_secs`, `lightning_wallet_sync_interval_secs`, + /// `fee_rate_cache_update_interval_secs`) cannot be updated at runtime. The other + /// intervals (peer reconnection, RGS, pathfinding scores, node announcements) will + /// still be updated. For iOS Notification Service Extensions where background sync + /// is disabled, use [`Node::sync_wallets`] for manual syncing instead. + /// + /// [`EsploraSyncConfig`]: crate::config::EsploraSyncConfig + /// [`ElectrumSyncConfig`]: crate::config::ElectrumSyncConfig + pub fn update_sync_intervals(&self, intervals: RuntimeSyncIntervals) { + // Update the shared RuntimeSyncIntervals for non-wallet tasks (peer reconnection, + // RGS sync, pathfinding scores, node announcements). These always work regardless + // of whether background_sync_config was enabled at build time. + *self.runtime_sync_intervals.write().unwrap() = intervals.clone(); + + // Also update chain source wallet sync intervals (onchain, lightning, fee rate). + // This will silently fail if background_sync_config was set to None at build time, + // because the sync loop and watch channel were never created. In that case, the + // wallet sync intervals in RuntimeSyncIntervals are effectively ignored, and users + // should call sync_wallets() manually instead. + let wallet_config = config::BackgroundSyncConfig { + onchain_wallet_sync_interval_secs: intervals.onchain_wallet_sync_interval_secs, + lightning_wallet_sync_interval_secs: intervals.lightning_wallet_sync_interval_secs, + fee_rate_cache_update_interval_secs: intervals.fee_rate_cache_update_interval_secs, + }; + let _ = self.chain_source.set_background_sync_config(wallet_config); + + log_info!(self.logger, "Updated runtime sync intervals."); + } + + /// Returns the current sync intervals for background tasks. + /// + /// See [`RuntimeSyncIntervals`] for the meaning of each interval. + pub fn current_sync_intervals(&self) -> RuntimeSyncIntervals { + self.runtime_sync_intervals.read().unwrap().clone() + } + + /// Returns whether the node is running in light mode. + /// + /// Light mode reduces resource usage by disabling optional background tasks. + /// See [`LightModeConfig`] for more information. + pub fn is_light_mode(&self) -> bool { + self.light_mode_config.is_some() + } + /// Retrieve the details of a specific payment with the given id. /// /// Returns `Some` if the payment was known and `None` otherwise. diff --git a/src/runtime.rs b/src/runtime.rs index 1e9883ae4..c12d8226b 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -18,6 +18,7 @@ use crate::logger::{log_debug, log_error, log_trace, LdkLogger, Logger}; pub(crate) struct Runtime { mode: RuntimeMode, + is_single_threaded: bool, background_tasks: Mutex>, cancellable_background_tasks: Mutex>, background_processor_task: Mutex>>, @@ -25,11 +26,15 @@ pub(crate) struct Runtime { } impl Runtime { - pub fn new(logger: Arc) -> Result { + pub fn new(logger: Arc, use_single_threaded: bool) -> Result { let mode = match tokio::runtime::Handle::try_current() { Ok(handle) => RuntimeMode::Handle(handle), Err(_) => { - let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + let rt = if use_single_threaded { + tokio::runtime::Builder::new_current_thread().enable_all().build()? + } else { + tokio::runtime::Builder::new_multi_thread().enable_all().build()? + }; RuntimeMode::Owned(rt) }, }; @@ -39,6 +44,7 @@ impl Runtime { Ok(Self { mode, + is_single_threaded: use_single_threaded, background_tasks, cancellable_background_tasks, background_processor_task, @@ -54,6 +60,7 @@ impl Runtime { Self { mode, + is_single_threaded: false, background_tasks, cancellable_background_tasks, background_processor_task, @@ -116,7 +123,12 @@ impl Runtime { // Since it seems to make a difference to `tokio` (see // https://docs.rs/tokio/latest/tokio/time/fn.timeout.html#panics) we make sure the futures // are always put in an `async` / `.await` closure. - tokio::task::block_in_place(move || handle.block_on(async { future.await })) + if self.is_single_threaded { + // block_in_place panics on current_thread runtime, so use block_on directly + handle.block_on(async { future.await }) + } else { + tokio::task::block_in_place(move || handle.block_on(async { future.await })) + } } pub fn abort_cancellable_background_tasks(&self) { diff --git a/src/scoring.rs b/src/scoring.rs index e85abade3..5176c0297 100644 --- a/src/scoring.rs +++ b/src/scoring.rs @@ -6,9 +6,7 @@ use lightning::routing::scoring::ChannelLiquidities; use lightning::util::ser::Readable; use lightning::{log_error, log_info, log_trace}; -use crate::config::{ - EXTERNAL_PATHFINDING_SCORES_SYNC_INTERVAL, EXTERNAL_PATHFINDING_SCORES_SYNC_TIMEOUT_SECS, -}; +use crate::config::{RuntimeSyncIntervals, EXTERNAL_PATHFINDING_SCORES_SYNC_TIMEOUT_SECS}; use crate::io::utils::write_external_pathfinding_scores_to_cache; use crate::logger::LdkLogger; use crate::runtime::Runtime; @@ -19,6 +17,7 @@ use crate::{write_node_metrics, DynStore, Logger, NodeMetrics, Scorer}; pub fn setup_background_pathfinding_scores_sync( url: String, scorer: Arc>, node_metrics: Arc>, kv_store: Arc, logger: Arc, runtime: Arc, + runtime_sync_intervals: Arc>, mut stop_receiver: tokio::sync::watch::Receiver<()>, ) { log_info!(logger, "External scores background syncing enabled from {}", url); @@ -26,8 +25,9 @@ pub fn setup_background_pathfinding_scores_sync( let logger = Arc::clone(&logger); runtime.spawn_background_processor_task(async move { - let mut interval = tokio::time::interval(EXTERNAL_PATHFINDING_SCORES_SYNC_INTERVAL); loop { + let interval_secs = + runtime_sync_intervals.read().unwrap().pathfinding_scores_sync_interval_secs; tokio::select! { _ = stop_receiver.changed() => { log_trace!( @@ -36,7 +36,7 @@ pub fn setup_background_pathfinding_scores_sync( ); return; } - _ = interval.tick() => { + _ = tokio::time::sleep(Duration::from_secs(interval_secs)) => { log_trace!( logger, "Background sync of external scores started.",