Skip to content

Commit afcebdf

Browse files
authored
Merge pull request #76 from synonymdev/fix/accept-stale-channel-monitors
feat: accept stale channel monitors for recovery
2 parents 1569477 + ae38ead commit afcebdf

20 files changed

Lines changed: 576 additions & 95 deletions

File tree

CHANGELOG.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
1-
# 0.7.0-rc.33 (Synonym Fork)
1+
# 0.7.0-rc.36 (Synonym Fork)
22

33
## Bug Fixes
44

5+
- Fixed orphaned channel migration blocking node startup when the existing monitor
6+
in the KV store can't be deserialized (e.g., `UnknownVersion` from a newer LDK
7+
version). The migration now skips writing and lets the node start normally,
8+
preserving the existing monitor data.
9+
- Fixed HTLC timeout force-close during stale monitor recovery. The healing keysend
10+
created HTLCs with a stale `cltv_expiry` (based on the ChannelManager's outdated
11+
best block height for users offline >24h). When chain sync caught up, LDK
12+
force-closed the channel (HTLCsTimedOut). Fix: sync the chain tip before sending
13+
healing payments so HTLCs get a valid CLTV expiry. If sync fails, skip the keysend
14+
to avoid the stale-CLTV force-close.
15+
- Fixed native crash (SIGABRT) during stale channel monitor recovery. The
16+
`CounterpartyCommitmentSecrets` store was not reset when force-syncing the
17+
monitor's `update_id`, causing `provide_secret()` to fail validation after
18+
a few commitment round-trips. The failed update triggered a
19+
`ChannelMonitorUpdateStatus` mode mismatch panic in the ChannelManager.
20+
Fix: reset the secrets store in `force_set_latest_update_id` so new secrets
21+
build a fresh, consistent tree. (rust-lightning fork change)
22+
- Added `BuildError::DangerousValue` variant to distinguish stale channel monitor failures from
23+
the 19 other `ReadFailed` causes. Apps can now catch this specific error to trigger one-shot
24+
recovery without false positives from unrelated I/O or deserialization errors.
25+
- Added `set_accept_stale_channel_monitors` builder API for recovery from channel monitor desync
26+
(e.g., after migration overwrote newer monitors with stale backup data). When enabled,
27+
force-syncs stale monitor update_ids during build, defers chain sync, and sends probes to
28+
trigger commitment round-trips that heal the monitor state. Depends on a patched rust-lightning
29+
fork (`synonymdev/rust-lightning#0.2.2-accept-stale-monitors`).
530
- Fixed cumulative change-address derivation index leak during fee estimation and dry-run
631
transaction builds. BDK's `TxBuilder::finish()` advances the internal (change) keychain index
732
each time it's called; repeated fee estimations would burn through change addresses without

Cargo.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exclude = ["bindings/uniffi-bindgen"]
44

