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
@@ -49,6 +51,7 @@ import to.bitkit.utils.LdkError
4951import to.bitkit.utils.LdkLogWriter
5052import to.bitkit.utils.Logger
5153import to.bitkit.utils.ServiceError
54+ import java.util.concurrent.ConcurrentHashMap
5255import javax.inject.Inject
5356import javax.inject.Singleton
5457import kotlin.io.path.Path
@@ -57,6 +60,7 @@ import kotlin.time.Duration.Companion.seconds
5760
5861typealias NodeEventHandler = suspend (Event ) -> Unit
5962
63+ @Suppress(" LargeClass" )
6064@Singleton
6165class LightningService @Inject constructor(
6266 @BgDispatcher private val bgDispatcher : CoroutineDispatcher ,
@@ -70,6 +74,8 @@ class LightningService @Inject constructor(
7074
7175 private lateinit var trustedPeers: List <PeerDetails >
7276
77+ private val channelCache = ConcurrentHashMap <String , ChannelDetails >()
78+
7379 suspend fun setup (
7480 walletIndex : Int ,
7581 customServerUrl : String? = null,
@@ -190,6 +196,7 @@ class LightningService @Inject constructor(
190196 }
191197 }
192198
199+ refreshChannelCache()
193200 Logger .info(" Node started" )
194201 }
195202
@@ -225,9 +232,79 @@ class LightningService @Inject constructor(
225232 node.syncWallets()
226233 // launch { setMaxDustHtlcExposureForCurrentChannels() }
227234 }
235+ refreshChannelCache()
236+
228237 Logger .debug(" LDK synced" )
229238 }
230239
240+ private suspend fun refreshChannelCache () {
241+ val node = this .node ? : return
242+
243+ ServiceQueue .LDK .background {
244+ val channels = node.listChannels()
245+ channels.forEach { channel ->
246+ channelCache[channel.channelId] = channel
247+ }
248+ }
249+ }
250+
251+ private suspend fun registerClosedChannel (channelId : String , reason : String? ) {
252+ try {
253+ val channel = ServiceQueue .LDK .background {
254+ channelCache[channelId]
255+ } ? : run {
256+ Logger .error(
257+ " Could not find channel details for closed channel: channelId=$channelId " ,
258+ context = TAG
259+ )
260+ return @registerClosedChannel
261+ }
262+
263+ val fundingTxo = channel.fundingTxo
264+ if (fundingTxo == null ) {
265+ Logger .error(
266+ " Channel has no funding transaction, cannot persist closed channel: channelId=$channelId " ,
267+ context = TAG
268+ )
269+ return @registerClosedChannel
270+ }
271+
272+ val channelName = channel.inboundScidAlias?.toString()
273+ ? : channel.channelId.take(CHANNEL_ID_PREVIEW_LENGTH ) + " …"
274+
275+ val closedAt = (System .currentTimeMillis() / 1000L ).toULong()
276+
277+ val closedChannel = ClosedChannelDetails (
278+ channelId = channel.channelId,
279+ counterpartyNodeId = channel.counterpartyNodeId,
280+ fundingTxoTxid = fundingTxo.txid,
281+ fundingTxoIndex = fundingTxo.vout,
282+ channelValueSats = channel.channelValueSats,
283+ closedAt = closedAt,
284+ outboundCapacityMsat = channel.outboundCapacityMsat,
285+ inboundCapacityMsat = channel.inboundCapacityMsat,
286+ counterpartyUnspendablePunishmentReserve = channel.counterpartyUnspendablePunishmentReserve,
287+ unspendablePunishmentReserve = channel.unspendablePunishmentReserve ? : 0u ,
288+ forwardingFeeProportionalMillionths = channel.config.forwardingFeeProportionalMillionths,
289+ forwardingFeeBaseMsat = channel.config.forwardingFeeBaseMsat,
290+ channelName = channelName,
291+ channelClosureReason = reason.orEmpty()
292+ )
293+
294+ ServiceQueue .CORE .background {
295+ upsertClosedChannel(closedChannel)
296+ }
297+
298+ ServiceQueue .LDK .background {
299+ channelCache.remove(channelId)
300+ }
301+
302+ Logger .info(" Registered closed channel: ${channel.userChannelId} " , context = TAG )
303+ } catch (e: Exception ) {
304+ Logger .error(" Failed to register closed channel: $e " , e, context = TAG )
305+ }
306+ }
307+
231308 // private fun setMaxDustHtlcExposureForCurrentChannels() {
232309 // if (Env.network != Network.REGTEST) {
233310 // Logger.debug("Not updating channel config for non-regtest network")
@@ -738,6 +815,9 @@ class LightningService @Inject constructor(
738815 Logger .info(
739816 " ⏳ Channel pending: channelId: $channelId userChannelId: $userChannelId formerTemporaryChannelId: $formerTemporaryChannelId counterpartyNodeId: $counterpartyNodeId fundingTxo: $fundingTxo "
740817 )
818+ launch {
819+ refreshChannelCache()
820+ }
741821 }
742822
743823 is Event .ChannelReady -> {
@@ -747,16 +827,22 @@ class LightningService @Inject constructor(
747827 Logger .info(
748828 " 👐 Channel ready: channelId: $channelId userChannelId: $userChannelId counterpartyNodeId: $counterpartyNodeId "
749829 )
830+ launch {
831+ refreshChannelCache()
832+ }
750833 }
751834
752835 is Event .ChannelClosed -> {
753836 val channelId = event.channelId
754837 val userChannelId = event.userChannelId
755838 val counterpartyNodeId = event.counterpartyNodeId ? : " ?"
756- val reason = event.reason
839+ val reason = event.reason?.toString() ? : " "
757840 Logger .info(
758841 " ⛔ Channel closed: channelId: $channelId userChannelId: $userChannelId counterpartyNodeId: $counterpartyNodeId reason: $reason "
759842 )
843+ launch {
844+ registerClosedChannel(channelId, reason)
845+ }
760846 }
761847 }
762848 }
@@ -781,6 +867,7 @@ class LightningService @Inject constructor(
781867
782868 companion object {
783869 private const val TAG = " LightningService"
870+ private const val CHANNEL_ID_PREVIEW_LENGTH = 10
784871 }
785872}
786873
0 commit comments