Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Commit 9f0a599

Browse files
authored
Merge pull request #174 from synonymdev/node-state-dump
feat: dump useful node info to logs on startup and each sync
2 parents 5a43e28 + addb568 commit 9f0a599

8 files changed

Lines changed: 280 additions & 116 deletions

File tree

example/Dev.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,19 @@ const Dev = (): ReactElement => {
698698
setMessage(res.value);
699699
}}
700700
/>
701+
702+
<Button
703+
title={'Node state'}
704+
onPress={async (): Promise<void> => {
705+
const res = await ldk.nodeStateDump();
706+
if (res.isErr()) {
707+
setMessage(res.error.message);
708+
return;
709+
}
710+
711+
setMessage(res.value);
712+
}}
713+
/>
701714
</View>
702715
</ScrollView>
703716

lib/android/src/main/java/com/reactnativeldk/Helpers.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,56 @@ fun UserConfig.mergeWithMap(map: ReadableMap): UserConfig {
390390
return this
391391
}
392392

393+
fun ChainMonitor.getClaimableBalancesAsJson(ignoredChannels: Array<ChannelDetails>): WritableArray {
394+
val result = Arguments.createArray()
395+
396+
get_claimable_balances(ignoredChannels).iterator().forEach { balance ->
397+
val map = Arguments.createMap()
398+
//Defaults if all castings for balance fail
399+
map.putInt("amount_satoshis", 0)
400+
map.putString("type", "Unknown")
401+
402+
(balance as? Balance.ClaimableAwaitingConfirmations)?.let { claimableAwaitingConfirmations ->
403+
map.putInt("amount_satoshis", claimableAwaitingConfirmations.amount_satoshis.toInt())
404+
map.putInt("confirmation_height", claimableAwaitingConfirmations.confirmation_height)
405+
map.putString("type", "ClaimableAwaitingConfirmations")
406+
}
407+
408+
(balance as? Balance.ClaimableOnChannelClose)?.let { claimableOnChannelClose ->
409+
map.putInt("amount_satoshis", claimableOnChannelClose.amount_satoshis.toInt())
410+
map.putString("type", "ClaimableOnChannelClose")
411+
}
412+
413+
(balance as? Balance.ContentiousClaimable)?.let { contentiousClaimable ->
414+
map.putInt("amount_satoshis", contentiousClaimable.amount_satoshis.toInt())
415+
map.putInt("timeout_height", contentiousClaimable.timeout_height)
416+
map.putString("type", "ContentiousClaimable")
417+
}
418+
419+
(balance as? Balance.CounterpartyRevokedOutputClaimable)?.let { counterpartyRevokedOutputClaimable ->
420+
map.putInt("amount_satoshis", counterpartyRevokedOutputClaimable.amount_satoshis.toInt())
421+
map.putString("type", "CounterpartyRevokedOutputClaimable")
422+
}
423+
424+
(balance as? Balance.MaybePreimageClaimableHTLC)?.let { maybePreimageClaimableHTLC ->
425+
map.putInt("amount_satoshis", maybePreimageClaimableHTLC.amount_satoshis.toInt())
426+
map.putInt("expiry_height", maybePreimageClaimableHTLC.expiry_height)
427+
map.putString("type", "MaybePreimageClaimableHTLC")
428+
}
429+
430+
(balance as? Balance.MaybeTimeoutClaimableHTLC)?.let { maybeTimeoutClaimableHTLC ->
431+
map.putInt("amount_satoshis", maybeTimeoutClaimableHTLC.amount_satoshis.toInt())
432+
map.putInt("claimable_height", maybeTimeoutClaimableHTLC.claimable_height)
433+
map.putString("type", "MaybeTimeoutClaimableHTLC")
434+
}
435+
436+
result.pushMap(map)
437+
}
438+
439+
return result
440+
}
441+
442+
393443
/// Helper for returning real network and currency as a tuple from a string
394444
fun getNetwork(chain: String): Pair<Network, Currency> {
395445
return when (chain) {

lib/android/src/main/java/com/reactnativeldk/LdkModule.kt

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import java.io.File
2424
import java.net.InetSocketAddress
2525
import java.nio.file.Files
2626
import java.nio.file.Paths
27+
import java.text.SimpleDateFormat
2728
import java.util.*
2829

2930

@@ -962,52 +963,9 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
962963
channelManager.list_channels() else
963964
arrayOf<ChannelDetails>()
964965

965-
val result = Arguments.createArray()
966966

967-
chainMonitor.get_claimable_balances(ignoredChannels).iterator().forEach { balance ->
968-
val map = Arguments.createMap()
969-
//Defaults if all castings for balance fail
970-
map.putInt("amount_satoshis", 0)
971-
map.putString("type", "Unknown")
972-
973-
(balance as? Balance.ClaimableAwaitingConfirmations)?.let { claimableAwaitingConfirmations ->
974-
map.putInt("amount_satoshis", claimableAwaitingConfirmations.amount_satoshis.toInt())
975-
map.putInt("confirmation_height", claimableAwaitingConfirmations.confirmation_height)
976-
map.putString("type", "ClaimableAwaitingConfirmations")
977-
}
978-
979-
(balance as? Balance.ClaimableOnChannelClose)?.let { claimableOnChannelClose ->
980-
map.putInt("amount_satoshis", claimableOnChannelClose.amount_satoshis.toInt())
981-
map.putString("type", "ClaimableOnChannelClose")
982-
}
983-
984-
(balance as? Balance.ContentiousClaimable)?.let { contentiousClaimable ->
985-
map.putInt("amount_satoshis", contentiousClaimable.amount_satoshis.toInt())
986-
map.putInt("timeout_height", contentiousClaimable.timeout_height)
987-
map.putString("type", "ContentiousClaimable")
988-
}
989-
990-
(balance as? Balance.CounterpartyRevokedOutputClaimable)?.let { counterpartyRevokedOutputClaimable ->
991-
map.putInt("amount_satoshis", counterpartyRevokedOutputClaimable.amount_satoshis.toInt())
992-
map.putString("type", "CounterpartyRevokedOutputClaimable")
993-
}
994-
995-
(balance as? Balance.MaybePreimageClaimableHTLC)?.let { maybePreimageClaimableHTLC ->
996-
map.putInt("amount_satoshis", maybePreimageClaimableHTLC.amount_satoshis.toInt())
997-
map.putInt("expiry_height", maybePreimageClaimableHTLC.expiry_height)
998-
map.putString("type", "MaybePreimageClaimableHTLC")
999-
}
1000967

1001-
(balance as? Balance.MaybeTimeoutClaimableHTLC)?.let { maybeTimeoutClaimableHTLC ->
1002-
map.putInt("amount_satoshis", maybeTimeoutClaimableHTLC.amount_satoshis.toInt())
1003-
map.putInt("claimable_height", maybeTimeoutClaimableHTLC.claimable_height)
1004-
map.putString("type", "MaybeTimeoutClaimableHTLC")
1005-
}
1006-
1007-
result.pushMap(map)
1008-
}
1009-
1010-
promise.resolve(result)
968+
promise.resolve(chainMonitor.getClaimableBalancesAsJson(ignoredChannels))
1011969
}
1012970

1013971
//MARK: Misc methods
@@ -1121,6 +1079,70 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
11211079
promise.resolve((res as Result_StringErrorZ_OK).res)
11221080
}
11231081