55
[package]
66
name = "ldk-node"
7-
version = "0.7.0-rc.33"
7+
version = "0.7.0-rc.36"
88
authors = ["Elias Rohrer <dev@tnull.de>"]
99
homepage = "https://lightningdevkit.org/"
1010
license = "MIT OR Apache-2.0"
@@ -123,6 +123,19 @@ check-cfg = [
123123
name = "payments"
124124
harness = false
125125

126+
[patch.crates-io]
127+
lightning = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
128+
lightning-types = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
129+
lightning-invoice = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
130+
lightning-net-tokio = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
131+
lightning-persister = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
132+
lightning-background-processor = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
133+
lightning-rapid-gossip-sync = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
134+
lightning-block-sync = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
135+
lightning-transaction-sync = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
136+
lightning-liquidity = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
137+
lightning-macros = { git = "https://github.com/synonymdev/rust-lightning", branch = "0.2.2-accept-stale-monitors" }
138+
126139
#[patch.crates-io]
127140
#lightning = { path = "../rust-lightning/lightning" }
128141
#lightning-types = { path = "../rust-lightning/lightning-types" }

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
import PackageDescription
55

6-
let tag = "v0.7.0-rc.33"
7-
let checksum = "a6bc32bf63117e80141f9e4cc529d33e16e141460b269125f4150e1251a1108a"
6+
let tag = "v0.7.0-rc.36"
7+
let checksum = "de56fe19149808ccc5e517047ea7bf6b4d5d2c2e33d3ad539ef0155bf1aec8f7"
88
let url = "https://github.com/synonymdev/ldk-node/releases/download/\(tag)/LDKNodeFFI.xcframework.zip"
99

1010
let package = Package(

bindings/kotlin/ldk-node-android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ android.useAndroidX=true
33
android.enableJetifier=true
44
kotlin.code.style=official
55
group=com.synonym
6-
version=0.7.0-rc.33
6+
version=0.7.0-rc.36
Binary file not shown.
Binary file not shown.
Binary file not shown.

bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,6 +1517,8 @@ internal typealias UniffiVTableCallbackInterfaceVssHeaderProviderUniffiByValue =
15171517

15181518

15191519

1520+
1521+
15201522

15211523

15221524

@@ -1971,6 +1973,11 @@ internal interface UniffiLib : Library {
19711973
`headerProvider`: Pointer?,
19721974
uniffiCallStatus: UniffiRustCallStatus,
19731975
): Pointer?
1976+
fun uniffi_ldk_node_fn_method_builder_set_accept_stale_channel_monitors(
1977+
`ptr`: Pointer?,
1978+
`accept`: Byte,
1979+
uniffiCallStatus: UniffiRustCallStatus,
1980+
): Unit
19741981
fun uniffi_ldk_node_fn_method_builder_set_address_type(
19751982
`ptr`: Pointer?,
19761983
`addressType`: RustBufferByValue,
@@ -3123,6 +3130,8 @@ internal interface UniffiLib : Library {
31233130
): Short
31243131
fun uniffi_ldk_node_checksum_method_builder_build_with_vss_store_and_header_provider(
31253132
): Short
3133+
fun uniffi_ldk_node_checksum_method_builder_set_accept_stale_channel_monitors(
3134+
): Short
31263135
fun uniffi_ldk_node_checksum_method_builder_set_address_type(
31273136
): Short
31283137
fun uniffi_ldk_node_checksum_method_builder_set_address_types_to_monitor(
@@ -3614,6 +3623,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) {
36143623
if (lib.uniffi_ldk_node_checksum_method_builder_build_with_vss_store_and_header_provider() != 9090.toShort()) {
36153624
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
36163625
}
3626+
if (lib.uniffi_ldk_node_checksum_method_builder_set_accept_stale_channel_monitors() != 25727.toShort()) {
3627+
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
3628+
}
36173629
if (lib.uniffi_ldk_node_checksum_method_builder_set_address_type() != 647.toShort()) {
36183630
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
36193631
}
@@ -5780,6 +5792,18 @@ open class Builder: Disposable, BuilderInterface {
57805792
})
57815793
}
57825794

5795+
override fun `setAcceptStaleChannelMonitors`(`accept`: kotlin.Boolean) {
5796+
callWithPointer {
5797+
uniffiRustCall { uniffiRustCallStatus ->
5798+
UniffiLib.INSTANCE.uniffi_ldk_node_fn_method_builder_set_accept_stale_channel_monitors(
5799+
it,
5800+
FfiConverterBoolean.lower(`accept`),
5801+
uniffiRustCallStatus,
5802+
)
5803+
}
5804+
}
5805+
}
5806+
57835807
override fun `setAddressType`(`addressType`: AddressType) {
57845808
callWithPointer {
57855809
uniffiRustCall { uniffiRustCallStatus ->
@@ -10290,13 +10314,14 @@ object FfiConverterTypeBuildError : FfiConverterRustBuffer<BuildException> {
1029010314
7 -> BuildException.InvalidNodeAlias(FfiConverterString.read(buf))
1029110315
8 -> BuildException.RuntimeSetupFailed(FfiConverterString.read(buf))
1029210316
9 -> BuildException.ReadFailed(FfiConverterString.read(buf))
10293-
10 -> BuildException.WriteFailed(FfiConverterString.read(buf))
10294-
11 -> BuildException.StoragePathAccessFailed(FfiConverterString.read(buf))
10295-
12 -> BuildException.KvStoreSetupFailed(FfiConverterString.read(buf))
10296-
13 -> BuildException.WalletSetupFailed(FfiConverterString.read(buf))
10297-
14 -> BuildException.LoggerSetupFailed(FfiConverterString.read(buf))
10298-
15 -> BuildException.NetworkMismatch(FfiConverterString.read(buf))
10299-
16 -> BuildException.AsyncPaymentsConfigMismatch(FfiConverterString.read(buf))
10317+
10 -> BuildException.DangerousValue(FfiConverterString.read(buf))
10318+
11 -> BuildException.WriteFailed(FfiConverterString.read(buf))
10319+
12 -> BuildException.StoragePathAccessFailed(FfiConverterString.read(buf))
10320+
13 -> BuildException.KvStoreSetupFailed(FfiConverterString.read(buf))
10321+
14 -> BuildException.WalletSetupFailed(FfiConverterString.read(buf))
10322+
15 -> BuildException.LoggerSetupFailed(FfiConverterString.read(buf))
10323+
16 -> BuildException.NetworkMismatch(FfiConverterString.read(buf))
10324+
17 -> BuildException.AsyncPaymentsConfigMismatch(FfiConverterString.read(buf))
1030010325
else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
1030110326
}
1030210327
}
@@ -10343,34 +10368,38 @@ object FfiConverterTypeBuildError : FfiConverterRustBuffer<BuildException> {
1034310368
buf.putInt(9)
1034410369
Unit
1034510370
}
10346-
is BuildException.WriteFailed -> {
10371+
is BuildException.DangerousValue -> {
1034710372
buf.putInt(10)
1034810373
Unit
1034910374
}
10350-
is BuildException.StoragePathAccessFailed -> {
10375+
is BuildException.WriteFailed -> {
1035110376
buf.putInt(11)
1035210377
Unit
1035310378
}
10354-
is BuildException.KvStoreSetupFailed -> {
10379+
is BuildException.StoragePathAccessFailed -> {
1035510380
buf.putInt(12)
1035610381
Unit
1035710382
}
10358-
is BuildException.WalletSetupFailed -> {
10383+
is BuildException.KvStoreSetupFailed -> {
1035910384
buf.putInt(13)
1036010385
Unit
1036110386
}
10362-
is BuildException.LoggerSetupFailed -> {
10387+
is BuildException.WalletSetupFailed -> {
1036310388
buf.putInt(14)
1036410389
Unit
1036510390
}
10366-
is BuildException.NetworkMismatch -> {
10391+
is BuildException.LoggerSetupFailed -> {
1036710392
buf.putInt(15)
1036810393
Unit
1036910394
}
10370-
is BuildException.AsyncPaymentsConfigMismatch -> {
10395+
is BuildException.NetworkMismatch -> {
1037110396
buf.putInt(16)
1037210397
Unit
1037310398
}
10399+
is BuildException.AsyncPaymentsConfigMismatch -> {
10400+
buf.putInt(17)
10401+
Unit
10402+
}
1037410403
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
1037510404
}
1037610405
}

bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ interface BuilderInterface {
299299
@Throws(BuildException::class)
300300
fun `buildWithVssStoreAndHeaderProvider`(`vssUrl`: kotlin.String, `storeId`: kotlin.String, `headerProvider`: VssHeaderProvider): Node
301301

302+
fun `setAcceptStaleChannelMonitors`(`accept`: kotlin.Boolean)
303+
302304
fun `setAddressType`(`addressType`: AddressType)
303305

304306
fun `setAddressTypesToMonitor`(`addressTypesToMonitor`: List<AddressType>)
@@ -1294,6 +1296,8 @@ sealed class BuildException(message: String): kotlin.Exception(message) {
12941296

12951297
class ReadFailed(message: String) : BuildException(message)
12961298

1299+
class DangerousValue(message: String) : BuildException(message)
1300+
12971301
class WriteFailed(message: String) : BuildException(message)
12981302

12991303
class StoragePathAccessFailed(message: String) : BuildException(message)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
org.gradle.jvmargs=-Xmx1536m
22
kotlin.code.style=official
33
group=com.synonym
4-
version=0.7.0-rc.33
4+
version=0.7.0-rc.36

0 commit comments

Comments
 (0)