Skip to content

Commit 007adc5

Browse files
committed
Fixed tests and Router use of UnwatchTxConfirmed
- Fixup so Router does not send `UnwatchTxConfirmed` only for the spending txs that will never confirm. Channel also has a `WatchTxConfirmed` event that may trigger later and should not be removed. - Fixup channel integration tests to generate enough blocks to deeply confirm a channel has closed once before waiting for the channel to close. - Fixup router tests to check that funding tx spend is unwatched after it's spending tx confirms
1 parent 0f250b1 commit 007adc5

File tree

4 files changed

+37
-41
lines changed

4 files changed

+37
-41
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
269269

270270
case Event(WatchTxConfirmedTriggered(_, _, spendingTx), d) =>
271271
d.spentChannels.get(spendingTx.txid) match {
272-
case Some(shortChannelId) => stay() using Validation.handleChannelSpent(d, watcher, nodeParams.db.network, shortChannelId)
272+
case Some(shortChannelId) => stay() using Validation.handleChannelSpent(d, watcher, nodeParams.db.network, spendingTx.txid, shortChannelId)
273273
case None => stay()
274274
}
275275

eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ object Validation {
267267
} else d1
268268
}
269269

270-
def handleChannelSpent(d: Data, watcher: typed.ActorRef[ZmqWatcher.Command], db: NetworkDb, shortChannelId: RealShortChannelId)(implicit ctx: ActorContext, log: LoggingAdapter): Data = {
270+
def handleChannelSpent(d: Data, watcher: typed.ActorRef[ZmqWatcher.Command], db: NetworkDb, spendingTxId: TxId, shortChannelId: RealShortChannelId)(implicit ctx: ActorContext, log: LoggingAdapter): Data = {
271271
implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors
272272
val lostChannel = d.channels.get(shortChannelId).orElse(d.prunedChannels.get(shortChannelId)).get
273273
log.info("funding tx for channelId={} was spent", shortChannelId)
@@ -294,7 +294,8 @@ object Validation {
294294
// we will re-add a spliced channel as a new channel later when we receive the announcement
295295
watcher ! UnwatchExternalChannelSpent(lostChannel.fundingTxId, outputIndex(lostChannel.ann.shortChannelId))
296296
val spendingTxs = d.spentChannels.filter(_._2 == shortChannelId).keySet
297-
spendingTxs.foreach(txId => watcher ! UnwatchTxConfirmed(txId))
297+
// stop watching the spending txs that will never confirm, but continue to watch the tx that spends the parent channel
298+
(spendingTxs - spendingTxId).foreach(txId => watcher ! UnwatchTxConfirmed(txId))
298299
val spentChannels1 = d.spentChannels -- spendingTxs
299300
d.copy(nodes = d.nodes -- lostNodes, channels = channels1, prunedChannels = prunedChannels1, graphWithBalances = graphWithBalances1, spentChannels = spentChannels1)
300301
}

eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,11 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
194194
val receivedByF = listReceivedByAddress(finalAddressF)
195195
(receivedByF diff previouslyReceivedByF).size == expectedTxCountF && (receivedByC diff previouslyReceivedByC).size == expectedTxCountC
196196
}, max = 30 seconds, interval = 1 second)
197-
// we generate blocks to make txs confirm
198-
generateBlocks(2, Some(minerAddress))
197+
// we generate enough blocks for the channel to be deeply confirmed
198+
generateBlocks(12, Some(minerAddress))
199199
// and we wait for the channel to close
200200
awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
201201
awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
202-
203-
// generate enough blocks so the router will know the channel has been closed and not spliced
204-
generateBlocks(12)
205202
awaitAnnouncements(1)
206203
}
207204

@@ -238,14 +235,11 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
238235
val receivedByF = listReceivedByAddress(finalAddressF, sender)
239236
(receivedByF diff previouslyReceivedByF).size == expectedTxCountF && (receivedByC diff previouslyReceivedByC).size == expectedTxCountC
240237
}, max = 30 seconds, interval = 1 second)
241-
// we generate blocks to make txs confirm
242-
generateBlocks(2, Some(minerAddress))
238+
// we generate enough blocks for the channel to be deeply confirmed
239+
generateBlocks(12, Some(minerAddress))
243240
// and we wait for the channel to close
244241
awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
245242
awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
246-
247-
// generate enough blocks so the router will know the channel has been closed and not spliced
248-
generateBlocks(12)
249243
awaitAnnouncements(1)
250244
}
251245