1082+
@ReactMethod
1083+
fun nodeStateDump(promise: Promise) {
1084+
val logDump: MutableList<String> = mutableListOf()
1085+
1086+
keysManager?.as_NodeSigner()?.get_node_id(Recipient.LDKRecipient_Node)?.let { pubKeyRes ->
1087+
if (pubKeyRes.is_ok) {
1088+
logDump.add("NodeID: ${(pubKeyRes as Result_PublicKeyNoneZ_OK).res.hexEncodedString()}")
1089+
}
1090+
}
1091+
1092+
channelManager?.list_channels()?.forEach { channel ->
1093+
logDump.add("Open channel:")
1094+
1095+
channel._funding_txo?._txid?.let { txId ->
1096+
logDump.add("Funding txid: ${txId.hexEncodedString()}")
1097+
}
1098+
1099+
logDump.add("Ready: ${if (channel._is_channel_ready) "YES" else "NO"}")
1100+
logDump.add("Usable: ${if (channel._is_usable) "YES" else "NO"}")
1101+
logDump.add("Balance: ${channel._balance_msat / 1000} sats")
1102+
}
1103+
1104+
chainMonitor?.getClaimableBalancesAsJson(arrayOf())?.let { claimableBalances ->
1105+
logDump.add("All claimable balances:\n $claimableBalances")
1106+
} ?: run {
1107+
logDump.add("Claimable balances unavailable. Chain monitor not set yet")
1108+
}
1109+
1110+
networkGraph?._last_rapid_gossip_sync_timestamp?.let { res ->
1111+
val syncTimestamp = if (res is Option_u32Z.Some) (res as Option_u32Z.Some).some.toLong() else 0
1112+
if (syncTimestamp == 0L) {
1113+
logDump.add("Last rapid gossip sync time: NEVER")
1114+
} else {
1115+
val date = Date(syncTimestamp * 1000)
1116+
val dateFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
1117+
logDump.add("Last rapid gossip sync time: ${dateFormatter.format(date)}")
1118+
}
1119+
} ?: run {
1120+
logDump.add("RGS last sync time unavailable.")
1121+
}
1122+
1123+
peerManager?._peer_node_ids?.let { peers ->
1124+
if (peers.isNotEmpty()) {
1125+
peers.forEach { peer ->
1126+
logDump.add("Connected peer: ${peer._a.hexEncodedString()}")
1127+
}
1128+
} else {
1129+
logDump.add("No connected peers")
1130+
}
1131+
} ?: run {
1132+
logDump.add("Connected peers unavailable. Peer manager not set.")
1133+
}
1134+
1135+
logDump.add("Storage: ${accountStoragePath}")
1136+
1137+
logDump.add("BackupClient setup: ${if (BackupClient.requiresSetup) "NO" else "YES"}")
1138+
logDump.add("Skip remote backups: ${if (BackupClient.skipRemoteBackup) "YES" else "NO"}")
1139+
1140+
val logString = "********NODE STATE********\n" + logDump.joinToString("\n") + "\n****************"
1141+
LogFile.write(logString)
1142+
1143+
promise.resolve(logString)
1144+
}
1145+
11241146
//Backup methods
11251147
@ReactMethod
11261148
fun backupSetup(seed: String, network: String, server: String, serverPubKey: String, promise: Promise) {

lib/ios/Helpers.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,69 @@ extension UserConfig {
401401
}
402402
}
403403

