@@ -9,12 +9,14 @@ import fr.acinq.eclair.channel.Channel.ANNOUNCEMENTS_MINCONF
99import fr.acinq.eclair.channel.Channel.FUNDING_TIMEOUT_FUNDEE_BLOCK
1010import fr.acinq.eclair.channel.Channel.MAX_NEGOTIATION_ITERATIONS
1111import fr.acinq.eclair.channel.Channel.handleSync
12+ import fr.acinq.eclair.channel.Helpers.Closing.btcAddressFromScriptPubKey
1213import fr.acinq.eclair.channel.Helpers.Closing.extractPreimages
1314import fr.acinq.eclair.channel.Helpers.Closing.onChainOutgoingHtlcs
1415import fr.acinq.eclair.channel.Helpers.Closing.overriddenOutgoingHtlcs
1516import fr.acinq.eclair.channel.Helpers.Closing.timedOutHtlcs
1617import fr.acinq.eclair.crypto.KeyManager
1718import fr.acinq.eclair.crypto.ShaChain
19+ import fr.acinq.eclair.db.OutgoingPayment.Status.Completed.Succeeded.OnChain.ChannelClosingType
1820import fr.acinq.eclair.router.Announcements
1921import fr.acinq.eclair.serialization.Serialization
2022import fr.acinq.eclair.transactions.CommitmentSpec
@@ -94,7 +96,8 @@ sealed class ChannelAction {
9496 data class StoreHtlcInfos (val htlcs : List <HtlcInfo >) : Storage()
9597 data class GetHtlcInfos (val revokedCommitTxId : ByteVector32 , val commitmentNumber : Long ) : Storage()
9698 data class StoreIncomingAmount (val amount : MilliSatoshi , val origin : ChannelOrigin ? ) : Storage()
97- data class StoreOutgoingAmount (val amount : MilliSatoshi ) : Storage()
99+ data class StoreChannelClosing (val amount : MilliSatoshi , val closingAddress : String , val isSentToDefaultAddress : Boolean ) : Storage()
100+ data class StoreChannelClosed (val txids : List <ByteVector32 >, val claimed : Satoshi , val type : ChannelClosingType ) : Storage()
98101 }
99102
100103 data class ProcessIncomingHtlc (val add : UpdateAddHtlc ) : ChannelAction()
@@ -164,7 +167,41 @@ sealed class ChannelState {
164167 this is WaitForInit && newState is Closing -> actions
165168 this is Closing && newState is Closing -> actions
166169 this is ChannelStateWithCommitments && newState is Closing -> {
167- actions + ChannelAction .Storage .StoreOutgoingAmount (this .commitments.localCommit.spec.toLocal)
170+ val channelBalance = commitments.localCommit.spec.toLocal
171+ if (channelBalance > 0 .msat) {
172+ val defaultScriptPubKey = commitments.localParams.defaultFinalScriptPubKey
173+ val localShutdown = when (this ) {
174+ is Normal -> this .localShutdown
175+ is Negotiating -> this .localShutdown
176+ is ShuttingDown -> this .localShutdown
177+ else -> null
178+ }
179+ if (localShutdown != null && localShutdown.scriptPubKey != defaultScriptPubKey) {
180+ // Non-default output address
181+ val btcAddr = Helpers .Closing .btcAddressFromScriptPubKey(
182+ scriptPubKey = localShutdown.scriptPubKey,
183+ chainHash = staticParams.nodeParams.chainHash
184+ ) ? : " unknown"
185+ actions + ChannelAction .Storage .StoreChannelClosing (
186+ amount = channelBalance,
187+ closingAddress = btcAddr,
188+ isSentToDefaultAddress = false
189+ )
190+ } else {
191+ // Default output address
192+ val btcAddr = Helpers .Closing .btcAddressFromScriptPubKey(
193+ scriptPubKey = defaultScriptPubKey,
194+ chainHash = staticParams.nodeParams.chainHash
195+ ) ? : " unknown"
196+ actions + ChannelAction .Storage .StoreChannelClosing (
197+ amount = channelBalance,
198+ closingAddress = btcAddr,
199+ isSentToDefaultAddress = true
200+ )
201+ }
202+ } else /* channelBalance <= 0.msat */ {
203+ actions
204+ }
168205 }
169206 else -> actions
170207 }
@@ -723,10 +760,10 @@ data class Offline(val state: ChannelStateWithCommitments) : ChannelState() {
723760 currentTip,
724761 currentOnChainFeerates,
725762 state.commitments,
726- null ,
727- currentBlockHeight.toLong(),
728- state.closingTxProposed.flatten().map { it.unsignedTx },
729- listOf (closingTx)
763+ fundingTx = null ,
764+ waitingSinceBlock = currentBlockHeight.toLong(),
765+ mutualCloseProposed = state.closingTxProposed.flatten().map { it.unsignedTx },
766+ mutualClosePublished = listOf (closingTx)
730767 )
731768 val actions = listOf (
732769 ChannelAction .Storage .StoreState (nextState),
@@ -994,10 +1031,10 @@ data class Syncing(val state: ChannelStateWithCommitments, val waitForTheirReest
9941031 currentTip,
9951032 currentOnChainFeerates,
9961033 state.commitments,
997- null ,
998- currentBlockHeight.toLong(),
999- state.closingTxProposed.flatten().map { it.unsignedTx },
1000- listOf (closingTx)
1034+ fundingTx = null ,
1035+ waitingSinceBlock = currentBlockHeight.toLong(),
1036+ mutualCloseProposed = state.closingTxProposed.flatten().map { it.unsignedTx },
1037+ mutualClosePublished = listOf (closingTx)
10011038 )
10021039 val actions = listOf (
10031040 ChannelAction .Storage .StoreState (nextState),
@@ -2331,10 +2368,10 @@ data class Negotiating(
23312368 currentTip,
23322369 currentOnChainFeerates,
23332370 commitments,
2334- null ,
2335- currentBlockHeight.toLong(),
2336- this .closingTxProposed.flatten().map { it.unsignedTx },
2337- listOf (signedClosingTx)
2371+ fundingTx = null ,
2372+ waitingSinceBlock = currentBlockHeight.toLong(),
2373+ mutualCloseProposed = this .closingTxProposed.flatten().map { it.unsignedTx },
2374+ mutualClosePublished = listOf (signedClosingTx)
23382375 )
23392376 val actions = listOf (
23402377 ChannelAction .Storage .StoreState (nextState),
@@ -2352,10 +2389,10 @@ data class Negotiating(
23522389 currentTip,
23532390 currentOnChainFeerates,
23542391 commitments,
2355- null ,
2356- currentBlockHeight.toLong(),
2357- this .closingTxProposed.flatten().map { it.unsignedTx } + listOf (signedClosingTx),
2358- listOf (signedClosingTx)
2392+ fundingTx = null ,
2393+ waitingSinceBlock = currentBlockHeight.toLong(),
2394+ mutualCloseProposed = this .closingTxProposed.flatten().map { it.unsignedTx } + listOf (signedClosingTx),
2395+ mutualClosePublished = listOf (signedClosingTx)
23592396 )
23602397 val actions = listOf (
23612398 ChannelAction .Storage .StoreState (nextState),
@@ -2399,10 +2436,10 @@ data class Negotiating(
23992436 currentTip,
24002437 currentOnChainFeerates,
24012438 commitments,
2402- null ,
2403- currentBlockHeight.toLong(),
2404- this .closingTxProposed.flatten().map { it.unsignedTx },
2405- listOf (closingTx)
2439+ fundingTx = null ,
2440+ waitingSinceBlock = currentBlockHeight.toLong(),
2441+ mutualCloseProposed = this .closingTxProposed.flatten().map { it.unsignedTx },
2442+ mutualClosePublished = listOf (closingTx)
24062443 )
24072444 val actions = listOf (
24082445 ChannelAction .Storage .StoreState (nextState),
@@ -2446,10 +2483,10 @@ data class Negotiating(
24462483 currentTip,
24472484 currentOnChainFeerates,
24482485 commitments,
2449- null ,
2450- currentBlockHeight.toLong(),
2451- this .closingTxProposed.flatten().map { it.unsignedTx } + listOf (bestUnpublishedClosingTx),
2452- listOf (bestUnpublishedClosingTx)
2486+ fundingTx = null ,
2487+ waitingSinceBlock = currentBlockHeight.toLong(),
2488+ mutualCloseProposed = this .closingTxProposed.flatten().map { it.unsignedTx } + listOf (bestUnpublishedClosingTx),
2489+ mutualClosePublished = listOf (bestUnpublishedClosingTx)
24532490 )
24542491 val actions = listOf (
24552492 ChannelAction .Storage .StoreState (nextState),
@@ -2590,7 +2627,11 @@ data class Closing(
25902627 futureRemoteCommitPublished = this .futureRemoteCommitPublished?.update(watch.tx),
25912628 revokedCommitPublished = this .revokedCommitPublished.map { it.update(watch.tx) }
25922629 )
2593- closing1.networkFeePaid(watch.tx)?.let { logger.info { " c:$channelId paid fee=${it.first} for txid=${watch.tx.txid} desc=${it.second} " } }
2630+ closing1.networkFeePaid(watch.tx)?.let {
2631+ logger.info { " c:$channelId paid fee=${it.first} for txid=${watch.tx.txid} desc=${it.second} " }
2632+ } ? : run {
2633+ logger.info { " c:$channelId paid UNKNOWN fee for txid=${watch.tx.txid} " }
2634+ }
25942635
25952636 // we may need to fail some htlcs in case a commitment tx was published and they have reached the timeout threshold
25962637 val htlcSettledActions = mutableListOf<ChannelAction >()
@@ -2631,19 +2672,20 @@ data class Closing(
26312672 commitments.payments[add.id]?.let { paymentId -> logger.info { " c:$channelId paymentId=$paymentId will settle on-chain (htlc #${add.id} sending ${add.amountMsat} )" } }
26322673 }
26332674
2634- val nextState = when (val closingType = closing1.isClosed(watch.tx)) {
2635- null -> closing1
2675+ val ( nextState, closedActions) = when (val closingType = closing1.isClosed(watch.tx)) {
2676+ null -> Pair ( closing1, listOf ())
26362677 else -> {
26372678 logger.info { " c:$channelId channel is now closed" }
26382679 if (closingType !is MutualClose ) {
26392680 logger.debug { " c:$channelId last known remoteChannelData=${commitments.remoteChannelData} " }
26402681 }
2641- Closed (closing1)
2682+ Pair ( Closed (closing1), listOf (closing1.storeChannelClosed(watch.tx)) )
26422683 }
26432684 }
26442685 val actions = buildList {
26452686 add(ChannelAction .Storage .StoreState (nextState))
26462687 addAll(htlcSettledActions)
2688+ addAll(closedActions)
26472689 }
26482690 Pair (nextState, actions)
26492691 }
@@ -2846,6 +2888,63 @@ data class Closing(
28462888 parentTxOut?.let { txOut -> txOut.amount - tx.txOut.map { it.amount }.sum() }?.let { it to desc }
28472889 }
28482890 }
2891+
2892+ private fun storeChannelClosed (additionalConfirmedTx : Transaction ? ): ChannelAction .Storage .StoreChannelClosed {
2893+ // We want to give the user the list of btc transactions for their outputs
2894+ val txids = mutableListOf<ByteVector32 >()
2895+ var claimed = 0 .sat
2896+ val type = when {
2897+ mutualClosePublished.isNotEmpty() -> ChannelClosingType .Mutual
2898+ localCommitPublished != null -> ChannelClosingType .Local
2899+ remoteCommitPublished != null -> ChannelClosingType .Remote
2900+ nextRemoteCommitPublished != null -> ChannelClosingType .Remote
2901+ futureRemoteCommitPublished != null -> ChannelClosingType .Remote
2902+ revokedCommitPublished.isNotEmpty() -> ChannelClosingType .Revoked
2903+ else -> ChannelClosingType .Other
2904+ }
2905+ additionalConfirmedTx?.let { confirmedTx ->
2906+ mutualClosePublished.firstOrNull { it.tx == confirmedTx }?.let {
2907+ txids + = it.tx.txid
2908+ claimed + = it.toLocalOutput?.amount ? : 0 .sat
2909+ }
2910+ }
2911+ localCommitPublished?.let {
2912+ val confirmedTxids = it.irrevocablySpent.values.map { it.txid }.toSet()
2913+ val allTxs = listOfNotNull(it.claimMainDelayedOutputTx?.tx) +
2914+ it.claimHtlcDelayedTxs.map { it.tx }
2915+ val confirmedTxs = allTxs.filter { confirmedTxids.contains(it.txid) }
2916+ if (confirmedTxs.isNotEmpty()) {
2917+ txids + = confirmedTxs.map { it.txid }
2918+ claimed + = confirmedTxs.map { it.txOut.first().amount }.sum()
2919+ }
2920+ }
2921+ listOfNotNull(
2922+ remoteCommitPublished,
2923+ nextRemoteCommitPublished,
2924+ futureRemoteCommitPublished
2925+ ).forEach {
2926+ val confirmedTxids = it.irrevocablySpent.values.map { it.txid }.toSet()
2927+ val allTxs = listOfNotNull(it.claimMainOutputTx?.tx) +
2928+ it.claimHtlcTxs.mapNotNull { it.value?.tx }
2929+ val confirmedTxs = allTxs.filter { confirmedTxids.contains(it.txid) }
2930+ if (confirmedTxs.isNotEmpty()) {
2931+ txids + = confirmedTxs.map { it.txid }
2932+ claimed + = confirmedTxs.map { it.txOut.first().amount }.sum()
2933+ }
2934+ }
2935+ revokedCommitPublished.forEach {
2936+ val confirmedTxids = it.irrevocablySpent.values.map { it.txid }.toSet()
2937+ val allTxs = listOfNotNull(it.claimMainOutputTx?.tx, it.mainPenaltyTx?.tx) +
2938+ it.htlcPenaltyTxs.map { it.tx } +
2939+ it.claimHtlcDelayedPenaltyTxs.map { it.tx }
2940+ val confirmedTxs = allTxs.filter { confirmedTxids.contains(it.txid) }
2941+ if (confirmedTxs.isNotEmpty()) {
2942+ txids + = confirmedTxs.map { it.txid }
2943+ claimed + = confirmedTxs.map { it.txOut.first().amount }.sum()
2944+ }
2945+ }
2946+ return ChannelAction .Storage .StoreChannelClosed (txids, claimed, type)
2947+ }
28492948}
28502949
28512950/* *
0 commit comments