Skip to content

Commit

Permalink
Merge pull request #227 from synonymdev/list-channel-monitors
Browse files Browse the repository at this point in the history
feat: list all channel monitors
  • Loading branch information
Jasonvdb committed Mar 22, 2024
2 parents d99ad68 + 0332c9b commit 067f7b6
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 66 deletions.
17 changes: 17 additions & 0 deletions example/Dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,23 @@ const Dev = (): ReactElement => {
}}
/>

<Button
title={'List channel monitors'}
onPress={async (): Promise<void> => {
const monitorsRes = await ldk.listChannelMonitors(true);
if (monitorsRes.isErr()) {
return setMessage(monitorsRes.error.message);
}

let msg = `Channel Monitors (${monitorsRes.value.length}): \n`;
monitorsRes.value.forEach((monitor) => {
msg += `\n\n${JSON.stringify(monitor)}`;
});

setMessage(msg);
}}
/>

<Button
title={'Show version'}
onPress={async (): Promise<void> => {
Expand Down
43 changes: 31 additions & 12 deletions lib/android/src/main/java/com/reactnativeldk/Helpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ val RouteHop.asJson: WritableMap
return hop
}


fun ChannelMonitor.asJson(channelId: String): WritableMap {
val result = Arguments.createMap()
result.putString("channel_id", channelId)
result.putHexString("funding_txo", _funding_txo._b)
result.putHexString("counterparty_node_id", _counterparty_node_id)

val balances = Arguments.createArray()
_claimable_balances.iterator().forEach { balance ->
balances.pushMap(balance.asJson)
}
result.putArray("claimable_balances", balances)
return result
}

fun WritableMap.putHexString(key: String, bytes: ByteArray?) {
if (bytes != null) {
putString(key, bytes.hexEncodedString())
Expand Down Expand Up @@ -437,56 +452,60 @@ fun UserConfig.mergeWithMap(map: ReadableMap): UserConfig {
return this
}

fun ChainMonitor.getClaimableBalancesAsJson(ignoredChannels: Array<ChannelDetails>): WritableArray {
val result = Arguments.createArray()

get_claimable_balances(ignoredChannels).iterator().forEach { balance ->
val Balance.asJson: WritableMap
get() {
val map = Arguments.createMap()
//Defaults if all castings for balance fail
map.putInt("amount_satoshis", 0)
map.putString("type", "Unknown")

(balance as? Balance.ClaimableAwaitingConfirmations)?.let { claimableAwaitingConfirmations ->
(this as? Balance.ClaimableAwaitingConfirmations)?.let { claimableAwaitingConfirmations ->
map.putInt("amount_satoshis", claimableAwaitingConfirmations.amount_satoshis.toInt())
map.putInt("confirmation_height", claimableAwaitingConfirmations.confirmation_height)
map.putString("type", "ClaimableAwaitingConfirmations")
}

(balance as? Balance.ClaimableOnChannelClose)?.let { claimableOnChannelClose ->
(this as? Balance.ClaimableOnChannelClose)?.let { claimableOnChannelClose ->
map.putInt("amount_satoshis", claimableOnChannelClose.amount_satoshis.toInt())
map.putString("type", "ClaimableOnChannelClose")
}

(balance as? Balance.ContentiousClaimable)?.let { contentiousClaimable ->
(this as? Balance.ContentiousClaimable)?.let { contentiousClaimable ->
map.putInt("amount_satoshis", contentiousClaimable.amount_satoshis.toInt())
map.putInt("timeout_height", contentiousClaimable.timeout_height)
map.putString("type", "ContentiousClaimable")
}

(balance as? Balance.CounterpartyRevokedOutputClaimable)?.let { counterpartyRevokedOutputClaimable ->
(this as? Balance.CounterpartyRevokedOutputClaimable)?.let { counterpartyRevokedOutputClaimable ->
map.putInt("amount_satoshis", counterpartyRevokedOutputClaimable.amount_satoshis.toInt())
map.putString("type", "CounterpartyRevokedOutputClaimable")
}

(balance as? Balance.MaybePreimageClaimableHTLC)?.let { maybePreimageClaimableHTLC ->
(this as? Balance.MaybePreimageClaimableHTLC)?.let { maybePreimageClaimableHTLC ->
map.putInt("amount_satoshis", maybePreimageClaimableHTLC.amount_satoshis.toInt())
map.putInt("expiry_height", maybePreimageClaimableHTLC.expiry_height)
map.putString("type", "MaybePreimageClaimableHTLC")
}

(balance as? Balance.MaybeTimeoutClaimableHTLC)?.let { maybeTimeoutClaimableHTLC ->
(this as? Balance.MaybeTimeoutClaimableHTLC)?.let { maybeTimeoutClaimableHTLC ->
map.putInt("amount_satoshis", maybeTimeoutClaimableHTLC.amount_satoshis.toInt())
map.putInt("claimable_height", maybeTimeoutClaimableHTLC.claimable_height)
map.putString("type", "MaybeTimeoutClaimableHTLC")
}

result.pushMap(map)
return map
}

fun ChainMonitor.getClaimableBalancesAsJson(ignoredChannels: Array<ChannelDetails>): WritableArray {
val result = Arguments.createArray()

get_claimable_balances(ignoredChannels).iterator().forEach { balance ->
result.pushMap(balance.asJson)
}

return result
}


/// Helper for returning real network and currency as a tuple from a string
fun getNetwork(chain: String): Pair<Network, Currency> {
return when (chain) {
Expand Down
36 changes: 34 additions & 2 deletions lib/android/src/main/java/com/reactnativeldk/LdkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,40 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
promise.resolve(list)
}

@ReactMethod
fun listChannelMonitors(ignoreOpenChannels: Boolean, promise: Promise) {
val channelManager = channelManager ?: return handleReject(promise, LdkErrors.init_channel_manager)
val keysManager = keysManager ?: return handleReject(promise, LdkErrors.init_keys_manager)
if (channelStoragePath == "") {
return handleReject(promise, LdkErrors.init_storage_path)
}

val ignoredChannels = if (ignoreOpenChannels)
channelManager.list_channels().map { it._channel_id.hexEncodedString() }.toTypedArray() else
arrayOf()

val channelFiles = File(channelStoragePath).listFiles()

val result = Arguments.createArray()
for (channelFile in channelFiles) {
val channelId = channelFile.nameWithoutExtension

//Ignore open channels
if (ignoredChannels.contains(channelId)) {
continue
}

val channelMonitor = UtilMethods.C2Tuple_ThirtyTwoBytesChannelMonitorZ_read(channelFile.readBytes(), keysManager!!.inner.as_EntropySource(), SignerProvider.new_impl(keysManager!!.signerProvider))

if (channelMonitor.is_ok) {
val channelMonitorResult = (channelMonitor as Result_C2Tuple_ThirtyTwoBytesChannelMonitorZDecodeErrorZ_OK)
result.pushMap(channelMonitorResult.res._b.asJson(channelMonitorResult.res._a.hexEncodedString()))
}
}

promise.resolve(result)
}

@ReactMethod
fun networkGraphListNodeIds(promise: Promise) {
val graph = networkGraph?.read_only() ?: return handleReject(promise, LdkErrors.init_network_graph)
Expand Down Expand Up @@ -1007,8 +1041,6 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
channelManager.list_channels() else
arrayOf<ChannelDetails>()



promise.resolve(chainMonitor.getClaimableBalancesAsJson(ignoredChannels))
}

Expand Down
113 changes: 62 additions & 51 deletions lib/ios/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ extension LightningDevKit.RouteHop {
}
}

extension ChannelMonitor {
func asJson(channelId: String) -> [String: Any?] {
return [
"channel_id": channelId,
"funding_txo": Data(getFundingTxo().1).hexEncodedString(),
"counterparty_node_id": Data(getCounterpartyNodeId() ?? []).hexEncodedString(),
"claimable_balances": getClaimableBalances().map({ $0.asJson })
]
}
}

extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
Expand Down Expand Up @@ -401,63 +412,63 @@ extension UserConfig {
}
}

extension Balance {
var asJson: [String: Any] {
switch getValueType() {
case .ClaimableAwaitingConfirmations:
let b = getValueAsClaimableAwaitingConfirmations()!
return [
"amount_satoshis": b.getAmountSatoshis(),
"confirmation_height": b.getConfirmationHeight(),
"type": "ClaimableAwaitingConfirmations"
] as [String : Any]
case .ClaimableOnChannelClose:
let b = getValueAsClaimableOnChannelClose()!
return [
"amount_satoshis": b.getAmountSatoshis(),
"type": "ClaimableOnChannelClose",
] as [String : Any]
case .ContentiousClaimable:
let b = getValueAsContentiousClaimable()!
return [
"amount_satoshis": b.getAmountSatoshis(),
"timeout_height": b.getTimeoutHeight(),
"type": "ContentiousClaimable"
] as [String : Any]
case .CounterpartyRevokedOutputClaimable:
let b = getValueAsCounterpartyRevokedOutputClaimable()!
return [
"amount_satoshis": b.getAmountSatoshis(),
"type": "CounterpartyRevokedOutputClaimable"
] as [String : Any]
case .MaybePreimageClaimableHTLC:
let b = getValueAsMaybePreimageClaimableHtlc()!
return [
"amount_satoshis": b.getAmountSatoshis(),
"expiry_height": b.getExpiryHeight(),
"type": "MaybePreimageClaimableHTLC"
] as [String : Any]
case .MaybeTimeoutClaimableHTLC:
let b = getValueAsMaybeTimeoutClaimableHtlc()!
return [
"amount_satoshis": b.getAmountSatoshis(),
"claimable_height": b.getClaimableHeight(),
"type": "MaybeTimeoutClaimableHTLC"
] as [String : Any]
default:
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Unknown balance type type in claimableBalances() \(getValueType())")
return ["amount_satoshis": 0, "type": "Unknown"] as [String : Any]
}
}
}

extension ChainMonitor {
func getClaimableBalancesAsJson(ignoredChannels: [Bindings.ChannelDetails]) -> [[String: Any]] {
var result: [[String: Any]] = []

let claimableBalances = self.getClaimableBalances(ignoredChannels: ignoredChannels)
for balance in claimableBalances {
switch balance.getValueType() {
case .ClaimableAwaitingConfirmations:
let b = balance.getValueAsClaimableAwaitingConfirmations()!
result.append([
"amount_satoshis": b.getAmountSatoshis(),
"confirmation_height": b.getConfirmationHeight(),
"type": "ClaimableAwaitingConfirmations"
] as [String : Any])
break
case .ClaimableOnChannelClose:
let b = balance.getValueAsClaimableOnChannelClose()!
result.append([
"amount_satoshis": b.getAmountSatoshis(),
"type": "ClaimableOnChannelClose",
] as [String : Any])
break
case .ContentiousClaimable:
let b = balance.getValueAsContentiousClaimable()!
result.append([
"amount_satoshis": b.getAmountSatoshis(),
"timeout_height": b.getTimeoutHeight(),
"type": "ContentiousClaimable"
] as [String : Any])
break
case .CounterpartyRevokedOutputClaimable:
let b = balance.getValueAsCounterpartyRevokedOutputClaimable()!
result.append([
"amount_satoshis": b.getAmountSatoshis(),
"type": "CounterpartyRevokedOutputClaimable"
] as [String : Any])
break
case .MaybePreimageClaimableHTLC:
let b = balance.getValueAsMaybePreimageClaimableHtlc()!
result.append([
"amount_satoshis": b.getAmountSatoshis(),
"expiry_height": b.getExpiryHeight(),
"type": "MaybePreimageClaimableHTLC"
] as [String : Any])
break
case .MaybeTimeoutClaimableHTLC:
let b = balance.getValueAsMaybeTimeoutClaimableHtlc()!
result.append([
"amount_satoshis": b.getAmountSatoshis(),
"claimable_height": b.getClaimableHeight(),
"type": "MaybeTimeoutClaimableHTLC"
] as [String : Any])
break
default:
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Unknown balance type type in claimableBalances() \(balance.getValueType())")
result.append(["amount_satoshis": 0, "type": "Unknown"] as [String : Any])
}
result.append(balance.asJson)
}

return result
Expand Down
3 changes: 3 additions & 0 deletions lib/ios/Ldk.m
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject)
reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(listChannelFiles:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(listChannelMonitors:(BOOL *)ignoreOpenChannels
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(networkGraphListNodeIds:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(networkGraphListChannels:(RCTPromiseResolveBlock)resolve
Expand Down
45 changes: 44 additions & 1 deletion lib/ios/Ldk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,49 @@ class Ldk: NSObject {
return resolve(try! FileManager.default.contentsOfDirectory(at: channelStoragePath, includingPropertiesForKeys: nil).map { $0.lastPathComponent })
}

@objc
func listChannelMonitors(_ ignoreOpenChannels: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
guard let channelManager else {
return handleReject(reject, .init_channel_manager)
}

guard let keysManager else {
return handleReject(reject, .init_keys_manager)
}

guard let channelStoragePath = Ldk.channelStoragePath else {
return handleReject(reject, .init_storage_path)
}

let excludeChannelIds = ignoreOpenChannels ? channelManager.listChannels().map { Data($0.getChannelId() ?? []).hexEncodedString() }.filter { $0 != "" } : []

let channelFiles = try! FileManager.default.contentsOfDirectory(at: channelStoragePath, includingPropertiesForKeys: nil)

var result: [[String: Any?]] = []
for channelFile in channelFiles {
let channelId = channelFile.lastPathComponent.replacingOccurrences(of: ".bin", with: "")

guard !excludeChannelIds.contains(channelId) else {
continue
}

let channelMonitorResult = Bindings.readThirtyTwoBytesChannelMonitor(
ser: [UInt8](try! Data(contentsOf: channelFile.standardizedFileURL)),
argA: keysManager.inner.asEntropySource(),
argB: keysManager.signerProvider
)

guard let (channelId, channelMonitor) = channelMonitorResult.getValue() else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Loading channel error. No channel value.")
continue
}

result.append(channelMonitor.asJson(channelId: Data(channelId).hexEncodedString()))
}

return resolve(result)
}

@objc
func networkGraphListNodeIds(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
guard let networkGraph = networkGraph?.readOnly() else {
Expand Down Expand Up @@ -1197,7 +1240,7 @@ class Ldk: NSObject {

@objc
func spendRecoveredForceCloseOutputs(_ transaction: NSString, confirmationHeight: NSInteger, changeDestinationScript: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
//TODO check which ones are not open channels and try spend them again

guard let channelStoragePath = Ldk.channelStoragePath, let keysManager, let channelManager else {
return handleReject(reject, .init_storage_path)
}
Expand Down
Loading

0 comments on commit 067f7b6

Please sign in to comment.