Skip to content

Commit 189e282

Browse files
authored
Remove obsolete WatchFundingConfirmed when using RBF (#2961)
When using RBF for a dual-funded channel or a splice, we set multiple `WatchFundingConfirmed` for conflicting transactions. When one of those transactions confirms, the others will never confirm: it is wasteful to keep watching for their confirmation. The watcher doesn't have enough information on its own to efficiently detect that some watches are double-spent: we instead rely on the consumer of the watch to tell the watcher to stop watching the RBF attempts. Fixes #2954
1 parent 8381fc4 commit 189e282

File tree

5 files changed

+73
-30
lines changed

5 files changed

+73
-30
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ object ZmqWatcher {
170170
private case object Keep extends AddWatchResult
171171
private case object Ignore extends AddWatchResult
172172

173+
/** Stop watching confirmations for a given transaction: must be used to stop watching obsolete RBF attempts. */
174+
case class UnwatchTxConfirmed(txId: TxId) extends Command
175+
173176
sealed trait WatchHint
174177
/**
175178
* In some cases we don't need to check watches every time a block is found and only need to check again after we
@@ -364,6 +367,14 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client
364367
val watchedUtxos1 = deprecatedWatches.foldLeft(watchedUtxos) { case (m, w) => removeWatchedUtxos(m, w) }
365368
watching(watches -- deprecatedWatches, watchedUtxos1)
366369

370+
case UnwatchTxConfirmed(txId) =>
371+
// We remove watches that match the given txId.
372+
val deprecatedWatches = watches.keySet.filter {
373+
case w: WatchConfirmed[_] => w.txId == txId
374+
case _ => false
375+
}
376+
watching(watches -- deprecatedWatches, watchedUtxos)
377+
367378
case ValidateRequest(replyTo, ann) =>
368379
client.validate(ann).map(replyTo ! _)
369380
Behaviors.same

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,13 @@ trait CommonFundingHandlers extends CommonHandlers {
9696
// Children splice transactions may already spend that confirmed funding transaction.
9797
val spliceSpendingTxs = commitments1.all.collect { case c if c.fundingTxIndex == commitment.fundingTxIndex + 1 => c.fundingTxId }
9898
watchFundingSpent(commitment, additionalKnownSpendingTxs = spliceSpendingTxs.toSet, None)
99-
// in the dual-funding case we can forget all other transactions, they have been double spent by the tx that just confirmed
100-
rollbackDualFundingTxs(d.commitments.active // note how we use the unpruned original commitments
99+
// In the dual-funding/splicing case we can forget all other transactions (RBF attempts), they have been
100+
// double-spent by the tx that just confirmed.
101+
val conflictingTxs = d.commitments.active // note how we use the unpruned original commitments
101102
.filter(c => c.fundingTxIndex == commitment.fundingTxIndex && c.fundingTxId != commitment.fundingTxId)
102-
.map(_.localFundingStatus).collect { case fundingTx: DualFundedUnconfirmedFundingTx => fundingTx.sharedTx })
103+
.map(_.localFundingStatus).collect { case fundingTx: DualFundedUnconfirmedFundingTx => fundingTx.sharedTx }
104+
conflictingTxs.foreach(tx => blockchain ! UnwatchTxConfirmed(tx.txId))
105+
rollbackDualFundingTxs(conflictingTxs)
103106
(commitments1, commitment)
104107
}
105108
}

eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,37 +189,53 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind
189189
import f._
190190

191191
val address = getNewAddress(probe)
192-
val tx = sendToAddress(address, Btc(1), probe)
193-
194-
watcher ! WatchFundingConfirmed(probe.ref, tx.txid, 1)
195-
watcher ! WatchFundingDeeplyBuried(probe.ref, tx.txid, 4)
196-
watcher ! WatchFundingDeeplyBuried(probe.ref, tx.txid, 4) // setting the watch multiple times should be a no-op
192+
val tx1 = sendToAddress(address, Btc(0.7), probe)
193+
val tx2 = sendToAddress(address, Btc(0.5), probe)
194+
195+
watcher ! WatchFundingConfirmed(probe.ref, tx1.txid, 1)
196+
watcher ! WatchFundingDeeplyBuried(probe.ref, tx1.txid, 4)
197+
watcher ! WatchFundingDeeplyBuried(probe.ref, tx1.txid, 4) // setting the watch multiple times should be a no-op
198+
watcher ! WatchFundingConfirmed(probe.ref, tx2.txid, 3)
199+
watcher ! WatchFundingDeeplyBuried(probe.ref, tx2.txid, 6)
197200
probe.expectNoMessage(100 millis)
198201

199202
watcher ! ListWatches(probe.ref)
200-
assert(probe.expectMsgType[Set[Watch[_]]].size == 2)
203+
assert(probe.expectMsgType[Set[Watch[_]]].size == 4)
201204

202205
generateBlocks(1)
203-
assert(probe.expectMsgType[WatchFundingConfirmedTriggered].tx.txid == tx.txid)
206+
assert(probe.expectMsgType[WatchFundingConfirmedTriggered].tx.txid == tx1.txid)
207+
probe.expectNoMessage(100 millis)
208+
209+
watcher ! ListWatches(probe.ref)
210+
assert(probe.expectMsgType[Set[Watch[_]]].size == 3)
211+
212+
generateBlocks(2)
213+
assert(probe.expectMsgType[WatchFundingConfirmedTriggered].tx.txid == tx2.txid)
204214
probe.expectNoMessage(100 millis)
205215

216+
watcher ! ListWatches(probe.ref)
217+
assert(probe.expectMsgType[Set[Watch[_]]].size == 2)
218+
219+
watcher ! UnwatchTxConfirmed(tx2.txid)
206220
watcher ! ListWatches(probe.ref)
207221
assert(probe.expectMsgType[Set[Watch[_]]].size == 1)
208222

209-
generateBlocks(3)
210-
assert(probe.expectMsgType[WatchFundingDeeplyBuriedTriggered].tx.txid == tx.txid)
223+
generateBlocks(1)
224+
assert(probe.expectMsgType[WatchFundingDeeplyBuriedTriggered].tx.txid == tx1.txid)
211225
probe.expectNoMessage(100 millis)
212226

213227
watcher ! ListWatches(probe.ref)
214228
assert(probe.expectMsgType[Set[Watch[_]]].isEmpty)
215229

216230
// If we try to watch a transaction that has already been confirmed, we should immediately receive a WatchEventConfirmed.
217-
watcher ! WatchFundingConfirmed(probe.ref, tx.txid, 1)
218-
assert(probe.expectMsgType[WatchFundingConfirmedTriggered].tx.txid == tx.txid)
219-
watcher ! WatchFundingConfirmed(probe.ref, tx.txid, 2)
220-
assert(probe.expectMsgType[WatchFundingConfirmedTriggered].tx.txid == tx.txid)
221-
watcher ! WatchFundingDeeplyBuried(probe.ref, tx.txid, 4)
222-
assert(probe.expectMsgType[WatchFundingDeeplyBuriedTriggered].tx.txid == tx.txid)
231+
watcher ! WatchFundingConfirmed(probe.ref, tx1.txid, 1)
232+
assert(probe.expectMsgType[WatchFundingConfirmedTriggered].tx.txid == tx1.txid)
233+
watcher ! WatchFundingConfirmed(probe.ref, tx2.txid, 2)
234+
assert(probe.expectMsgType[WatchFundingConfirmedTriggered].tx.txid == tx2.txid)
235+
watcher ! WatchFundingDeeplyBuried(probe.ref, tx1.txid, 4)
236+
assert(probe.expectMsgType[WatchFundingDeeplyBuriedTriggered].tx.txid == tx1.txid)
237+
watcher ! WatchFundingDeeplyBuried(probe.ref, tx2.txid, 4)
238+
assert(probe.expectMsgType[WatchFundingDeeplyBuriedTriggered].tx.txid == tx2.txid)
223239
probe.expectNoMessage(100 millis)
224240

225241
watcher ! ListWatches(probe.ref)

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package fr.acinq.eclair.channel.states.c
1919
import akka.actor.typed.scaladsl.adapter.{ClassicActorRefOps, actorRefAdapter}
2020
import akka.testkit.{TestFSMRef, TestProbe}
2121
import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt}
22-
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, Transaction, TxIn}
22+
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, Transaction}
2323
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
2424
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
2525
import fr.acinq.eclair.blockchain.{CurrentBlockHeight, SingleKeyOnChainWallet}
@@ -33,6 +33,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, SetChannelId
3333
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.FakeTxPublisherFactory
3434
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
3535
import fr.acinq.eclair.transactions.Transactions
36+
import fr.acinq.eclair.transactions.Transactions.ClaimLocalAnchorOutputTx
3637
import fr.acinq.eclair.wire.protocol._
3738
import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
3839
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
@@ -751,7 +752,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
751752
val bobCommitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx
752753
alice ! WatchFundingSpentTriggered(bobCommitTx.tx)
753754
aliceListener.expectMsgType[TransactionPublished]
754-
val claimAnchor = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
755+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
755756
val claimMain = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
756757
assert(claimMain.input.txid == bobCommitTx.tx.txid)
757758
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.tx.txid)
@@ -777,6 +778,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
777778
assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
778779
assert(aliceListener.expectMsgType[ShortChannelIdAssigned].shortIds.real.isInstanceOf[RealScidStatus.Temporary])
779780
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid)
781+
alice2blockchain.expectMsg(UnwatchTxConfirmed(fundingTx2.txId))
780782
alice2blockchain.expectNoMessage(100 millis)
781783
alice2bob.expectNoMessage(100 millis)
782784
awaitCond(alice.stateData.isInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY])
@@ -786,7 +788,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
786788
// Bob broadcasts his commit tx.
787789
alice ! WatchFundingSpentTriggered(bobCommitTx1)
788790
assert(aliceListener.expectMsgType[TransactionPublished].tx.txid == bobCommitTx1.txid)
789-
val claimAnchor = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
791+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
790792
val claimMain = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
791793
assert(claimMain.input.txid == bobCommitTx1.txid)
792794
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx1.txid)
@@ -807,7 +809,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
807809
assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx)
808810
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx.txid)
809811
alice2 ! WatchFundingSpentTriggered(bobData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx)
810-
val claimAnchorAlice = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
812+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
811813
val claimMainAlice = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
812814
assert(claimMainAlice.input.txid == bobData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
813815
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
@@ -818,7 +820,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
818820
assert(bobListener.expectMsgType[TransactionConfirmed].tx == fundingTx)
819821
assert(bob2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx.txid)
820822
bob2 ! WatchFundingSpentTriggered(aliceData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx)
821-
val claimAnchorBob = bob2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
823+
assert(bob2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
822824
val claimMainBob = bob2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
823825
assert(claimMainBob.input.txid == aliceData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
824826
assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
@@ -844,8 +846,9 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
844846
alice2 ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx1)
845847
assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
846848
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid)
849+
alice2blockchain.expectMsg(UnwatchTxConfirmed(fundingTx2.txId))
847850
alice2 ! WatchFundingSpentTriggered(bobCommitTx1)
848-
val claimAnchorAlice = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
851+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
849852
val claimMainAlice = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
850853
assert(claimMainAlice.input.txid == bobCommitTx1.txid)
851854
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx1.txid)
@@ -856,8 +859,9 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
856859
bob2 ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx1)
857860
assert(bobListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
858861
assert(bob2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid)
862+
bob2blockchain.expectMsg(UnwatchTxConfirmed(fundingTx2.txId))
859863
bob2 ! WatchFundingSpentTriggered(aliceCommitTx1)
860-
val claimAnchorBob = bob2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
864+
assert(bob2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
861865
val claimMainBob = bob2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
862866
assert(claimMainBob.input.txid == aliceCommitTx1.txid)
863867
assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx1.txid)
@@ -1016,7 +1020,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
10161020
awaitCond(alice.stateName == CLOSING)
10171021
aliceListener.expectMsgType[ChannelAborted]
10181022
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == aliceCommitTx.txid)
1019-
val claimAnchorLocal = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
1023+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
10201024
val claimMainLocal = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
10211025
assert(claimMainLocal.input.txid == aliceCommitTx.txid)
10221026
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx.txid)
@@ -1025,7 +1029,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
10251029
val bobCommitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
10261030
alice ! WatchFundingSpentTriggered(bobCommitTx)
10271031
alice2blockchain.expectMsgType[WatchOutputSpent]
1028-
val claimAnchorRemote = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
1032+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
10291033
val claimMainRemote = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
10301034
assert(claimMainRemote.input.txid == bobCommitTx.txid)
10311035
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
@@ -1050,7 +1054,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
10501054
awaitCond(alice.stateName == CLOSING)
10511055
aliceListener.expectMsgType[ChannelAborted]
10521056
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == aliceCommitTx2.tx.txid)
1053-
val claimAnchor2 = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
1057+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
10541058
val claimMain2 = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
10551059
assert(claimMain2.input.txid == aliceCommitTx2.tx.txid)
10561060
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx2.tx.txid)
@@ -1061,8 +1065,9 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
10611065
assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
10621066
alice2blockchain.expectMsgType[WatchOutputSpent]
10631067
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid)
1068+
alice2blockchain.expectMsg(UnwatchTxConfirmed(fundingTx2.txId))
10641069
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == aliceCommitTx1.tx.txid)
1065-
val claimAnchor1 = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
1070+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
10661071
val claimMain1 = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
10671072
assert(claimMain1.input.txid == aliceCommitTx1.tx.txid)
10681073
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx1.tx.txid)
@@ -1072,7 +1077,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
10721077
// Bob publishes his commit tx, Alice reacts by spending her remote main output.
10731078
alice ! WatchFundingSpentTriggered(bobCommitTx1.tx)
10741079
alice2blockchain.expectMsgType[WatchOutputSpent]
1075-
val claimAnchorRemote = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
1080+
assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
10761081
val claimMainRemote = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
10771082
assert(claimMainRemote.input.txid == bobCommitTx1.tx.txid)
10781083
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx1.tx.txid)

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,13 +737,15 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
737737

738738
val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(300_000 sat, defaultSpliceOutScriptPubKey)))
739739
val spliceCommitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.find(_.fundingTxId == spliceTx.txid).get
740+
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == spliceTx.txid)
740741

741742
// Alice RBFs the splice transaction.
742743
// Our dummy bitcoin wallet adds an additional input at every funding attempt.
743744
val rbfTx1 = initiateRbf(f, FeeratePerKw(15_000 sat), sInputsCount = 2, sOutputsCount = 2)
744745
assert(rbfTx1.txIn.size == spliceTx.txIn.size + 1)
745746
spliceTx.txIn.foreach(txIn => assert(rbfTx1.txIn.map(_.outPoint).contains(txIn.outPoint)))
746747
assert(rbfTx1.txOut.size == spliceTx.txOut.size)
748+
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == rbfTx1.txid)
747749

748750
// Bob RBFs the splice transaction: he needs to add an input to pay the fees.
749751
// Our dummy bitcoin wallet adds an additional input for Alice: a real bitcoin wallet would simply lower the previous change output.
@@ -752,6 +754,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
752754
assert(rbfTx2.txIn.size > rbfTx1.txIn.size)
753755
rbfTx1.txIn.foreach(txIn => assert(rbfTx2.txIn.map(_.outPoint).contains(txIn.outPoint)))
754756
assert(rbfTx2.txOut.size == rbfTx1.txOut.size + 1)
757+
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == rbfTx2.txid)
755758

756759
// There are three pending splice transactions that double-spend each other.
757760
inside(alice.stateData.asInstanceOf[DATA_NORMAL]) { data =>
@@ -767,6 +770,11 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
767770
confirmSpliceTx(f, rbfTx2)
768771
inside(alice.stateData.asInstanceOf[DATA_NORMAL]) { data =>
769772
assert(data.commitments.active.map(_.fundingTxId) == Seq(rbfTx2.txid))
773+
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == rbfTx2.txid)
774+
alice2blockchain.expectMsgAllOf(
775+
UnwatchTxConfirmed(spliceTx.txid),
776+
UnwatchTxConfirmed(rbfTx1.txid),
777+
)
770778
data.commitments.active.foreach(c => assert(c.localCommit.spec.toLocal == spliceCommitment.localCommit.spec.toLocal))
771779
data.commitments.active.foreach(c => assert(c.localCommit.spec.toRemote == spliceCommitment.localCommit.spec.toRemote))
772780
}

0 commit comments

Comments
 (0)