@@ -294,14 +288,11 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
294288
val receivedByF = listReceivedByAddress(finalAddressF, sender)
295289
(receivedByF diff previouslyReceivedByF).size == expectedTxCountF && (receivedByC diff previouslyReceivedByC).size == expectedTxCountC
296290
}, max = 30 seconds, interval = 1 second)
297-
// we generate blocks to make txs confirm
298-
generateBlocks(2, Some(minerAddress))
291+
// we generate enough blocks for the channel to be deeply confirmed
292+
generateBlocks(12, Some(minerAddress))
299293
// and we wait for the channel to close
300294
awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
301295
awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
302-
303-
// generate enough blocks so the router will know the channel has been closed and not spliced
304-
generateBlocks(12)
305296
awaitAnnouncements(1)
306297
}
307298

@@ -353,14 +344,11 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
353344
val receivedByF = listReceivedByAddress(finalAddressF, sender)
354345
(receivedByF diff previouslyReceivedByF).size == expectedTxCountF && (receivedByC diff previouslyReceivedByC).size == expectedTxCountC
355346
}, max = 30 seconds, interval = 1 second)
356-
// we generate blocks to make tx confirm
357-
generateBlocks(2, Some(minerAddress))
347+
// we generate enough blocks for the channel to be deeply confirmed
348+
generateBlocks(12, Some(minerAddress))
358349
// and we wait for the channel to close
359350
awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
360351
awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
361-
362-
// generate enough blocks so the router will know the channel has been closed and not spliced
363-
generateBlocks(12)
364352
awaitAnnouncements(1)
365353
}
366354

@@ -599,15 +587,13 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec {
599587
bitcoinClient.getMempool().pipeTo(sender.ref)
600588
sender.expectMsgType[Seq[Transaction]].exists(_.txIn.head.outPoint.txid == fundingOutpoint.txid)
601589
}, max = 20 seconds, interval = 1 second)
602-
generateBlocks(3)
590+
// we generate enough blocks for the channel to be deeply confirmed
591+
generateBlocks(12)
603592
awaitCond(stateListener.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds)
604593

605-
bitcoinClient.lookForSpendingTx(None, fundingOutpoint.txid, fundingOutpoint.index.toInt, limit = 10).pipeTo(sender.ref)
594+
bitcoinClient.lookForSpendingTx(None, fundingOutpoint.txid, fundingOutpoint.index.toInt, limit = 12).pipeTo(sender.ref)
606595
val closingTx = sender.expectMsgType[Transaction]
607596
assert(closingTx.txOut.map(_.publicKeyScript).toSet == Set(finalPubKeyScriptC, finalPubKeyScriptF))
608-
609-
// generate enough blocks so the router will know the channel has been closed and not spliced
610-
generateBlocks(12)
611597
awaitAnnouncements(1)
612598
}
613599

eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,15 @@ class RouterSpec extends BaseRouterSpec {
319319
probe.expectMsg(PublicNode(node_b, 2, publicChannelCapacity * 2))
320320
}
321321

322+
def fundingTx(node1: PublicKey, node2: PublicKey, capacity: Satoshi = publicChannelCapacity): Transaction = {
323+
val fundingScript = write(pay2wsh(Scripts.multiSig2of2(node1, node2)))
324+
val fundingTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(capacity, fundingScript) :: Nil, lockTime = 0)
325+
fundingTx
326+
}
327+
322328
def spendingTx(node1: PublicKey, node2: PublicKey, capacity: Satoshi = publicChannelCapacity): Transaction = {
323329
val fundingScript = write(pay2wsh(Scripts.multiSig2of2(node1, node2)))
324-
val previousFundingTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(capacity, fundingScript) :: Nil, lockTime = 0)
325-
val nextFundingTx = Transaction(version = 0, txIn = TxIn(OutPoint(previousFundingTx, 0), fundingScript, 0) :: Nil, txOut = TxOut(capacity, fundingScript) :: Nil, lockTime = 0)
330+
val nextFundingTx = Transaction(version = 0, txIn = TxIn(OutPoint(fundingTx(node1, node2, capacity), 0), fundingScript, 0) :: Nil, txOut = TxOut(capacity, fundingScript) :: Nil, lockTime = 0)
326331
nextFundingTx
327332
}
328333

