Skip to content

Commit c214b07

Browse files
authored
Don't rebroadcast announcements for spent channels (#3235)
* Don't rebroadcast spent channels We introduced a mechanism to keep spent channels in the `Router` until the spending transaction has enough confirmations to avoid removing spliced channels from our graph too eagerly. An unwanted side-effect is that while we're waiting for confirmations, we may broadcast channel announcements for these spent channels, which looks like spam to our peers. We now filter out channels that have been spent for which we're waiting for confirmations when sending out gossip. * Notify front nodes immediately when channel is spent Another side-effect of the splice change is that we didn't notify front nodes that the channel was spent before it reached enough confirmations so we kept sending channel gossip to our peers. We now immediately notify the front nodes so that they stop sending this channel to our peers.
1 parent e3fd186 commit c214b07

File tree

3 files changed

+63
-14
lines changed

3 files changed

+63
-14
lines changed

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,18 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
161161
stay()
162162

163163
case Event(TickBroadcast, d) =>
164-
if (d.rebroadcast.channels.isEmpty && d.rebroadcast.updates.isEmpty && d.rebroadcast.nodes.isEmpty) {
164+
// We don't rebroadcast channels that have recently been spent: it may be a splice, but it makes more sense for
165+
// the receiver to directly receive the post-splice announcement.
166+
val spentChannels = d.spentChannels.values.flatten.toSet[ShortChannelId]
167+
val rebroadcast = d.rebroadcast.copy(
168+
channels = d.rebroadcast.channels.filterNot { case (c, _) => spentChannels.contains(c.shortChannelId) },
169+
updates = d.rebroadcast.updates.filterNot { case (u, _) => spentChannels.contains(u.shortChannelId) }
170+
)
171+
if (rebroadcast.channels.isEmpty && rebroadcast.updates.isEmpty && rebroadcast.nodes.isEmpty) {
165172
stay()
166173
} else {
167-
log.debug("staggered broadcast details: channels={} updates={} nodes={}", d.rebroadcast.channels.size, d.rebroadcast.updates.size, d.rebroadcast.nodes.size)
168-
context.system.eventStream.publish(d.rebroadcast)
174+
log.debug("staggered broadcast details: channels={} updates={} nodes={}", rebroadcast.channels.size, rebroadcast.updates.size, rebroadcast.nodes.size)
175+
context.system.eventStream.publish(rebroadcast)
169176
stay() using d.copy(rebroadcast = Rebroadcast(channels = Map.empty, updates = Map.empty, nodes = Map.empty))
170177
}
171178

@@ -269,6 +276,8 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
269276
case Some(spendingTx) =>
270277
log.info("funding tx txId={} of channelId={} has been spent by txId={}: waiting for the spending tx to have enough confirmations before removing the channel from the graph", fundingTxId, shortChannelId, spendingTx.txid)
271278
watcher ! WatchTxConfirmed(self, spendingTx.txid, nodeParams.routerConf.channelSpentSpliceDelay)
279+
// We immediately notify front nodes, to ensure that we stop broadcasting this channel to our peers.
280+
context.system.eventStream.publish(ChannelLost(shortChannelId))
272281
stay() using d.copy(spentChannels = d.spentChannels.updated(spendingTx.txid, d.spentChannels.getOrElse(spendingTx.txid, Set.empty) + shortChannelId))
273282
case None =>
274283
// If the channel was spent by a transaction that is already confirmed, it would be very inefficient to scan
@@ -312,14 +321,18 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
312321
stay() using Sync.handleSendChannelQuery(d, s)
313322

314323
case Event(PeerRoutingMessage(peerConnection, remoteNodeId, q: QueryChannelRange), d) =>
315-
Sync.handleQueryChannelRange(d.channels, nodeParams.routerConf, RemoteGossip(peerConnection, remoteNodeId), q)
324+
val spentChannels = d.spentChannels.values.flatten.toSet
325+
val channels = d.channels.filterNot { case (scid, _) => spentChannels.contains(scid) }
326+
Sync.handleQueryChannelRange(channels, nodeParams.routerConf, RemoteGossip(peerConnection, remoteNodeId), q)
316327
stay()
317328

318329
case Event(PeerRoutingMessage(peerConnection, remoteNodeId, r: ReplyChannelRange), d) =>
319330
stay() using Sync.handleReplyChannelRange(d, nodeParams.routerConf, RemoteGossip(peerConnection, remoteNodeId), r)
320331

321332
case Event(PeerRoutingMessage(peerConnection, remoteNodeId, q: QueryShortChannelIds), d) =>
322-
Sync.handleQueryShortChannelIds(d.nodes, d.channels, RemoteGossip(peerConnection, remoteNodeId), q)
333+
val spentChannels = d.spentChannels.values.flatten.toSet
334+
val channels = d.channels.filterNot { case (scid, _) => spentChannels.contains(scid) }
335+
Sync.handleQueryShortChannelIds(d.nodes, channels, RemoteGossip(peerConnection, remoteNodeId), q)
323336
stay()
324337

325338
case Event(PeerRoutingMessage(peerConnection, remoteNodeId, r: ReplyShortChannelIdsEnd), d) =>

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ object Sync {
168168
var updateCount = 0
169169
var nodeCount = 0
170170

171-
processChannelQuery(nodes, channels)(
171+
processChannelQuery(nodes, channels,
172172
q.shortChannelIds.array,
173173
flags,
174174
ca => {
@@ -291,13 +291,13 @@ object Sync {
291291
* @param onNode called when a node announcement matches
292292
*
293293
*/
294-
def processChannelQuery(nodes: Map[PublicKey, NodeAnnouncement],
295-
channels: SortedMap[RealShortChannelId, PublicChannel])(
296-
ids: List[RealShortChannelId],
297-
flags: List[Long],
298-
onChannel: ChannelAnnouncement => Unit,
299-
onUpdate: ChannelUpdate => Unit,
300-
onNode: NodeAnnouncement => Unit)(implicit log: LoggingAdapter): Unit = {
294+
private def processChannelQuery(nodes: Map[PublicKey, NodeAnnouncement],
295+
channels: SortedMap[RealShortChannelId, PublicChannel],
296+
ids: List[RealShortChannelId],
297+
flags: List[Long],
298+
onChannel: ChannelAnnouncement => Unit,
299+
onUpdate: ChannelUpdate => Unit,
300+
onNode: NodeAnnouncement => Unit)(implicit log: LoggingAdapter): Unit = {
301301
import QueryShortChannelIdsTlv.QueryFlagType
302302

303303
// we loop over channel ids and query flag. We track node Ids for node announcement
@@ -500,7 +500,7 @@ object Sync {
500500
checksums = checksums)
501501
}
502502

503-
def addToSync(syncMap: Map[PublicKey, Syncing], current: Syncing, remoteNodeId: PublicKey, pending: List[QueryShortChannelIds]): (Map[PublicKey, Syncing], Option[QueryShortChannelIds]) = {
503+
private def addToSync(syncMap: Map[PublicKey, Syncing], current: Syncing, remoteNodeId: PublicKey, pending: List[QueryShortChannelIds]): (Map[PublicKey, Syncing], Option[QueryShortChannelIds]) = {
504504
pending match {
505505
case head :: rest =>
506506
// they may send back several reply_channel_range messages for a single query_channel_range query, and we must not

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,28 @@ class RouterSpec extends BaseRouterSpec {
303303
router ! Router.TickBroadcast
304304
eventListener.expectNoMessage(100 millis)
305305
}
306+
{
307+
// funding tx spent (splice)
308+
val priv_z = randomKey()
309+
val priv_funding_z = randomKey()
310+
val chan_az = channelAnnouncement(RealShortChannelId(BlockHeight(420000), 0, 0), priv_z, priv_a, priv_funding_z, priv_funding_a)
311+
peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_az))
312+
assert(watcher.expectMsgType[ValidateRequest].ann == chan_az)
313+
watcher.send(router, ValidateResult(chan_az, Right(Transaction(2, Nil, TxOut(1000000 sat, write(pay2wsh(Scripts.multiSig2of2(funding_a, priv_funding_z.publicKey)))) :: Nil, 0), UtxoStatus.Unspent)))
314+
peerConnection.expectMsg(TransportHandler.ReadAck(chan_az))
315+
peerConnection.expectMsg(GossipDecision.Accepted(chan_az))
316+
assert(watcher.expectMsgType[WatchExternalChannelSpent].shortChannelId == chan_az.shortChannelId)
317+
eventListener.expectMsg(ChannelsDiscovered(SingleChannelDiscovered(chan_az, 1000000 sat, None, None) :: Nil))
318+
peerConnection.expectNoMessage(100 millis)
319+
eventListener.expectNoMessage(100 millis)
320+
// The channel is spent by a splice transaction: it is removed from the rebroadcast list.
321+
router ! WatchExternalChannelSpentTriggered(RealShortChannelId(BlockHeight(420000), 0, 0), Some(spendingTx(funding_a, priv_funding_z.publicKey)))
322+
watcher.expectMsgType[WatchTxConfirmed]
323+
// We notify front nodes to ensure they also stop broadcasting this channel.
324+
eventListener.expectMsg(ChannelLost(RealShortChannelId(BlockHeight(420000), 0, 0)))
325+
router ! Router.TickBroadcast
326+
eventListener.expectNoMessage(100 millis)
327+
}
306328

307329
watcher.expectNoMessage(100 millis)
308330
}
@@ -343,6 +365,7 @@ class RouterSpec extends BaseRouterSpec {
343365

344366
router ! WatchExternalChannelSpentTriggered(scid_ab, Some(spendingTx(funding_a, funding_b)))
345367
watcher.expectMsgType[WatchTxConfirmed]
368+
eventListener.expectMsg(ChannelLost(scid_ab))
346369
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_a, funding_b))
347370
watcher.expectMsg(UnwatchExternalChannelSpent(channels(scid_ab).fundingTxId, ShortChannelId.outputIndex(scid_ab)))
348371
eventListener.expectMsg(ChannelLost(scid_ab))
@@ -365,6 +388,7 @@ class RouterSpec extends BaseRouterSpec {
365388

366389
router ! WatchExternalChannelSpentTriggered(scid_bc, Some(spendingTx(funding_b, funding_c)))
367390
watcher.expectMsgType[WatchTxConfirmed]
391+
eventListener.expectMsg(ChannelLost(scid_bc))
368392
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_b, funding_c))
369393
watcher.expectMsg(UnwatchExternalChannelSpent(channels(scid_bc).fundingTxId, ShortChannelId.outputIndex(scid_bc)))
370394
eventListener.expectMsg(ChannelLost(scid_bc))
@@ -406,6 +430,7 @@ class RouterSpec extends BaseRouterSpec {
406430
// The channel is closed, now we can remove it from the DB.
407431
router ! WatchExternalChannelSpentTriggered(scid_au, Some(spendingTx(funding_a, priv_funding_u.publicKey)))
408432
assert(watcher.expectMsgType[WatchTxConfirmed].txId == spendingTx(funding_a, priv_funding_u.publicKey).txid)
433+
eventListener.expectMsg(ChannelLost(scid_au))
409434
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_a, priv_funding_u.publicKey))
410435
watcher.expectMsg(UnwatchExternalChannelSpent(channels(scid_au).fundingTxId, ShortChannelId.outputIndex(scid_au)))
411436
eventListener.expectMsg(ChannelLost(scid_au))
@@ -1196,6 +1221,7 @@ class RouterSpec extends BaseRouterSpec {
11961221
assert(w.txId == spliceTx1.txid)
11971222
assert(w.minDepth == 12)
11981223
}
1224+
eventListener.expectMsg(ChannelLost(scid_ab))
11991225
eventListener.expectNoMessage(100 millis)
12001226

12011227
// Channel ab is spent and confirmed by an RBF of splice tx.
@@ -1206,6 +1232,7 @@ class RouterSpec extends BaseRouterSpec {
12061232
assert(w.txId == spliceTx2.txid)
12071233
assert(w.minDepth == 12)
12081234
}
1235+
eventListener.expectMsg(ChannelLost(scid_ab))
12091236
eventListener.expectNoMessage(100 millis)
12101237

12111238
// The splice of channel ab is announced.
@@ -1246,37 +1273,43 @@ class RouterSpec extends BaseRouterSpec {
12461273
assert(w.txId == spliceTx_ab.txid)
12471274
assert(w.minDepth == 12)
12481275
}
1276+
eventListener.expectMsg(ChannelLost(scid_ab))
12491277

12501278
// Channel bc is spent by a splice tx.
12511279
router ! WatchExternalChannelSpentTriggered(scid_bc, Some(spliceTx_bc))
12521280
inside(watcher.expectMsgType[WatchTxConfirmed]) { w =>
12531281
assert(w.txId == spliceTx_bc.txid)
12541282
assert(w.minDepth == 12)
12551283
}
1284+
eventListener.expectMsg(ChannelLost(scid_bc))
12561285

12571286
// Channel bc2 is spent by a splice tx.
12581287
router ! WatchExternalChannelSpentTriggered(scid_bc2, Some(spliceTx_bc2))
12591288
inside(watcher.expectMsgType[WatchTxConfirmed]) { w =>
12601289
assert(w.txId == spliceTx_bc2.txid)
12611290
assert(w.minDepth == 12)
12621291
}
1292+
eventListener.expectMsg(ChannelLost(scid_bc2))
12631293

12641294
// Channels ab, bc and bc2 are all spent by the same batch splice tx.
12651295
router ! WatchExternalChannelSpentTriggered(scid_ab, Some(batchSpliceTx))
12661296
inside(watcher.expectMsgType[WatchTxConfirmed]) { w =>
12671297
assert(w.txId == batchSpliceTx.txid)
12681298
assert(w.minDepth == 12)
12691299
}
1300+
eventListener.expectMsg(ChannelLost(scid_ab))
12701301
router ! WatchExternalChannelSpentTriggered(scid_bc, Some(batchSpliceTx))
12711302
inside(watcher.expectMsgType[WatchTxConfirmed]) { w =>
12721303
assert(w.txId == batchSpliceTx.txid)
12731304
assert(w.minDepth == 12)
12741305
}
1306+
eventListener.expectMsg(ChannelLost(scid_bc))
12751307
router ! WatchExternalChannelSpentTriggered(scid_bc2, Some(batchSpliceTx))
12761308
inside(watcher.expectMsgType[WatchTxConfirmed]) { w =>
12771309
assert(w.txId == batchSpliceTx.txid)
12781310
assert(w.minDepth == 12)
12791311
}
1312+
eventListener.expectMsg(ChannelLost(scid_bc2))
12801313

12811314
// Channels ab, bc and bc2 are also all spent by an RBF of the batch splice tx.
12821315
val newCapacity_ab_RBF = newCapacity_ab - 1000.sat
@@ -1286,16 +1319,19 @@ class RouterSpec extends BaseRouterSpec {
12861319
assert(w.txId == batchSpliceTx_RBF.txid)
12871320
assert(w.minDepth == 12)
12881321
}
1322+
eventListener.expectMsg(ChannelLost(scid_ab))
12891323
router ! WatchExternalChannelSpentTriggered(scid_bc, Some(batchSpliceTx_RBF))
12901324
inside(watcher.expectMsgType[WatchTxConfirmed]) { w =>
12911325
assert(w.txId == batchSpliceTx_RBF.txid)
12921326
assert(w.minDepth == 12)
12931327
}
1328+
eventListener.expectMsg(ChannelLost(scid_bc))
12941329
router ! WatchExternalChannelSpentTriggered(scid_bc2, Some(batchSpliceTx_RBF))
12951330
inside(watcher.expectMsgType[WatchTxConfirmed]) { w =>
12961331
assert(w.txId == batchSpliceTx_RBF.txid)
12971332
assert(w.minDepth == 12)
12981333
}
1334+
eventListener.expectMsg(ChannelLost(scid_bc2))
12991335

13001336
// The router tracks the possible spending txs for channels ab, bc and bc2.
13011337
val sender = TestProbe()

0 commit comments

Comments
 (0)