11package to.bitkit.services
22
3+ import com.synonym.bitkitcore.ClosedChannelDetails
4+ import com.synonym.bitkitcore.upsertClosedChannel
35import kotlinx.coroutines.CoroutineDispatcher
46import kotlinx.coroutines.currentCoroutineContext
57import kotlinx.coroutines.delay
@@ -57,6 +59,7 @@ import kotlin.time.Duration.Companion.seconds
5759
5860typealias NodeEventHandler = suspend (Event ) -> Unit
5961
62+ @Suppress(" LargeClass" )
6063@Singleton
6164class LightningService @Inject constructor(
6265 @BgDispatcher private val bgDispatcher : CoroutineDispatcher ,
@@ -70,6 +73,8 @@ class LightningService @Inject constructor(
7073
7174 private lateinit var trustedPeers: List <PeerDetails >
7275
76+ private val channelCache = mutableMapOf<String , ChannelDetails >()
77+
7378 suspend fun setup (
7479 walletIndex : Int ,
7580 customServerUrl : String? = null,
@@ -190,6 +195,7 @@ class LightningService @Inject constructor(
190195 }
191196 }
192197
198+ refreshChannelCache()
193199 Logger .info(" Node started" )
194200 }
195201
@@ -225,9 +231,73 @@ class LightningService @Inject constructor(
225231 node.syncWallets()
226232 // launch { setMaxDustHtlcExposureForCurrentChannels() }
227233 }
234+ refreshChannelCache()
235+
228236 Logger .debug(" LDK synced" )
229237 }
230238
239+ private suspend fun refreshChannelCache () {
240+ val node = this .node ? : return
241+
242+ ServiceQueue .LDK .background {
243+ val channels = node.listChannels()
244+ channels.forEach { channel ->
245+ channelCache[channel.channelId] = channel
246+ }
247+ }
248+ }
249+
250+ private suspend fun registerClosedChannel (channelId : String , reason : String? ) {
251+ try {
252+ val channel = ServiceQueue .LDK .background {
253+ channelCache[channelId]?.also {
254+ channelCache.remove(channelId)
255+ }
256+ } ? : run {
257+ Logger .error(
258+ " Could not find channel details for closed channel: channelId=$channelId " ,
259+ context = TAG
260+ )
261+ return @registerClosedChannel
262+ }
263+
264+ val channelName = channel.inboundScidAlias?.toString()
265+ ? : channel.channelId.take(10 ) + " …"
266+
267+ val fundingTxo = channel.fundingTxo
268+ if (fundingTxo == null ) {
269+ Logger .error(" Channel has no funding transaction" , context = TAG )
270+ return
271+ }
272+
273+ val closedAt = (System .currentTimeMillis() / 1000L ).toULong()
274+
275+ val closedChannel = ClosedChannelDetails (
276+ channelId = channel.channelId,
277+ counterpartyNodeId = channel.counterpartyNodeId,
278+ fundingTxoTxid = fundingTxo.txid,
279+ fundingTxoIndex = fundingTxo.vout,
280+ channelValueSats = channel.channelValueSats,
281+ closedAt = closedAt,
282+ outboundCapacityMsat = channel.outboundCapacityMsat,
283+ inboundCapacityMsat = channel.inboundCapacityMsat,
284+ counterpartyUnspendablePunishmentReserve = channel.counterpartyUnspendablePunishmentReserve,
285+ unspendablePunishmentReserve = channel.unspendablePunishmentReserve ? : 0u ,
286+ forwardingFeeProportionalMillionths = channel.config.forwardingFeeProportionalMillionths,
287+ forwardingFeeBaseMsat = channel.config.forwardingFeeBaseMsat,
288+ channelName = channelName,
289+ channelClosureReason = reason ? : " "
290+ )
291+
292+ ServiceQueue .CORE .background {
293+ upsertClosedChannel(closedChannel)
294+ }
295+ Logger .info(" Registered closed channel: ${channel.userChannelId} " , context = TAG )
296+ } catch (e: Exception ) {
297+ Logger .error(" Failed to register closed channel: $e " , e, context = TAG )
298+ }
299+ }
300+
231301 // private fun setMaxDustHtlcExposureForCurrentChannels() {
232302 // if (Env.network != Network.REGTEST) {
233303 // Logger.debug("Not updating channel config for non-regtest network")
@@ -738,6 +808,9 @@ class LightningService @Inject constructor(
738808 Logger .info(
739809 " ⏳ Channel pending: channelId: $channelId userChannelId: $userChannelId formerTemporaryChannelId: $formerTemporaryChannelId counterpartyNodeId: $counterpartyNodeId fundingTxo: $fundingTxo "
740810 )
811+ launch {
812+ refreshChannelCache()
813+ }
741814 }
742815
743816 is Event .ChannelReady -> {
@@ -747,16 +820,22 @@ class LightningService @Inject constructor(
747820 Logger .info(
748821 " 👐 Channel ready: channelId: $channelId userChannelId: $userChannelId counterpartyNodeId: $counterpartyNodeId "
749822 )
823+ launch {
824+ refreshChannelCache()
825+ }
750826 }
751827
752828 is Event .ChannelClosed -> {
753829 val channelId = event.channelId
754830 val userChannelId = event.userChannelId
755831 val counterpartyNodeId = event.counterpartyNodeId ? : " ?"
756- val reason = event.reason
832+ val reason = event.reason?.toString() ? : " "
757833 Logger .info(
758834 " ⛔ Channel closed: channelId: $channelId userChannelId: $userChannelId counterpartyNodeId: $counterpartyNodeId reason: $reason "
759835 )
836+ launch {
837+ registerClosedChannel(channelId, reason)
838+ }
760839 }
761840 }
762841 }
0 commit comments