Skip to content

Commit 43a486e

Browse files
committed
Fix nits/issues found by @t-bast
Also fixed two other issues: - add the splice channel to the routing graph as a new channel if the parent channel does not exist (could be pruned) - when a splice confirms, also send ChannelLost to remove the parent scid from the FrontRouter
1 parent 31c57f4 commit 43a486e

File tree

3 files changed

+32
-16
lines changed

3 files changed

+32
-16
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
6666
context.system.eventStream.subscribe(self, classOf[LocalChannelUpdate])
6767
context.system.eventStream.subscribe(self, classOf[LocalChannelDown])
6868
context.system.eventStream.subscribe(self, classOf[AvailableBalanceChanged])
69-
context.system.eventStream.subscribe(self, classOf[CurrentBlockHeight])
7069
context.system.eventStream.publish(SubscriptionsComplete(this.getClass))
7170

7271
startTimerWithFixedDelay(TickBroadcast.toString, TickBroadcast, nodeParams.routerConf.routerBroadcastInterval)
@@ -265,7 +264,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
265264

266265
case Event(WatchExternalChannelSpentTriggered(shortChannelId, spendingTx), d) if d.channels.contains(shortChannelId) || d.prunedChannels.contains(shortChannelId) =>
267266
val txId = d.channels.getOrElse(shortChannelId, d.prunedChannels(shortChannelId)).fundingTxId
268-
log.info("funding tx txId={} of channelId={} has been spent - delay removing it from the graph until {} blocks after the spend confirms", txId, shortChannelId, ANNOUNCEMENTS_MINCONF * 2)
267+
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", txId, shortChannelId, spendingTx.txid)
269268
watcher ! WatchTxConfirmed(self, spendingTx.txid, ANNOUNCEMENTS_MINCONF * 2)
270269
stay() using d.copy(spentChannels = d.spentChannels + (spendingTx.txid -> shortChannelId))
271270

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@ import fr.acinq.eclair.db.NetworkDb
3131
import fr.acinq.eclair.router.Graph.GraphStructure.GraphEdge
3232
import fr.acinq.eclair.router.Monitoring.Metrics
3333
import fr.acinq.eclair.router.Router._
34-
import fr.acinq.eclair.router.Validation.{addPublicChannel, splicePublicChannel}
3534
import fr.acinq.eclair.transactions.Scripts
3635
import fr.acinq.eclair.wire.protocol._
37-
import fr.acinq.eclair.{BlockHeight, Logs, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TxCoordinates}
36+
import fr.acinq.eclair.{BlockHeight, Logs, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TimestampSecond, TxCoordinates}
3837

3938
object Validation {
4039

@@ -115,7 +114,15 @@ object Validation {
115114
remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.Accepted(c)))
116115
val capacity = tx.txOut(outputIndex).amount
117116
d0.spentChannels.get(tx.txid) match {
118-
case Some(parentScid) => Some(splicePublicChannel(d0, nodeParams, watcher, c, tx.txid, capacity, d0.channels(parentScid)))
117+
case Some(parentScid) =>
118+
d0.channels.get(parentScid) match {
119+
case Some(parentChannel) =>
120+
Some(updateSplicedPublicChannel(d0, nodeParams, watcher, c, tx.txid, capacity, parentChannel))
121+
case None =>
122+
log.error("spent parent channel shortChannelId={} not found for splice shortChannelId={}", parentScid, c.shortChannelId)
123+
val spentChannels1 = d0.spentChannels.filter(_._2 != parentScid)
124+
Some(addPublicChannel(d0, nodeParams, watcher, c, tx.txid, capacity, None).copy(spentChannels = spentChannels1))
125+
}
119126
case None => Some(addPublicChannel(d0, nodeParams, watcher, c, tx.txid, capacity, None))
120127
}
121128
}
@@ -160,35 +167,40 @@ object Validation {
160167
}
161168
}
162169

