diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index dfe7fb5af9..bad6c97e40 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -1645,8 +1645,8 @@ object Helpers { val relevantOutpoints = tx.txIn.map(_.outPoint).filter(outPoint => { // is this the commit tx itself? (we could do this outside of the loop...) val isCommitTx = localCommitPublished.commitTx.txid == tx.txid - // does the tx spend an output of the local commitment tx? - val spendsTheCommitTx = localCommitPublished.commitTx.txid == outPoint.txid + // does the tx spend an output of the local commitment tx (other than the anchor output)? + val spendsTheCommitTx = localCommitPublished.commitTx.txid == outPoint.txid && !localCommitPublished.anchorOutput_opt.contains(outPoint) // is the tx one of our 3rd stage delayed txs? (a 3rd stage tx is a tx spending the output of an htlc tx, which // is itself spending the output of the commitment tx) val is3rdStageDelayedTx = localCommitPublished.htlcDelayedOutputs.contains(outPoint) @@ -1672,8 +1672,8 @@ object Helpers { val relevantOutpoints = tx.txIn.map(_.outPoint).filter(outPoint => { // is this the commit tx itself? (we could do this outside of the loop...) val isCommitTx = remoteCommitPublished.commitTx.txid == tx.txid - // does the tx spend an output of the remote commitment tx? - val spendsTheCommitTx = remoteCommitPublished.commitTx.txid == outPoint.txid + // does the tx spend an output of the remote commitment tx (other than the anchor output)? + val spendsTheCommitTx = remoteCommitPublished.commitTx.txid == outPoint.txid && !remoteCommitPublished.anchorOutput_opt.contains(outPoint) isCommitTx || spendsTheCommitTx }) // then we add the relevant outpoints to the map keeping track of which txid spends which outpoint @@ -1696,8 +1696,8 @@ object Helpers { val relevantOutpoints = tx.txIn.map(_.outPoint).filter(outPoint => { // is this the commit tx itself? (we could do this outside of the loop...) val isCommitTx = revokedCommitPublished.commitTx.txid == tx.txid - // does the tx spend an output of the remote commitment tx? - val spendsTheCommitTx = revokedCommitPublished.commitTx.txid == outPoint.txid + // does the tx spend an output of the remote commitment tx (other than the anchor output)? + val spendsTheCommitTx = revokedCommitPublished.commitTx.txid == outPoint.txid && !revokedCommitPublished.anchorOutput_opt.contains(outPoint) // is the tx one of our 3rd stage delayed txs? (a 3rd stage tx is a tx spending the output of an htlc tx, which // is itself spending the output of the commitment tx) val is3rdStageDelayedTx = revokedCommitPublished.htlcDelayedOutputs.contains(outPoint) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala index a2ac4b4016..7b0869eccd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala @@ -256,7 +256,7 @@ trait ErrorHandlers extends CommonHandlers { // we will watch for its confirmation. This ensures that we detect double-spends that could come from: // - our own RBF attempts // - remote transactions for outputs that both parties may spend (e.g. HTLCs) - val watchSpentQueue = lcp.localOutput_opt ++ lcp.anchorOutput_opt ++ lcp.htlcOutputs.toSeq + val watchSpentQueue = lcp.localOutput_opt ++ (if (!lcp.isConfirmed) lcp.anchorOutput_opt else None) ++ lcp.htlcOutputs.toSeq watchSpentIfNeeded(lcp.commitTx, watchSpentQueue, lcp.irrevocablySpent) } @@ -337,7 +337,7 @@ trait ErrorHandlers extends CommonHandlers { // we will watch for its confirmation. This ensures that we detect double-spends that could come from: // - our own RBF attempts // - remote transactions for outputs that both parties may spend (e.g. HTLCs) - val watchSpentQueue = rcp.localOutput_opt ++ rcp.anchorOutput_opt ++ rcp.htlcOutputs.toSeq + val watchSpentQueue = rcp.localOutput_opt ++ (if (!rcp.isConfirmed) rcp.anchorOutput_opt else None) ++ rcp.htlcOutputs.toSeq watchSpentIfNeeded(rcp.commitTx, watchSpentQueue, rcp.irrevocablySpent) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 66ff368e0c..2be9104ac8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -993,7 +993,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSING) // Alice republishes the HTLC-success transaction, which then confirms. assert(alice2blockchain.expectReplaceableTxPublished[HtlcSuccessTx].input == htlcSuccess.input) - closingTxs.anchorTx_opt.foreach(anchorTx => alice2blockchain.expectWatchOutputSpent(anchorTx.txIn.head.outPoint)) alice2blockchain.expectWatchOutputSpent(htlcSuccess.input.outPoint) alice ! WatchOutputSpentTriggered(htlcSuccess.amountIn, htlcSuccess.tx) alice2blockchain.expectWatchTxConfirmed(htlcSuccess.tx.txid) @@ -1010,7 +1009,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! INPUT_RESTORED(beforeRestart2) alice2blockchain.expectMsgType[SetChannelId] awaitCond(alice.stateName == CLOSING) - closingTxs.anchorTx_opt.foreach(anchorTx => alice2blockchain.expectWatchOutputSpent(anchorTx.txIn.head.outPoint)) // Alice republishes the 3rd-stage HTLC transaction, which then confirms. alice2blockchain.expectFinalTxPublished(htlcDelayedTx.tx.txid) alice2blockchain.expectWatchOutputSpent(htlcDelayedTx.input) @@ -1252,7 +1250,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob re-publishes closing transactions: he has 1 HTLC-success and 1 HTLC-timeout transactions left. val republishedHtlcTxsBob = (1 to 2).map(_ => bob2blockchain.expectMsgType[PublishReplaceableTx]) - bob2blockchain.expectWatchOutputsSpent(remainingHtlcOutputs ++ closingTxsBob.anchorTx_opt.map(_.txIn.head.outPoint).toSeq) + bob2blockchain.expectWatchOutputsSpent(remainingHtlcOutputs) assert(republishedHtlcTxsBob.map(_.input).toSet == Set(htlcTimeoutTxBob2.txIn.head.outPoint, closingTxsBob.htlcSuccessTxs.head.txIn.head.outPoint)) bob2blockchain.expectNoMessage(100 millis)