diff --git a/Cargo.toml b/Cargo.toml index 1291ed3d8..cbad375bd 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.8" authors = ["Elias Rohrer "] homepage = "https://lightningdevkit.org/" license = "MIT OR Apache-2.0" diff --git a/Package.swift b/Package.swift index 550824a6d..a66f0365c 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.8" +let checksum = "7ce9e6eb4335f40b5bb4e17e2eb94ab10e41845b41b635e47b7baca66a30b819" 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..a0b608c53 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.8 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..f16c372bb 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..38dfa39da 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..7af5ccc66 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..a465e96b7 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 @@ -1485,6 +1485,12 @@ internal typealias UniffiVTableCallbackInterfaceVssHeaderProviderUniffiByValue = + + + + + + @@ -2195,6 +2201,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, @@ -2351,6 +2361,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 +2672,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 +2899,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( @@ -3095,6 +3115,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( @@ -3159,6 +3181,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 +3298,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") } @@ -3595,6 +3622,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") } @@ -3691,6 +3721,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() != 42322.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") } @@ -6844,6 +6877,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 { @@ -7246,6 +7290,19 @@ open class Node: Disposable, NodeInterface { } } + @Throws(NodeException::class) + override fun `updateSyncIntervals`(`intervals`: RuntimeSyncIntervals) { + callWithPointer { + uniffiRustCallWithError(NodeExceptionErrorHandler) { 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 -> @@ -9659,6 +9716,31 @@ object FfiConverterTypeRoutingFees: FfiConverterRustBuffer { +object FfiConverterTypeRuntimeSyncIntervals: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): RuntimeSyncIntervals { + return RuntimeSyncIntervals( + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + ) + } + + override fun allocationSize(value: RuntimeSyncIntervals) = ( + FfiConverterULong.allocationSize(value.`onchainWalletSyncIntervalSecs`) + + FfiConverterULong.allocationSize(value.`lightningWalletSyncIntervalSecs`) + + FfiConverterULong.allocationSize(value.`feeRateCacheUpdateIntervalSecs`) + ) + + override fun write(value: RuntimeSyncIntervals, buf: ByteBuffer) { + 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 +11149,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 +11408,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 +13868,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..364bc900f 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 @@ -424,6 +424,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) @@ -502,6 +504,9 @@ interface NodeInterface { @Throws(NodeException::class) fun `updateChannelConfig`(`userChannelId`: UserChannelId, `counterpartyNodeId`: PublicKey, `channelConfig`: ChannelConfig) + @Throws(NodeException::class) + fun `updateSyncIntervals`(`intervals`: RuntimeSyncIntervals) + fun `verifySignature`(`msg`: List, `sig`: kotlin.String, `pkey`: PublicKey): kotlin.Boolean fun `waitNextEvent`(): Event @@ -1099,6 +1104,17 @@ data class RoutingFees ( +@kotlinx.serialization.Serializable +data class RuntimeSyncIntervals ( + 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 +1799,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..b5a8b8a41 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.8 diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 04a5d56bb..0182d6dea 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); }; @@ -29,6 +30,12 @@ dictionary BackgroundSyncConfig { u64 fee_rate_cache_update_interval_secs; }; +dictionary RuntimeSyncIntervals { + u64 onchain_wallet_sync_interval_secs; + u64 lightning_wallet_sync_interval_secs; + u64 fee_rate_cache_update_interval_secs; +}; + dictionary EsploraSyncConfig { BackgroundSyncConfig? background_sync_config; }; @@ -185,6 +192,9 @@ interface Node { boolean verify_signature([ByRef]sequence msg, [ByRef]string sig, [ByRef]PublicKey pkey); [Throws=NodeError] bytes export_pathfinding_scores(); + [Throws=NodeError] + void update_sync_intervals(RuntimeSyncIntervals intervals); + RuntimeSyncIntervals current_sync_intervals(); }; [Enum] @@ -383,6 +393,7 @@ enum NodeError { "NoSpendableOutputs", "CoinSelectionFailed", "InvalidMnemonic", + "BackgroundSyncNotEnabled", }; dictionary NodeStatus { diff --git a/bindings/python/src/ldk_node/ldk_node.py b/bindings/python/src/ldk_node/ldk_node.py index f357f2ed0..6ab7cb973 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: @@ -675,6 +677,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: @@ -739,6 +743,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() != 42322: + 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: @@ -1761,6 +1767,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, @@ -1949,6 +1960,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 +2335,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 +2622,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 @@ -2922,6 +2946,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 @@ -3018,6 +3045,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 @@ -5421,6 +5451,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, ): @@ -5485,6 +5517,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 +5617,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) @@ -5958,6 +6001,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_with_error(_UniffiConverterTypeNodeError,_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) @@ -8839,6 +8893,49 @@ def write(value, buf): _UniffiConverterUInt32.write(value.proportional_millionths, buf) +class RuntimeSyncIntervals: + onchain_wallet_sync_interval_secs: "int" + lightning_wallet_sync_interval_secs: "int" + fee_rate_cache_update_interval_secs: "int" + def __init__(self, *, onchain_wallet_sync_interval_secs: "int", lightning_wallet_sync_interval_secs: "int", fee_rate_cache_update_interval_secs: "int"): + 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(onchain_wallet_sync_interval_secs={}, lightning_wallet_sync_interval_secs={}, fee_rate_cache_update_interval_secs={})".format(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.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( + 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.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.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 +12109,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 +12371,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 +12503,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 +12632,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 +15999,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,)) @@ -15968,10 +16082,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..f4bfdca10 100644 --- a/bindings/swift/Sources/LDKNode/LDKNode.swift +++ b/bindings/swift/Sources/LDKNode/LDKNode.swift @@ -2471,6 +2471,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 @@ -2535,6 +2537,8 @@ public protocol NodeProtocol: AnyObject { func updateChannelConfig(userChannelId: UserChannelId, counterpartyNodeId: PublicKey, channelConfig: ChannelConfig) throws + func updateSyncIntervals(intervals: RuntimeSyncIntervals) throws + func verifySignature(msg: [UInt8], sig: String, pkey: PublicKey) -> Bool func waitNextEvent() -> Event @@ -2628,6 +2632,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) @@ -2849,6 +2859,12 @@ open class Node: } } + open func updateSyncIntervals(intervals: RuntimeSyncIntervals) throws { try rustCallWithError(FfiConverterTypeNodeError.lift) { + 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(), @@ -6518,6 +6534,75 @@ public func FfiConverterTypeRoutingFees_lower(_ value: RoutingFees) -> RustBuffe return FfiConverterTypeRoutingFees.lower(value) } +public struct RuntimeSyncIntervals { + 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(onchainWalletSyncIntervalSecs: UInt64, lightningWalletSyncIntervalSecs: UInt64, feeRateCacheUpdateIntervalSecs: UInt64) { + self.onchainWalletSyncIntervalSecs = onchainWalletSyncIntervalSecs + self.lightningWalletSyncIntervalSecs = lightningWalletSyncIntervalSecs + self.feeRateCacheUpdateIntervalSecs = feeRateCacheUpdateIntervalSecs + } +} + +extension RuntimeSyncIntervals: Equatable, Hashable { + public static func == (lhs: RuntimeSyncIntervals, rhs: RuntimeSyncIntervals) -> Bool { + 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(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( + 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.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 +8277,8 @@ public enum NodeError { case CoinSelectionFailed(message: String) case InvalidMnemonic(message: String) + + case BackgroundSyncNotEnabled(message: String) } #if swift(>=5.8) @@ -8451,6 +8538,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 +8672,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 +11773,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 +11820,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 } @@ -12041,6 +12144,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 } @@ -12137,6 +12243,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() != 42322 { + 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..7c393d336 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -47,7 +47,8 @@ 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, + RuntimeSyncIntervals, DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, + WALLET_KEYS_SEED_LEN, }; use crate::connection::ConnectionManager; use crate::event::EventQueue; @@ -1912,6 +1913,7 @@ fn build_with_store_internal( node_metrics, om_mailbox, async_payments_role, + runtime_sync_intervals: Arc::new(RwLock::new(RuntimeSyncIntervals::default())), }) } 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..883627f80 100644 --- a/src/config.rs +++ b/src/config.rs @@ -387,6 +387,86 @@ impl Default for BackgroundSyncConfig { } } +/// Runtime-adjustable sync intervals for background wallet syncing. +/// +/// This struct allows updating sync intervals after `node.start()` has been called, +/// which is useful for mobile apps that want to reduce battery usage when in the background. +/// +/// ### Defaults +/// +/// | Parameter | Value (secs) | +/// |----------------------------------------|--------------| +/// | `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 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 { + 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: + /// - 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) + /// + /// Ideal for Android foreground services when the app goes to background. + pub fn battery_saving() -> Self { + Self { + 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 + } + } +} + +impl From for BackgroundSyncConfig { + fn from(intervals: RuntimeSyncIntervals) -> Self { + Self { + 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, + } + } +} + +/// 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() +} + /// Configuration for syncing with an Esplora backend. /// /// Background syncing is enabled by default, using the default values specified in diff --git a/src/error.rs b/src/error.rs index fbd44d9a2..c122cda04 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,7 @@ 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/lib.rs b/src/lib.rs index 04e9df283..5683b067d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,9 +117,10 @@ pub use builder::{BuildError, ChannelDataMigration}; #[cfg(not(feature = "uniffi"))] pub use builder::NodeBuilder as Builder; use chain::ChainSource; +pub use config::{battery_saving_sync_intervals, RuntimeSyncIntervals}; use config::{ - default_user_config, may_announce_channel, AsyncPaymentsRole, ChannelConfig, Config, - NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL, + default_user_config, may_announce_channel, AsyncPaymentsRole, BackgroundSyncConfig, + ChannelConfig, Config, NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL, }; use connection::ConnectionManager; pub use error::Error as NodeError; @@ -208,6 +209,7 @@ pub struct Node { node_metrics: Arc>, om_mailbox: Option>, async_payments_role: Option, + runtime_sync_intervals: Arc>, } impl Node { @@ -1605,6 +1607,46 @@ impl Node { } } + /// Updates the intervals for background wallet 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 + /// cannot be updated at runtime and this method will return an error. + /// In that case, use [`Node::sync_wallets`] for manual syncing instead. + /// + /// [`EsploraSyncConfig`]: crate::config::EsploraSyncConfig + /// [`ElectrumSyncConfig`]: crate::config::ElectrumSyncConfig + pub fn update_sync_intervals(&self, intervals: RuntimeSyncIntervals) -> Result<(), Error> { + // Update the shared RuntimeSyncIntervals + *self.runtime_sync_intervals.write().unwrap() = intervals.clone(); + + // Update chain source wallet sync intervals + let wallet_config = BackgroundSyncConfig::from(intervals); + self.chain_source.set_background_sync_config(wallet_config)?; + + log_info!(self.logger, "Updated runtime sync intervals."); + Ok(()) + } + + /// Returns the current sync intervals for background wallet sync tasks. + /// + /// See [`RuntimeSyncIntervals`] for the meaning of each interval. + pub fn current_sync_intervals(&self) -> RuntimeSyncIntervals { + self.runtime_sync_intervals.read().unwrap().clone() + } + /// Retrieve the details of a specific payment with the given id. /// /// Returns `Some` if the payment was known and `None` otherwise.