163-
private def splicePublicChannel(d: Data, nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], ann: ChannelAnnouncement, spliceTxId: TxId, capacity: Satoshi, parentChannel: PublicChannel)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = {
170+
private def updateSplicedPublicChannel(d: Data, nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], ann: ChannelAnnouncement, spliceTxId: TxId, capacity: Satoshi, parentChannel: PublicChannel)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = {
164171
implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors
165172
val fundingOutputIndex = outputIndex(ann.shortChannelId)
166173
watcher ! WatchExternalChannelSpent(ctx.self, spliceTxId, fundingOutputIndex, ann.shortChannelId)
174+
// we notify front nodes that the channel has been replaced
167175
ctx.system.eventStream.publish(ChannelsDiscovered(SingleChannelDiscovered(ann, capacity, None, None) :: Nil))
176+
ctx.system.eventStream.publish(ChannelLost(parentChannel.shortChannelId))
168177
nodeParams.db.network.addChannel(ann, spliceTxId, capacity)
169178
nodeParams.db.network.removeChannel(parentChannel.shortChannelId)
170-
val pubChan = PublicChannel(
179+
val newPubChan = parentChannel.copy(
171180
ann = ann,
172181
fundingTxId = spliceTxId,
173182
capacity = capacity,
174-
update_1_opt = None,
175-
update_2_opt = None,
176-
meta_opt = parentChannel.meta_opt
183+
// update the timestamps of the channel updates to ensure the spliced channel is not pruned
184+
update_1_opt = parentChannel.update_1_opt.map(_.copy(shortChannelId = ann.shortChannelId, timestamp = TimestampSecond.now())),
185+
update_2_opt = parentChannel.update_2_opt.map(_.copy(shortChannelId = ann.shortChannelId, timestamp = TimestampSecond.now())),
177186
)
178-
log.debug("replacing parent channel scid={} with splice channel scid={}; splice channel={}", parentChannel.shortChannelId, ann.shortChannelId, pubChan)
187+
log.debug("replacing parent channel scid={} with splice channel scid={}; splice channel={}", parentChannel.shortChannelId, ann.shortChannelId, newPubChan)
179188
// we need to update the graph because the edge identifiers and capacity change from the parent scid to the new splice scid
180-
log.debug("updating the graph for shortChannelId={}", pubChan.shortChannelId)
189+
log.debug("updating the graph for shortChannelId={}", newPubChan.shortChannelId)
181190
val graph1 = d.graphWithBalances.updateChannel(ChannelDesc(parentChannel.shortChannelId, parentChannel.nodeId1, parentChannel.nodeId2), ann.shortChannelId, capacity)
191+
val spentChannels1 = d.spentChannels.filter(_._2 != parentChannel.shortChannelId)
182192
d.copy(
183193
// we also add the splice scid -> channelId and remove the parent scid -> channelId mappings
184-
channels = d.channels + (pubChan.shortChannelId -> pubChan) - parentChannel.shortChannelId,
194+
channels = d.channels + (newPubChan.shortChannelId -> newPubChan) - parentChannel.shortChannelId,
195+
// remove the parent channel from the pruned channels
196+
prunedChannels = d.prunedChannels - parentChannel.shortChannelId,
185197
// we also add the newly validated channels to the rebroadcast queue
186198
rebroadcast = d.rebroadcast.copy(
187199
// we rebroadcast the splice channel to our peers
188-
channels = d.rebroadcast.channels + (pubChan.ann -> d.awaiting.getOrElse(pubChan.ann, if (pubChan.nodeId1 == nodeParams.nodeId || pubChan.nodeId2 == nodeParams.nodeId) Seq(LocalGossip) else Nil).toSet),
200+
channels = d.rebroadcast.channels + (newPubChan.ann -> d.awaiting.getOrElse(newPubChan.ann, if (isRelatedTo(ann, nodeParams.nodeId)) Seq(LocalGossip) else Nil).toSet),
189201
),
190202
graphWithBalances = graph1,
191-
spentChannels = d.spentChannels.filter(_._2 != parentChannel.shortChannelId)
203+
spentChannels = spentChannels1
192204
)
193205
}
194206

@@ -272,7 +284,11 @@ object Validation {
272284
db.removeNode(nodeId)
273285
ctx.system.eventStream.publish(NodeLost(nodeId))
274286
}
275-
d.copy(nodes = d.nodes -- lostNodes, channels = channels1, prunedChannels = prunedChannels1, graphWithBalances = graphWithBalances1, spentChannels = d.spentChannels.filter(_._2 != shortChannelId))
287+
// we no longer need to track this or alternative transactions that spent the parent channel
288+
// either this channel was really closed, or it was spliced and the announcement was not received in time
289+
// we will re-add a spliced channel as a new channel later when we receive the announcement
290+
val spentChannels1 = d.spentChannels.filter(_._2 != shortChannelId)
291+
d.copy(nodes = d.nodes -- lostNodes, channels = channels1, prunedChannels = prunedChannels1, graphWithBalances = graphWithBalances1, spentChannels = spentChannels1)
276292
}
277293

278294
def handleNodeAnnouncement(d: Data, db: NetworkDb, origins: Set[GossipOrigin], n: NodeAnnouncement, wasStashed: Boolean = false)(implicit ctx: ActorContext, log: LoggingAdapter): Data = {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,7 @@ class RouterSpec extends BaseRouterSpec {
11461146
// The channel update for the splice is confirmed and the channel is not removed.
11471147
router ! WatchTxConfirmedTriggered(BlockHeight(0), 0, spendingTx(funding_a, funding_b))
11481148
eventListener.expectMsg(ChannelsDiscovered(SingleChannelDiscovered(spliceAnn, newCapacity1, None, None) :: Nil))
1149+
eventListener.expectMsg(ChannelLost(scid_ab))
11491150
peerConnection.expectNoMessage(100 millis)
11501151
eventListener.expectNoMessage(100 millis)
11511152

0 commit comments

Comments
 (0)