@@ -335,6 +340,7 @@ class RouterSpec extends BaseRouterSpec {
335340
watcher.expectMsgType[WatchTxConfirmed]
336341
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_a, funding_b))
337342
eventListener.expectMsg(ChannelLost(scid_ab))
343+
assert(watcher.expectMsgType[UnwatchExternalChannelSpent].txId == fundingTx(funding_a, funding_b).txid)
338344
assert(nodeParams.db.network.getChannel(scid_ab).isEmpty)
339345
// a doesn't have any channels, b still has one with c
340346
eventListener.expectMsg(NodeLost(a))
@@ -345,6 +351,7 @@ class RouterSpec extends BaseRouterSpec {
345351
router ! WatchExternalChannelSpentTriggered(scid_cd, spendingTx(funding_c, funding_d))
346352
watcher.expectMsgType[WatchTxConfirmed]
347353
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_c, funding_d))
354+
assert(watcher.expectMsgType[UnwatchExternalChannelSpent].txId == fundingTx(funding_c, funding_d).txid)
348355
eventListener.expectMsg(ChannelLost(scid_cd))
349356
assert(nodeParams.db.network.getChannel(scid_cd).isEmpty)
350357
// d doesn't have any channels, c still has one with b
@@ -356,6 +363,7 @@ class RouterSpec extends BaseRouterSpec {
356363
router ! WatchExternalChannelSpentTriggered(scid_bc, spendingTx(funding_b, funding_c))
357364
watcher.expectMsgType[WatchTxConfirmed]
358365
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_b, funding_c))
366+
assert(watcher.expectMsgType[UnwatchExternalChannelSpent].txId == fundingTx(funding_b, funding_c).txid)
359367
eventListener.expectMsg(ChannelLost(scid_bc))
360368
assert(nodeParams.db.network.getChannel(scid_bc).isEmpty)
361369
// now b and c do not have any channels
@@ -374,10 +382,10 @@ class RouterSpec extends BaseRouterSpec {
374382
val priv_funding_u = randomKey()
375383
val scid_au = RealShortChannelId(fixture.nodeParams.currentBlockHeight - 5000, 5, 0)
376384
val ann = channelAnnouncement(scid_au, priv_a, priv_u, priv_funding_a, priv_funding_u)
377-
val fundingTx = Transaction(2, Nil, Seq(TxOut(500_000 sat, write(pay2wsh(Scripts.multiSig2of2(funding_a, priv_funding_u.publicKey))))), 0)
385+
val fundingTx_au = fundingTx(funding_a, priv_funding_u.publicKey, 500_000 sat)
378386
router ! PeerRoutingMessage(TestProbe().ref, remoteNodeId, ann)
379387
watcher.expectMsgType[ValidateRequest]
380-
watcher.send(router, ValidateResult(ann, Right((fundingTx, UtxoStatus.Unspent))))
388+
watcher.send(router, ValidateResult(ann, Right((fundingTx_au, UtxoStatus.Unspent))))
381389
watcher.expectMsgType[WatchExternalChannelSpent]
382390
eventListener.expectMsg(ChannelsDiscovered(SingleChannelDiscovered(ann, 500_000 sat, None, None) :: Nil))
383391
awaitAssert(assert(nodeParams.db.network.getChannel(scid_au).nonEmpty))
@@ -392,6 +400,7 @@ class RouterSpec extends BaseRouterSpec {
392400
router ! WatchExternalChannelSpentTriggered(scid_au, spendingTx(funding_a, priv_funding_u.publicKey))
393401
assert(watcher.expectMsgType[WatchTxConfirmed].txId == spendingTx(funding_a, priv_funding_u.publicKey).txid)
394402
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_a, priv_funding_u.publicKey))
403+
assert(watcher.expectMsgType[UnwatchExternalChannelSpent].txId == fundingTx_au.txid)
395404
eventListener.expectMsg(ChannelLost(scid_au))
396405
eventListener.expectMsg(NodeLost(priv_u.publicKey))
397406
awaitAssert(assert(nodeParams.db.network.getChannel(scid_au).isEmpty))
@@ -905,12 +914,12 @@ class RouterSpec extends BaseRouterSpec {
905914
val scid = RealShortChannelId(fixture.nodeParams.currentBlockHeight - 5000, 5, 0)
906915
val capacity = 1_000_000.sat
907916
val ann = channelAnnouncement(scid, priv_a, priv_c, priv_funding_a, priv_funding_c)
908-
val fundingTx = Transaction(2, Nil, Seq(TxOut(capacity, write(pay2wsh(Scripts.multiSig2of2(funding_a, funding_c))))), 0)
909917
val peerConnection = TestProbe()
918+
val fundingTx_ac = fundingTx(funding_a, funding_c, capacity)
910919
peerConnection.ignoreMsg { case _: TransportHandler.ReadAck => true }
911920
peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, ann))
912921
watcher.expectMsgType[ValidateRequest]
913-
watcher.send(router, ValidateResult(ann, Right((fundingTx, UtxoStatus.Unspent))))
922+
watcher.send(router, ValidateResult(ann, Right((fundingTx_ac, UtxoStatus.Unspent))))
914923
peerConnection.expectMsg(GossipDecision.Accepted(ann))
915924
probe.send(router, GetChannels)
916925
assert(probe.expectMsgType[Iterable[ChannelAnnouncement]].exists(_.shortChannelId == scid))
@@ -928,17 +937,17 @@ class RouterSpec extends BaseRouterSpec {
928937
val staleUpdate = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, scid, CltvExpiryDelta(72), 1 msat, 10 msat, 100, htlcMaximum, timestamp = staleTimestamp)
929938
peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, staleUpdate))
930939
peerConnection.expectMsg(GossipDecision.Stale(staleUpdate))
931-
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx.txid, capacity, None, None, None)))
940+
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx_ac.txid, capacity, None, None, None)))
932941