404+
extension ChainMonitor {
405+
func getClaimableBalancesAsJson(ignoredChannels: [Bindings.ChannelDetails]) -> [[String: Any]] {
406+
var result: [[String: Any]] = []
407+
408+
let claimableBalances = self.getClaimableBalances(ignoredChannels: ignoredChannels)
409+
for balance in claimableBalances {
410+
switch balance.getValueType() {
411+
case .ClaimableAwaitingConfirmations:
412+
let b = balance.getValueAsClaimableAwaitingConfirmations()!
413+
result.append([
414+
"amount_satoshis": b.getAmountSatoshis(),
415+
"confirmation_height": b.getConfirmationHeight(),
416+
"type": "ClaimableAwaitingConfirmations"
417+
] as [String : Any])
418+
break
419+
case .ClaimableOnChannelClose:
420+
let b = balance.getValueAsClaimableOnChannelClose()!
421+
result.append([
422+
"amount_satoshis": b.getAmountSatoshis(),
423+
"type": "ClaimableOnChannelClose",
424+
] as [String : Any])
425+
break
426+
case .ContentiousClaimable:
427+
let b = balance.getValueAsContentiousClaimable()!
428+
result.append([
429+
"amount_satoshis": b.getAmountSatoshis(),
430+
"timeout_height": b.getTimeoutHeight(),
431+
"type": "ContentiousClaimable"
432+
] as [String : Any])
433+
break
434+
case .CounterpartyRevokedOutputClaimable:
435+
let b = balance.getValueAsCounterpartyRevokedOutputClaimable()!
436+
result.append([
437+
"amount_satoshis": b.getAmountSatoshis(),
438+
"type": "CounterpartyRevokedOutputClaimable"
439+
] as [String : Any])
440+
break
441+
case .MaybePreimageClaimableHTLC:
442+
let b = balance.getValueAsMaybePreimageClaimableHtlc()!
443+
result.append([
444+
"amount_satoshis": b.getAmountSatoshis(),
445+
"expiry_height": b.getExpiryHeight(),
446+
"type": "MaybePreimageClaimableHTLC"
447+
] as [String : Any])
448+
break
449+
case .MaybeTimeoutClaimableHTLC:
450+
let b = balance.getValueAsMaybeTimeoutClaimableHtlc()!
451+
result.append([
452+
"amount_satoshis": b.getAmountSatoshis(),
453+
"claimable_height": b.getClaimableHeight(),
454+
"type": "MaybeTimeoutClaimableHTLC"
455+
] as [String : Any])
456+
break
457+
default:
458+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Unknown balance type type in claimableBalances() \(balance.getValueType())")
459+
result.append(["amount_satoshis": 0, "type": "Unknown"] as [String : Any])
460+
}
461+
}
462+
463+
return result
464+
}
465+
}
466+
404467
func handlePaymentSendFailure(_ reject: RCTPromiseRejectBlock, error: Bindings.PaymentSendFailure) {
405468
switch error.getValueType() {
406469
case .AllFailedResendSafe:

lib/ios/Ldk.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject)
160160
RCT_EXTERN_METHOD(nodeSign:(NSString *)message
161161
resolve:(RCTPromiseResolveBlock)resolve
162162
reject:(RCTPromiseRejectBlock)reject)
163+
RCT_EXTERN_METHOD(nodeStateDump:(RCTPromiseResolveBlock)resolve
164+
reject:(RCTPromiseRejectBlock)reject)
163165

164166
//MARK: Backup methods
165167
RCT_EXTERN_METHOD(backupSetup:(NSString *)seed

0 commit comments

Comments
 (0)