@@ -31,10 +31,9 @@ import fr.acinq.eclair.db.NetworkDb
3131import fr .acinq .eclair .router .Graph .GraphStructure .GraphEdge
3232import fr .acinq .eclair .router .Monitoring .Metrics
3333import fr .acinq .eclair .router .Router ._
34- import fr .acinq .eclair .router .Validation .{addPublicChannel , splicePublicChannel }
3534import fr .acinq .eclair .transactions .Scripts
3635import 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
3938object 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 = {
0 commit comments