933942
// We receive a non-stale channel update for one side of the channel.
934943
val update_ac_1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, scid, CltvExpiryDelta(72), 1 msat, 10 msat, 100, htlcMaximum, timestamp = TimestampSecond.now() - 3.days)
935944
peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_ac_1))
936945
peerConnection.expectMsg(GossipDecision.RelatedChannelPruned(update_ac_1))
937946
peerConnection.expectNoMessage(100 millis)
938947
if (update_ac_1.channelFlags.isNode1) {
939-
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx.txid, capacity, Some(update_ac_1), None, None)))
948+
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx_ac.txid, capacity, Some(update_ac_1), None, None)))
940949
} else {
941-
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx.txid, capacity, None, Some(update_ac_1), None)))
950+
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx_ac.txid, capacity, None, Some(update_ac_1), None)))
942951
}
943952
probe.send(router, GetRouterData)
944953
val routerData1 = probe.expectMsgType[Data]
@@ -953,9 +962,9 @@ class RouterSpec extends BaseRouterSpec {
953962
peerConnection.expectMsg(GossipDecision.RelatedChannelPruned(update_ac_2))
954963
peerConnection.expectNoMessage(100 millis)
955964
if (update_ac_2.channelFlags.isNode1) {
956-
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx.txid, capacity, Some(update_ac_2), None, None)))
965+
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx_ac.txid, capacity, Some(update_ac_2), None, None)))
957966
} else {
958-
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx.txid, capacity, None, Some(update_ac_2), None)))
967+
assert(nodeParams.db.network.getChannel(scid).contains(PublicChannel(ann, fundingTx_ac.txid, capacity, None, Some(update_ac_2), None)))
959968
}
960969
probe.send(router, GetRouterData)
961970
val routerData2 = probe.expectMsgType[Data]
@@ -972,9 +981,9 @@ class RouterSpec extends BaseRouterSpec {
972981
assert(routerData3.channels.contains(scid))
973982
assert(!routerData3.prunedChannels.contains(scid))
974983
if (update_ac_2.channelFlags.isNode1) {
975-
assert(routerData3.channels.get(scid).contains(PublicChannel(ann, fundingTx.txid, capacity, Some(update_ac_2), Some(update_ca), None)))
984+
assert(routerData3.channels.get(scid).contains(PublicChannel(ann, fundingTx_ac.txid, capacity, Some(update_ac_2), Some(update_ca), None)))
976985
} else {
977-
assert(routerData3.channels.get(scid).contains(PublicChannel(ann, fundingTx.txid, capacity, Some(update_ca), Some(update_ac_2), None)))
986+
assert(routerData3.channels.get(scid).contains(PublicChannel(ann, fundingTx_ac.txid, capacity, Some(update_ca), Some(update_ac_2), None)))
978987
}
979988
assert(routerData3.graphWithBalances.graph.containsEdge(ChannelDesc(update_ac_2, ann)))
980989
assert(routerData3.graphWithBalances.graph.containsEdge(ChannelDesc(update_ca, ann)))

0 commit comments

Comments
 (0)