Skip to content

Commit 067f7b6

Browse files
authored
Merge pull request #227 from synonymdev/list-channel-monitors
feat: list all channel monitors
2 parents d99ad68 + 0332c9b commit 067f7b6

File tree

8 files changed

+217
-66
lines changed

8 files changed

+217
-66
lines changed

example/Dev.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,23 @@ const Dev = (): ReactElement => {
587587
}}
588588
/>
589589

590+
<Button
591+
title={'List channel monitors'}
592+
onPress={async (): Promise<void> => {
593+
const monitorsRes = await ldk.listChannelMonitors(true);
594+
if (monitorsRes.isErr()) {
595+
return setMessage(monitorsRes.error.message);
596+
}
597+
598+
let msg = `Channel Monitors (${monitorsRes.value.length}): \n`;
599+
monitorsRes.value.forEach((monitor) => {
600+
msg += `\n\n${JSON.stringify(monitor)}`;
601+
});
602+
603+
setMessage(msg);
604+
}}
605+
/>
606+
590607
<Button
591608
title={'Show version'}
592609
onPress={async (): Promise<void> => {

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

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,21 @@ val RouteHop.asJson: WritableMap
199199
return hop
200200
}
201201

202+
203+
fun ChannelMonitor.asJson(channelId: String): WritableMap {
204+
val result = Arguments.createMap()
205+
result.putString("channel_id", channelId)
206+
result.putHexString("funding_txo", _funding_txo._b)
207+
result.putHexString("counterparty_node_id", _counterparty_node_id)
208+
209+
val balances = Arguments.createArray()
210+
_claimable_balances.iterator().forEach { balance ->
211+
balances.pushMap(balance.asJson)
212+
}
213+
result.putArray("claimable_balances", balances)
214+
return result
215+
}
216+
202217
fun WritableMap.putHexString(key: String, bytes: ByteArray?) {
203218
if (bytes != null) {
204219
putString(key, bytes.hexEncodedString())
@@ -437,56 +452,60 @@ fun UserConfig.mergeWithMap(map: ReadableMap): UserConfig {
437452
return this
438453
}
439454

440-
fun ChainMonitor.getClaimableBalancesAsJson(ignoredChannels: Array<ChannelDetails>): WritableArray {
441-
val result = Arguments.createArray()
442-
443-
get_claimable_balances(ignoredChannels).iterator().forEach { balance ->
455+
val Balance.asJson: WritableMap
456+
get() {
444457
val map = Arguments.createMap()
445458
//Defaults if all castings for balance fail
446459
map.putInt("amount_satoshis", 0)
447460
map.putString("type", "Unknown")
448461

449-
(balance as? Balance.ClaimableAwaitingConfirmations)?.let { claimableAwaitingConfirmations ->
462+
(this as? Balance.ClaimableAwaitingConfirmations)?.let { claimableAwaitingConfirmations ->
450463
map.putInt("amount_satoshis", claimableAwaitingConfirmations.amount_satoshis.toInt())
451464
map.putInt("confirmation_height", claimableAwaitingConfirmations.confirmation_height)
452465
map.putString("type", "ClaimableAwaitingConfirmations")
453466
}
454467

455-
(balance as? Balance.ClaimableOnChannelClose)?.let { claimableOnChannelClose ->
468+
(this as? Balance.ClaimableOnChannelClose)?.let { claimableOnChannelClose ->
456469
map.putInt("amount_satoshis", claimableOnChannelClose.amount_satoshis.toInt())
457470
map.putString("type", "ClaimableOnChannelClose")
458471
}
459472

460-
(balance as? Balance.ContentiousClaimable)?.let { contentiousClaimable ->
473+
(this as? Balance.ContentiousClaimable)?.let { contentiousClaimable ->
461474
map.putInt("amount_satoshis", contentiousClaimable.amount_satoshis.toInt())
462475
map.putInt("timeout_height", contentiousClaimable.timeout_height)
463476
map.putString("type", "ContentiousClaimable")
464477
}
465478

466-
(balance as? Balance.CounterpartyRevokedOutputClaimable)?.let { counterpartyRevokedOutputClaimable ->
479+
(this as? Balance.CounterpartyRevokedOutputClaimable)?.let { counterpartyRevokedOutputClaimable ->
467480
map.putInt("amount_satoshis", counterpartyRevokedOutputClaimable.amount_satoshis.toInt())
468481
map.putString("type", "CounterpartyRevokedOutputClaimable")
469482
}
470483

471-
(balance as? Balance.MaybePreimageClaimableHTLC)?.let { maybePreimageClaimableHTLC ->
484+
(this as? Balance.MaybePreimageClaimableHTLC)?.let { maybePreimageClaimableHTLC ->
472485
map.putInt("amount_satoshis", maybePreimageClaimableHTLC.amount_satoshis.toInt())
473486
map.putInt("expiry_height", maybePreimageClaimableHTLC.expiry_height)
474487
map.putString("type", "MaybePreimageClaimableHTLC")
475488
}
476489

477-
(balance as? Balance.MaybeTimeoutClaimableHTLC)?.let { maybeTimeoutClaimableHTLC ->
490+
(this as? Balance.MaybeTimeoutClaimableHTLC)?.let { maybeTimeoutClaimableHTLC ->
478491
map.putInt("amount_satoshis", maybeTimeoutClaimableHTLC.amount_satoshis.toInt())
479492
map.putInt("claimable_height", maybeTimeoutClaimableHTLC.claimable_height)
480493
map.putString("type", "MaybeTimeoutClaimableHTLC")
481494
}
482495

483-
result.pushMap(map)
496+
return map
497+
}
498+
499+
fun ChainMonitor.getClaimableBalancesAsJson(ignoredChannels: Array<ChannelDetails>): WritableArray {
500+
val result = Arguments.createArray()
501+
502+
get_claimable_balances(ignoredChannels).iterator().forEach { balance ->
503+
result.pushMap(balance.asJson)
484504
}
485505

486506
return result
487507
}
488508

489-
490509
/// Helper for returning real network and currency as a tuple from a string
491510
fun getNetwork(chain: String): Pair<Network, Currency> {
492511
return when (chain) {

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,40 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
940940
promise.resolve(list)
941941
}
942942

943+
@ReactMethod
944+
fun listChannelMonitors(ignoreOpenChannels: Boolean, promise: Promise) {
945+
val channelManager = channelManager ?: return handleReject(promise, LdkErrors.init_channel_manager)
946+
val keysManager = keysManager ?: return handleReject(promise, LdkErrors.init_keys_manager)
947+
if (channelStoragePath == "") {
948+
return handleReject(promise, LdkErrors.init_storage_path)
949+
}
950+
951+
val ignoredChannels = if (ignoreOpenChannels)
952+
channelManager.list_channels().map { it._channel_id.hexEncodedString() }.toTypedArray() else
953+
arrayOf()
954+
955+
val channelFiles = File(channelStoragePath).listFiles()
956+
957+
val result = Arguments.createArray()
958+
for (channelFile in channelFiles) {
959+
val channelId = channelFile.nameWithoutExtension
960+
961+
//Ignore open channels
962+
if (ignoredChannels.contains(channelId)) {
963+
continue
964+
}
965+
966+
val channelMonitor = UtilMethods.C2Tuple_ThirtyTwoBytesChannelMonitorZ_read(channelFile.readBytes(), keysManager!!.inner.as_EntropySource(), SignerProvider.new_impl(keysManager!!.signerProvider))
967+
968+
if (channelMonitor.is_ok) {
969+
val channelMonitorResult = (channelMonitor as Result_C2Tuple_ThirtyTwoBytesChannelMonitorZDecodeErrorZ_OK)
970+
result.pushMap(channelMonitorResult.res._b.asJson(channelMonitorResult.res._a.hexEncodedString()))
971+
}
972+
}
973+
974+
promise.resolve(result)
975+
}
976+
943977
@ReactMethod
944978
fun networkGraphListNodeIds(promise: Promise) {
945979
val graph = networkGraph?.read_only() ?: return handleReject(promise, LdkErrors.init_network_graph)
@@ -1007,8 +1041,6 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
10071041
channelManager.list_channels() else
10081042
arrayOf<ChannelDetails>()
10091043

1010-
1011-
10121044
promise.resolve(chainMonitor.getClaimableBalancesAsJson(ignoredChannels))
10131045
}
10141046

lib/ios/Helpers.swift

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,17 @@ extension LightningDevKit.RouteHop {
187187
}
188188
}
189189

190+
extension ChannelMonitor {
191+
func asJson(channelId: String) -> [String: Any?] {
192+
return [
193+
"channel_id": channelId,
194+
"funding_txo": Data(getFundingTxo().1).hexEncodedString(),
195+
"counterparty_node_id": Data(getCounterpartyNodeId() ?? []).hexEncodedString(),
196+
"claimable_balances": getClaimableBalances().map({ $0.asJson })
197+
]
198+
}
199+
}
200+
190201
extension Data {
191202
struct HexEncodingOptions: OptionSet {
192203
let rawValue: Int
@@ -401,63 +412,63 @@ extension UserConfig {
401412
}
402413
}
403414

415+
extension Balance {
416+
var asJson: [String: Any] {
417+
switch getValueType() {
418+
case .ClaimableAwaitingConfirmations:
419+
let b = getValueAsClaimableAwaitingConfirmations()!
420+
return [
421+
"amount_satoshis": b.getAmountSatoshis(),
422+
"confirmation_height": b.getConfirmationHeight(),
423+
"type": "ClaimableAwaitingConfirmations"
424+
] as [String : Any]
425+
case .ClaimableOnChannelClose:
426+
let b = getValueAsClaimableOnChannelClose()!
427+
return [
428+
"amount_satoshis": b.getAmountSatoshis(),
429+
"type": "ClaimableOnChannelClose",
430+
] as [String : Any]
431+
case .ContentiousClaimable:
432+
let b = getValueAsContentiousClaimable()!
433+
return [
434+
"amount_satoshis": b.getAmountSatoshis(),
435+
"timeout_height": b.getTimeoutHeight(),
436+
"type": "ContentiousClaimable"
437+
] as [String : Any]
438+
case .CounterpartyRevokedOutputClaimable:
439+
let b = getValueAsCounterpartyRevokedOutputClaimable()!
440+
return [
441+
"amount_satoshis": b.getAmountSatoshis(),
442+
"type": "CounterpartyRevokedOutputClaimable"
443+
] as [String : Any]
444+
case .MaybePreimageClaimableHTLC:
445+
let b = getValueAsMaybePreimageClaimableHtlc()!
446+
return [
447+
"amount_satoshis": b.getAmountSatoshis(),
448+
"expiry_height": b.getExpiryHeight(),
449+
"type": "MaybePreimageClaimableHTLC"
450+
] as [String : Any]
451+
case .MaybeTimeoutClaimableHTLC:
452+
let b = getValueAsMaybeTimeoutClaimableHtlc()!
453+
return [
454+
"amount_satoshis": b.getAmountSatoshis(),
455+
"claimable_height": b.getClaimableHeight(),
456+
"type": "MaybeTimeoutClaimableHTLC"
457+
] as [String : Any]
458+
default:
459+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Unknown balance type type in claimableBalances() \(getValueType())")
460+
return ["amount_satoshis": 0, "type": "Unknown"] as [String : Any]
461+
}
462+
}
463+
}
464+
404465
extension ChainMonitor {
405466
func getClaimableBalancesAsJson(ignoredChannels: [Bindings.ChannelDetails]) -> [[String: Any]] {
406467
var result: [[String: Any]] = []
407468

408469
let claimableBalances = self.getClaimableBalances(ignoredChannels: ignoredChannels)
409470
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-
}
471+
result.append(balance.asJson)
461472
}
462473

463474
return result

lib/ios/Ldk.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject)
108108
reject:(RCTPromiseRejectBlock)reject)
109109
RCT_EXTERN_METHOD(listChannelFiles:(RCTPromiseResolveBlock)resolve
110110
reject:(RCTPromiseRejectBlock)reject)
111+
RCT_EXTERN_METHOD(listChannelMonitors:(BOOL *)ignoreOpenChannels
112+
resolve:(RCTPromiseResolveBlock)resolve
113+
reject:(RCTPromiseRejectBlock)reject)
111114
RCT_EXTERN_METHOD(networkGraphListNodeIds:(RCTPromiseResolveBlock)resolve
112115
reject:(RCTPromiseRejectBlock)reject)
113116
RCT_EXTERN_METHOD(networkGraphListChannels:(RCTPromiseResolveBlock)resolve

lib/ios/Ldk.swift

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,49 @@ class Ldk: NSObject {
10081008
return resolve(try! FileManager.default.contentsOfDirectory(at: channelStoragePath, includingPropertiesForKeys: nil).map { $0.lastPathComponent })
10091009
}
10101010

1011+
@objc
1012+
func listChannelMonitors(_ ignoreOpenChannels: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1013+
guard let channelManager else {
1014+
return handleReject(reject, .init_channel_manager)
1015+
}
1016+
1017+
guard let keysManager else {
1018+
return handleReject(reject, .init_keys_manager)
1019+
}
1020+
1021+
guard let channelStoragePath = Ldk.channelStoragePath else {
1022+
return handleReject(reject, .init_storage_path)
1023+
}
1024+
1025+
let excludeChannelIds = ignoreOpenChannels ? channelManager.listChannels().map { Data($0.getChannelId() ?? []).hexEncodedString() }.filter { $0 != "" } : []
1026+
1027+
let channelFiles = try! FileManager.default.contentsOfDirectory(at: channelStoragePath, includingPropertiesForKeys: nil)
1028+
1029+
var result: [[String: Any?]] = []
1030+
for channelFile in channelFiles {
1031+
let channelId = channelFile.lastPathComponent.replacingOccurrences(of: ".bin", with: "")
1032+
1033+
guard !excludeChannelIds.contains(channelId) else {
1034+
continue
1035+
}
1036+
1037+
let channelMonitorResult = Bindings.readThirtyTwoBytesChannelMonitor(
1038+
ser: [UInt8](try! Data(contentsOf: channelFile.standardizedFileURL)),
1039+
argA: keysManager.inner.asEntropySource(),
1040+
argB: keysManager.signerProvider
1041+
)
1042+
1043+
guard let (channelId, channelMonitor) = channelMonitorResult.getValue() else {
1044+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Loading channel error. No channel value.")
1045+
continue
1046+
}
1047+
1048+
result.append(channelMonitor.asJson(channelId: Data(channelId).hexEncodedString()))
1049+
}
1050+
1051+
return resolve(result)
1052+
}
1053+
10111054
@objc
10121055
func networkGraphListNodeIds(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
10131056
guard let networkGraph = networkGraph?.readOnly() else {
@@ -1197,7 +1240,7 @@ class Ldk: NSObject {
11971240

11981241
@objc
11991242
func spendRecoveredForceCloseOutputs(_ transaction: NSString, confirmationHeight: NSInteger, changeDestinationScript: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
1200-
//TODO check which ones are not open channels and try spend them again
1243+
12011244
guard let channelStoragePath = Ldk.channelStoragePath, let keysManager, let channelManager else {
12021245
return handleReject(reject, .init_storage_path)
12031246
}

0 commit comments

Comments
 (0)