Skip to content

Commit ff0b210

Browse files
authored
Add more force-close tests for HTLC settlement (#772)
This commit is importing test updates and refactoring to the closing helpers from ACINQ/eclair#3040 We don't relay HTLCs, so we don't have an upstream channel to relay preimages to, but it's important to relay preimages to the payment handler to correctly mark payments as succeeded (or failed) and store the proof of payment.
1 parent 3cf3c1e commit ff0b210

File tree

12 files changed

+762
-588
lines changed

12 files changed

+762
-588
lines changed

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelData.kt

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import fr.acinq.lightning.channel.Helpers.watchConfirmedIfNeeded
1010
import fr.acinq.lightning.channel.Helpers.watchSpentIfNeeded
1111
import fr.acinq.lightning.crypto.KeyManager
1212
import fr.acinq.lightning.logging.LoggingContext
13-
import fr.acinq.lightning.transactions.Scripts
1413
import fr.acinq.lightning.transactions.Transactions.TransactionWithInputInfo.*
1514
import fr.acinq.lightning.utils.toMilliSatoshi
1615

@@ -70,7 +69,7 @@ data class LocalCommitPublished(
7069
// is the commitment tx buried? (we need to check this because we may not have any outputs)
7170
val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid)
7271
// is our main output confirmed (if we have one)?
73-
val isMainOutputConfirmed = claimMainDelayedOutputTx?.let { irrevocablySpent.contains(it.input.outPoint) } ?: true
72+
val isMainOutputConfirmed = claimMainDelayedOutputTx == null || irrevocablySpent.contains(claimMainDelayedOutputTx.input.outPoint)
7473
// are all htlc outputs from the commitment tx spent (we need to check them all because we may receive preimages later)?
7574
val allHtlcsSpent = (htlcTxs.keys - irrevocablySpent.keys).isEmpty()
7675
// are all outputs from htlc txs spent?
@@ -86,22 +85,6 @@ data class LocalCommitPublished(
8685
return irrevocablySpent.values.any { it.txid == commitTx.txid } || irrevocablySpent.keys.any { it.txid == commitTx.txid }
8786
}
8887

89-
fun isHtlcTimeout(tx: Transaction): Boolean {
90-
return tx.txIn
91-
.filter { htlcTxs[it.outPoint] is HtlcTx.HtlcTimeoutTx }
92-
.map { it.witness }
93-
.mapNotNull(Scripts.extractPaymentHashFromHtlcTimeout())
94-
.isNotEmpty()
95-
}
96-
97-
fun isHtlcSuccess(tx: Transaction): Boolean {
98-
return tx.txIn
99-
.filter { htlcTxs[it.outPoint] is HtlcTx.HtlcSuccessTx }
100-
.map { it.witness }
101-
.mapNotNull(Scripts.extractPreimageFromHtlcSuccess())
102-
.isNotEmpty()
103-
}
104-
10588
internal fun LoggingContext.doPublish(nodeParams: NodeParams, channelId: ByteVector32): List<ChannelAction> {
10689
val publishQueue = buildList {
10790
add(ChannelAction.Blockchain.PublishTx(commitTx, ChannelAction.Blockchain.PublishTx.Type.CommitTx))
@@ -183,7 +166,7 @@ data class RemoteCommitPublished(
183166
// is the commitment tx buried? (we need to check this because we may not have any outputs)
184167
val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid)
185168
// is our main output confirmed (if we have one)?
186-
val isMainOutputConfirmed = claimMainOutputTx?.let { irrevocablySpent.contains(it.input.outPoint) } ?: true
169+
val isMainOutputConfirmed = claimMainOutputTx == null || irrevocablySpent.contains(claimMainOutputTx.input.outPoint)
187170
// are all htlc outputs from the commitment tx spent (we need to check them all because we may receive preimages later)?
188171
val allHtlcsSpent = (claimHtlcTxs.keys - irrevocablySpent.keys).isEmpty()
189172
return isCommitTxConfirmed && isMainOutputConfirmed && allHtlcsSpent
@@ -193,22 +176,6 @@ data class RemoteCommitPublished(
193176
return irrevocablySpent.values.any { it.txid == commitTx.txid } || irrevocablySpent.keys.any { it.txid == commitTx.txid }
194177
}
195178

196-
fun isClaimHtlcTimeout(tx: Transaction): Boolean {
197-
return tx.txIn
198-
.filter { claimHtlcTxs[it.outPoint] is ClaimHtlcTx.ClaimHtlcTimeoutTx }
199-
.map { it.witness }
200-
.mapNotNull(Scripts.extractPaymentHashFromClaimHtlcTimeout())
201-
.isNotEmpty()
202-
}
203-
204-
fun isClaimHtlcSuccess(tx: Transaction): Boolean {
205-
return tx.txIn
206-
.filter { claimHtlcTxs[it.outPoint] is ClaimHtlcTx.ClaimHtlcSuccessTx }
207-
.map { it.witness }
208-
.mapNotNull(Scripts.extractPreimageFromClaimHtlcSuccess())
209-
.isNotEmpty()
210-
}
211-
212179
internal fun LoggingContext.doPublish(nodeParams: NodeParams, channelId: ByteVector32): List<ChannelAction> {
213180
val publishQueue = buildList {
214181
claimMainOutputTx?.let { add(ChannelAction.Blockchain.PublishTx(it)) }

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelException.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ data class FundingTxSpent (override val channelId: Byte
5555
data class HtlcsTimedOutDownstream (override val channelId: ByteVector32, val htlcs: Set<UpdateAddHtlc>) : ChannelException(channelId, "one or more htlcs timed out downstream: ids=${htlcs.map { it.id } .joinToString(",")}")
5656
data class FulfilledHtlcsWillTimeout (override val channelId: ByteVector32, val htlcs: Set<UpdateAddHtlc>) : ChannelException(channelId, "one or more htlcs that should be fulfilled are close to timing out: ids=${htlcs.map { it.id }.joinToString()}")
5757
data class HtlcOverriddenByLocalCommit (override val channelId: ByteVector32, val htlc: UpdateAddHtlc) : ChannelException(channelId, "htlc ${htlc.id} was overridden by local commit")
58+
data class HtlcOverriddenByRemoteCommit (override val channelId: ByteVector32, val htlc: UpdateAddHtlc) : ChannelException(channelId, "htlc ${htlc.id} was overridden by remote commit")
5859
data class FeerateTooSmall (override val channelId: ByteVector32, val remoteFeeratePerKw: FeeratePerKw) : ChannelException(channelId, "remote fee rate is too small: remoteFeeratePerKw=${remoteFeeratePerKw.toLong()}")
5960
data class FeerateTooDifferent (override val channelId: ByteVector32, val localFeeratePerKw: FeeratePerKw, val remoteFeeratePerKw: FeeratePerKw) : ChannelException(channelId, "local/remote feerates are too different: remoteFeeratePerKw=${remoteFeeratePerKw.toLong()} localFeeratePerKw=${localFeeratePerKw.toLong()}")
6061
data class InvalidCommitmentSignature (override val channelId: ByteVector32, val txId: TxId) : ChannelException(channelId, "invalid commitment signature: txId=$txId")

0 commit comments

Comments
 (0)