Don't load network graph twice on start-up (#3290)
What changed, and why it matters
This commit is a performance and startup-time optimization for the Eclair Lightning node. It changes the startup sequence so the network graph (channels and nodes) is read from the database once instead of twice. The Router actor now receives the already-loaded data via a new Init message rather than loading it itself. There is no security vulnerability here; the change is purely about efficiency and code structure.
No security action required. This is a routine performance refactor. Reviewers may want to verify that the Router still correctly handles the Init message and that no code path creates a Router without sending Init, but the diff shows tests and Setup were updated consistently.
Security signals we found
No security-relevant behavior change identified
Refactoring of actor initialization order only
Removed router.init-timeout configuration option
Evidence from the diff
The patch removes the second DB load of network graph data during node startup. DBChecker.checkNetworkDB now returns (SortedMap[RealShortChannelId, Router.PublicChannel], Seq[NodeAnnouncement]) instead of Unit. The Router actor is created without an initialization Promise, then sent a Router.Init message containing the pre-loaded channels/nodes and an optional Promise. The Router’s FSM now starts in WAIT_FOR_INIT with a Nothing state, transitions to NORMAL on receiving Init, and performs the same pruning, metrics, event publishing, and watch-spent scheduling as before. The router.init-timeout config key is removed because startup now awaits routerInitialized.future directly without a separate timeout future. Test fixtures are updated to send Router.Init explicitly.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/Setup.scalaeclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scalaeclair-core/src/main/scala/fr/acinq/eclair/router/Router.scalaeclair-core/src/main/resources/reference.confInspect captured patch +178 / −140
diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf
index 7b03f01..2eda41c 100644
--- a/eclair-core/src/main/resources/reference.conf
+++ b/eclair-core/src/main/resources/reference.conf
@@ -438,7 +438,6 @@ eclair {
channel-exclude-duration = 60 seconds // when a temporary channel failure is returned, we exclude the channel from our payment routes for this duration
broadcast-interval = 60 seconds // see BOLT #7
- init-timeout = 5 minutes
balance-estimate-half-life = 1 day // time after which the confidence of the balance estimate is halved
sync {
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala b/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala
index 2a6c9b8..bacd948 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala
@@ -17,8 +17,11 @@
package fr.acinq.eclair
import fr.acinq.eclair.channel.{ChannelDataWithCommitments, PersistentChannelData}
+import fr.acinq.eclair.router.Router
+import fr.acinq.eclair.wire.protocol.NodeAnnouncement
import grizzled.slf4j.Logging
+import scala.collection.immutable.SortedMap
import scala.util.{Failure, Success, Try}
object DBChecker extends Logging {
@@ -47,9 +50,9 @@ object DBChecker extends Logging {
/**
* Tests if the network database is readable.
*/
- def checkNetworkDB(nodeParams: NodeParams): Unit =
+ def checkNetworkDB(nodeParams: NodeParams): (SortedMap[RealShortChannelId, Router.PublicChannel], Seq[NodeAnnouncement]) =
Try(nodeParams.db.network.listChannels(), nodeParams.db.network.listNodes()) match {
- case Success(_) => ()
+ case Success((channels, nodes)) => (channels, nodes)
case Failure(_) => throw IncompatibleNetworkDBException
}
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Logs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Logs.scala
index 878b6ba..9c4d077 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/Logs.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/Logs.scala
@@ -30,6 +30,7 @@ import fr.acinq.eclair.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.io.{Peer, PeerConnection}
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router._
+import fr.acinq.eclair.wire.protocol
import fr.acinq.eclair.wire.protocol._
import grizzled.slf4j.Logger
@@ -153,7 +154,7 @@ object Logs {
case _: PeerConnection.DelayedRebroadcast => Some(LogCategory.ROUTING_SYNC)
case _: Ping => Some(LogCategory.CONNECTION)
case _: Pong => Some(LogCategory.CONNECTION)
- case _: Init => Some(LogCategory.CONNECTION)
+ case _: protocol.Init => Some(LogCategory.CONNECTION)
case _: Rebroadcast => Some(LogCategory.ROUTING_SYNC)
case _ => None
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
index 70cbe8b..4124e03 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
@@ -233,7 +233,7 @@ class Setup(val datadir: File,
channelsListenerReady = Promise[Done]()
channels = DBChecker.checkChannelsDB(nodeParams)
- _ = DBChecker.checkNetworkDB(nodeParams)
+ (networkChannels, networkNodes) = DBChecker.checkNetworkDB(nodeParams)
defaultFeerates = {
val confDefaultFeerates = FeeratesPerKB(
@@ -341,9 +341,9 @@ class Setup(val datadir: File,
system.spawn(Behaviors.supervise(ZmqWatcher(nodeParams, blockHeight, watcherBitcoinClient)).onFailure(typed.SupervisorStrategy.resume), "zmq-watcher")
}
- router = system.actorOf(SimpleSupervisor.props(Router.props(nodeParams, watcher, Some(routerInitialized)), "router", SupervisorStrategy.Resume))
- routerTimeout = after(FiniteDuration(config.getDuration("router.init-timeout").getSeconds, TimeUnit.SECONDS), using = system.scheduler)(Future.failed(new RuntimeException("Router initialization timed out")))
- _ <- Future.firstCompletedOf(routerInitialized.future :: routerTimeout :: Nil)
+ router = system.actorOf(SimpleSupervisor.props(Router.props(nodeParams, watcher), "router", SupervisorStrategy.Resume))
+ _ = router ! Router.Init(networkChannels, networkNodes, Some(routerInitialized))
+ _ <- routerInitialized.future
_ = bitcoinClient.getReceiveAddress().map(address => logger.info(s"initial address=$address for bitcoin wallet=${bitcoinClient.rpcClient.wallet.getOrElse("(default)")}"))
channelsListener = system.spawn(ChannelsListener(channelsListenerReady), name = "channels-listener")
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala
index 16fdea1..22ca823 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala
@@ -24,9 +24,10 @@ import fr.acinq.eclair.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.io.Switchboard.RouterPeerConf
import fr.acinq.eclair.io.{ClientSpawner, Peer, PeerConnection, Switchboard}
import fr.acinq.eclair.payment.relay.Relayer.RelayFees
-import fr.acinq.eclair.router.Graph.{HeuristicsConstants, PaymentPathWeight, WeightRatios}
+import fr.acinq.eclair.router.Graph.HeuristicsConstants
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router._
+import fr.acinq.eclair.wire.protocol
import fr.acinq.eclair.wire.protocol.CommonCodecs._
import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._
import fr.acinq.eclair.wire.protocol.QueryChannelRangeTlv.queryFlagsCodec
@@ -91,8 +92,8 @@ object EclairInternalsSerializer {
val messageRouteParamsCodec: Codec[MessageRouteParams] = (
("maxRouteLength" | int32) ::
(("baseFactor" | double) ::
- ("ageFactor" | double) ::
- ("capacityFactor" | double)).as[Graph.MessageWeightRatios]).as[MessageRouteParams]
+ ("ageFactor" | double) ::
+ ("capacityFactor" | double)).as[Graph.MessageWeightRatios]).as[MessageRouteParams]
val syncConfCodec: Codec[Router.SyncConf] = (
("requestNodeAnnouncements" | bool(8)) ::
@@ -136,7 +137,7 @@ object EclairInternalsSerializer {
val peerConnectionKillCodec: Codec[PeerConnection.Kill] = peerConnectionKillReasonCodec.as[PeerConnection.Kill]
- val lengthPrefixedInitCodec: Codec[Init] = variableSizeBytes(uint16, initCodec)
+ val lengthPrefixedInitCodec: Codec[protocol.Init] = variableSizeBytes(uint16, initCodec)
val lengthPrefixedNodeAnnouncementCodec: Codec[NodeAnnouncement] = variableSizeBytes(uint16, nodeAnnouncementCodec)
val lengthPrefixedChannelAnnouncementCodec: Codec[ChannelAnnouncement] = variableSizeBytes(uint16, channelAnnouncementCodec)
val lengthPrefixedChannelUpdateCodec: Codec[ChannelUpdate] = variableSizeBytes(uint16, channelUpdateCodec)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala
index e7a47b1..ad1a48d 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala
@@ -29,7 +29,6 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{Sphinx, TransportHandler}
-import fr.acinq.eclair.db.NetworkDb
import fr.acinq.eclair.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.payment.Invoice.ExtraEdge
import fr.acinq.eclair.payment.relay.Relayer
@@ -51,7 +50,7 @@ import scala.util.{Random, Try}
/**
* Created by PM on 24/05/2016.
*/
-class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], initialized: Option[Promise[Done]] = None) extends FSMDiagnosticActorLogging[Router.State, Router.Data] {
+class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command]) extends FSMDiagnosticActorLogging[Router.State, Router.RouterData] {
import Router._
@@ -60,62 +59,61 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
// we pass these to helpers classes so that they have the logging context
implicit def implicitLog: DiagnosticLoggingAdapter = diagLog
- context.system.eventStream.subscribe(self, classOf[ShortChannelIdAssigned])
- context.system.eventStream.subscribe(self, classOf[LocalChannelUpdate])
- context.system.eventStream.subscribe(self, classOf[LocalChannelDown])
- context.system.eventStream.subscribe(self, classOf[AvailableBalanceChanged])
- context.system.eventStream.publish(SubscriptionsComplete(this.getClass))
-
- startTimerWithFixedDelay(TickBroadcast.toString, TickBroadcast, nodeParams.routerConf.routerBroadcastInterval)
- startTimerWithFixedDelay(TickPruneStaleChannels.toString, TickPruneStaleChannels, 1 hour)
-
- val db: NetworkDb = nodeParams.db.network
-
- {
- log.info("loading network announcements from db...")
- val (pruned, channels) = db.listChannels().partition { case (_, pc) => pc.isStale(nodeParams.currentBlockHeight) }
- val nodes = db.listNodes()
- Metrics.Nodes.withoutTags().update(nodes.size)
- Metrics.Channels.withoutTags().update(channels.size)
- log.info("loaded from db: channels={} nodes={}", channels.size, nodes.size)
- log.info("{} pruned channels at blockHeight={}", pruned.size, nodeParams.currentBlockHeight)
- // this will be used to calculate routes
- val graph = DirectedGraph.makeGraph(channels, nodes)
- // send events for remaining channels/nodes
- context.system.eventStream.publish(ChannelsDiscovered(channels.values.map(pc => SingleChannelDiscovered(pc.ann, pc.capacity, pc.update_1_opt, pc.update_2_opt))))
- context.system.eventStream.publish(ChannelUpdatesReceived(channels.values.flatMap(pc => pc.update_1_opt ++ pc.update_2_opt ++ Nil)))
- context.system.eventStream.publish(NodesDiscovered(nodes))
-
- // watch the funding tx of all these channels
- // note: some of them may already have been spent, in that case we will receive the watch event immediately
- (channels.values ++ pruned.values).foreach { pc =>
- val txid = pc.fundingTxId
- val outputIndex = ShortChannelId.coordinates(pc.ann.shortChannelId).outputIndex
- // avoid herd effect at startup because watch-spent are intensive in terms of rpc calls to bitcoind
- context.system.scheduler.scheduleOnce(Random.nextLong(nodeParams.routerConf.watchSpentWindow.toSeconds).seconds) {
- watcher ! WatchExternalChannelSpent(self, txid, outputIndex, pc.ann.shortChannelId)
+ startWith(WAIT_FOR_INIT, Nothing)
+
+ when(WAIT_FOR_INIT) {
+ case Event(Router.Init(publicChannels, publicNodes, initialized_opt), _) =>
+ context.system.eventStream.subscribe(self, classOf[ShortChannelIdAssigned])
+ context.system.eventStream.subscribe(self, classOf[LocalChannelUpdate])
+ context.system.eventStream.subscribe(self, classOf[LocalChannelDown])
+ context.system.eventStream.subscribe(self, classOf[AvailableBalanceChanged])
+ context.system.eventStream.publish(SubscriptionsComplete(this.getClass))
+
+ startTimerWithFixedDelay(TickBroadcast.toString, TickBroadcast, nodeParams.routerConf.routerBroadcastInterval)
+ startTimerWithFixedDelay(TickPruneStaleChannels.toString, TickPruneStaleChannels, 1 hour)
+
+ val (pruned, channels) = publicChannels.partition { case (_, pc) => pc.isStale(nodeParams.currentBlockHeight) }
+ Metrics.Nodes.withoutTags().update(publicNodes.size)
+ Metrics.Channels.withoutTags().update(channels.size)
+ log.info("loaded from db: channels={} nodes={}", channels.size, publicNodes.size)
+ log.info("{} pruned channels at blockHeight={}", pruned.size, nodeParams.currentBlockHeight)
+ // this will be used to calculate routes
+ val graph = DirectedGraph.makeGraph(channels, publicNodes)
+ // send events for remaining channels/nodes
+ context.system.eventStream.publish(ChannelsDiscovered(channels.values.map(pc => SingleChannelDiscovered(pc.ann, pc.capacity, pc.update_1_opt, pc.update_2_opt))))
+ context.system.eventStream.publish(ChannelUpdatesReceived(channels.values.flatMap(pc => pc.update_1_opt ++ pc.update_2_opt ++ Nil)))
+ context.system.eventStream.publish(NodesDiscovered(publicNodes))
+
+ // watch the funding tx of all these channels
+ // note: some of them may already have been spent, in that case we will receive the watch event immediately
+ (channels.values ++ pruned.values).foreach { pc =>
+ val txid = pc.fundingTxId
+ val outputIndex = ShortChannelId.coordinates(pc.ann.shortChannelId).outputIndex
+ // avoid herd effect at startup because watch-spent are intensive in terms of rpc calls to bitcoind
+ context.system.scheduler.scheduleOnce(Random.nextLong(nodeParams.routerConf.watchSpentWindow.toSeconds).seconds) {
+ watcher ! WatchExternalChannelSpent(self, txid, outputIndex, pc.ann.shortChannelId)
+ }
}
- }
- // on restart we update our node announcement
- // note that if we don't currently have public channels, this will be ignored
- val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures(), fundingRates_opt = nodeParams.liquidityAdsConfig.rates_opt)
- self ! nodeAnn
-
- log.info("initialization completed, ready to process messages")
- Try(initialized.map(_.success(Done)))
- val data = Data(
- nodes.map(n => n.nodeId -> n).toMap, channels, pruned,
- Stash(Map.empty, Map.empty),
- rebroadcast = Rebroadcast(channels = Map.empty, updates = Map.empty, nodes = Map.empty),
- awaiting = Map.empty,
- privateChannels = Map.empty,
- scid2PrivateChannels = Map.empty,
- excludedChannels = Map.empty,
- graphWithBalances = GraphWithBalanceEstimates(graph, nodeParams.routerConf.balanceEstimateHalfLife),
- sync = Map.empty,
- spentChannels = Map.empty)
- startWith(NORMAL, data)
+ // on restart we update our node announcement
+ // note that if we don't currently have public channels, this will be ignored
+ val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures(), fundingRates_opt = nodeParams.liquidityAdsConfig.rates_opt)
+ self ! nodeAnn
+
+ log.info("initialization completed, ready to process messages")
+ Try(initialized_opt.map(_.success(Done)))
+ val data = Data(
+ publicNodes.map(n => n.nodeId -> n).toMap, channels, pruned,
+ Stash(Map.empty, Map.empty),
+ rebroadcast = Rebroadcast(channels = Map.empty, updates = Map.empty, nodes = Map.empty),
+ awaiting = Map.empty,
+ privateChannels = Map.empty,
+ scid2PrivateChannels = Map.empty,
+ excludedChannels = Map.empty,
+ graphWithBalances = GraphWithBalanceEstimates(graph, nodeParams.routerConf.balanceEstimateHalfLife),
+ sync = Map.empty,
+ spentChannels = Map.empty)
+ goto(NORMAL) using data
}
when(NORMAL) {
@@ -132,7 +130,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
sender() ! RoutingState(d.channels.values, d.nodes.values)
stay()
- case Event(GetRoutingStateStreaming, d) =>
+ case Event(GetRoutingStateStreaming, d: Data) =>
val listener = sender()
d.nodes
.values
@@ -160,7 +158,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
}))
stay()
- case Event(TickBroadcast, d) =>
+ case Event(TickBroadcast, d: Data) =>
// We don't rebroadcast channels that have recently been spent: it may be a splice, but it makes more sense for
// the receiver to directly receive the post-splice announcement.
val spentChannels = d.spentChannels.values.flatten.toSet[ShortChannelId]
@@ -176,10 +174,10 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
stay() using d.copy(rebroadcast = Rebroadcast(channels = Map.empty, updates = Map.empty, nodes = Map.empty))
}
- case Event(TickPruneStaleChannels, d) =>
+ case Event(TickPruneStaleChannels, d: Data) =>
stay() using StaleChannels.handlePruneStaleChannels(d, nodeParams.db.network, nodeParams.currentBlockHeight)
- case Event(ExcludeChannel(desc, duration_opt), d) =>
+ case Event(ExcludeChannel(desc, duration_opt), d: Data) =>
log.info("excluding shortChannelId={} from nodeId={} for duration={}", desc.shortChannelId, desc.a, duration_opt.getOrElse("n/a"))
val (excludedUntil, oldTimer) = d.excludedChannels.get(desc) match {
case Some(ExcludedForever) => (TimestampSecond.max, None)
@@ -201,15 +199,15 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
}
stay() using d.copy(excludedChannels = d.excludedChannels + (desc -> newState))
- case Event(LiftChannelExclusion(desc@ChannelDesc(shortChannelId, nodeId, _)), d) =>
+ case Event(LiftChannelExclusion(desc@ChannelDesc(shortChannelId, nodeId, _)), d: Data) =>
log.info("reinstating shortChannelId={} from nodeId={}", shortChannelId, nodeId)
stay() using d.copy(excludedChannels = d.excludedChannels - desc)
- case Event(GetExcludedChannels, d) =>
+ case Event(GetExcludedChannels, d: Data) =>
sender() ! d.excludedChannels
stay()
- case Event(GetNode(replyTo, nodeId), d) =>
+ case Event(GetNode(replyTo, nodeId), d: Data) =>
d.nodes.get(nodeId) match {
case Some(announcement) =>
// This only provides a lower bound on the number of channels this peer has: disabled channels will be filtered out.
@@ -221,40 +219,40 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
}
stay()
- case Event(GetNodes, d) =>
+ case Event(GetNodes, d: Data) =>
sender() ! d.nodes.values
stay()
- case Event(GetChannels, d) =>
+ case Event(GetChannels, d: Data) =>
sender() ! d.channels.values.map(_.ann)
stay()
- case Event(GetChannelsMap, d) =>
+ case Event(GetChannelsMap, d: Data) =>
sender() ! d.channels
stay()
- case Event(GetChannelUpdates, d) =>
+ case Event(GetChannelUpdates, d: Data) =>
val updates: Iterable[ChannelUpdate] = d.channels.values.flatMap(d => d.update_1_opt ++ d.update_2_opt) ++ d.privateChannels.values.flatMap(d => d.update_1_opt ++ d.update_2_opt)
sender() ! updates
stay()
- case Event(GetRouterData, d) =>
+ case Event(GetRouterData, d: Data) =>
sender() ! d
stay()
- case Event(fr: FinalizeRoute, d) =>
+ case Event(fr: FinalizeRoute, d: Data) =>
stay() using RouteCalculation.finalizeRoute(d, nodeParams.nodeId, fr)
- case Event(r: RouteRequest, d) =>
+ case Event(r: RouteRequest, d: Data) =>
stay() using RouteCalculation.handleRouteRequest(d, nodeParams.currentBlockHeight, r)
- case Event(r: BlindedRouteRequest, d) =>
+ case Event(r: BlindedRouteRequest, d: Data) =>
stay() using RouteCalculation.handleBlindedRouteRequest(d, nodeParams.currentBlockHeight, r)
- case Event(r: MessageRouteRequest, d) =>
+ case Event(r: MessageRouteRequest, d: Data) =>
stay() using RouteCalculation.handleMessageRouteRequest(d, nodeParams.currentBlockHeight, r, nodeParams.routerConf.messageRouteParams)
- case Event(GetNodeId(replyTo, shortChannelId, isNode1), d) =>
+ case Event(GetNodeId(replyTo, shortChannelId, isNode1), d: Data) =>
replyTo ! d.channels.get(shortChannelId).map(channel => if (isNode1) channel.nodeId1 else channel.nodeId2)
stay()
@@ -264,13 +262,13 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
log.warning("message {} for wrong chain {}, we're on {}", routingMessage, routingMessage.chainHash, nodeParams.chainHash)
stay()
- case Event(PeerRoutingMessage(peerConnection, remoteNodeId, c: ChannelAnnouncement), d) =>
+ case Event(PeerRoutingMessage(peerConnection, remoteNodeId, c: ChannelAnnouncement), d: Data) =>
stay() using Validation.handleChannelAnnouncement(d, watcher, RemoteGossip(peerConnection, remoteNodeId), c)
- case Event(r: ValidateResult, d) =>
+ case Event(r: ValidateResult, d: Data) =>
stay() using Validation.handleChannelValidationResponse(d, nodeParams, watcher, r)
- case Event(WatchExternalChannelSpentTriggered(shortChannelId, spendingTx_opt), d) if d.channels.contains(shortChannelId) || d.prunedChannels.contains(shortChannelId) =>
+ case Event(WatchExternalChannelSpentTriggered(shortChannelId, spendingTx_opt), d: Data) if d.channels.contains(shortChannelId) || d.prunedChannels.contains(shortChannelId) =>
val fundingTxId = d.channels.get(shortChannelId).orElse(d.prunedChannels.get(shortChannelId)).get.fundingTxId
spendingTx_opt match {
case Some(spendingTx) =>
@@ -287,7 +285,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
stay() using Validation.handleChannelSpent(d, watcher, nodeParams.db.network, None, Set(shortChannelId))
}
- case Event(WatchTxConfirmedTriggered(_, _, spendingTx), d) =>
+ case Event(WatchTxConfirmedTriggered(_, _, spendingTx), d: Data) =>
d.spentChannels.get(spendingTx.txid) match {
case Some(shortChannelIds) => stay() using Validation.handleChannelSpent(d, watcher, nodeParams.db.network, Some(spendingTx.txid), shortChannelIds)
case None => stay()
@@ -299,13 +297,13 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
case Event(PeerRoutingMessage(peerConnection, remoteNodeId, n: NodeAnnouncement), d: Data) =>
stay() using Validation.handleNodeAnnouncement(d, nodeParams.db.network, Set(RemoteGossip(peerConnection, remoteNodeId)), n)
- case Event(scia: ShortChannelIdAssigned, d) =>
+ case Event(scia: ShortChannelIdAssigned, d: Data) =>
stay() using Validation.handleShortChannelIdAssigned(d, nodeParams.nodeId, scia)
case Event(u: ChannelUpdate, d: Data) => // from payment lifecycle
stay() using Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.currentBlockHeight, Right(RemoteChannelUpdate(u, Set(LocalGossip))))
- case Event(PeerRoutingMessage(peerConnection, remoteNodeId, u: ChannelUpdate), d) => // from network (gossip or peer)
+ case Event(PeerRoutingMessage(peerConnection, remoteNodeId, u: ChannelUpdate), d: Data) => // from network (gossip or peer)
stay() using Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.currentBlockHeight, Right(RemoteChannelUpdate(u, Set(RemoteGossip(peerConnection, remoteNodeId)))))
case Event(lcu: LocalChannelUpdate, d: Data) => // from local channel
@@ -317,34 +315,34 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
case Event(e: AvailableBalanceChanged, d: Data) =>
stay() using Validation.handleAvailableBalanceChanged(d, e)
- case Event(s: SendChannelQuery, d) =>
+ case Event(s: SendChannelQuery, d: Data) =>
stay() using Sync.handleSendChannelQuery(d, s)
- case Event(PeerRoutingMessage(peerConnection, remoteNodeId, q: QueryChannelRange), d) =>
+ case Event(PeerRoutingMessage(peerConnection, remoteNodeId, q: QueryChannelRange), d: Data) =>
val spentChannels = d.spentChannels.values.flatten.toSet
val channels = d.channels.filterNot { case (scid, _) => spentChannels.contains(scid) }
Sync.handleQueryChannelRange(channels, nodeParams.routerConf, RemoteGossip(peerConnection, remoteNodeId), q)
stay()
- case Event(PeerRoutingMessage(peerConnection, remoteNodeId, r: ReplyChannelRange), d) =>
+ case Event(PeerRoutingMessage(peerConnection, remoteNodeId, r: ReplyChannelRange), d: Data) =>
stay() using Sync.handleReplyChannelRange(d, nodeParams.routerConf, RemoteGossip(peerConnection, remoteNodeId), r)
- case Event(PeerRoutingMessage(peerConnection, remoteNodeId, q: QueryShortChannelIds), d) =>
+ case Event(PeerRoutingMessage(peerConnection, remoteNodeId, q: QueryShortChannelIds), d: Data) =>
val spentChannels = d.spentChannels.values.flatten.toSet
val channels = d.channels.filterNot { case (scid, _) => spentChannels.contains(scid) }
Sync.handleQueryShortChannelIds(d.nodes, channels, RemoteGossip(peerConnection, remoteNodeId), q)
stay()
- case Event(PeerRoutingMessage(peerConnection, remoteNodeId, r: ReplyShortChannelIdsEnd), d) =>
+ case Event(PeerRoutingMessage(peerConnection, remoteNodeId, r: ReplyShortChannelIdsEnd), d: Data) =>
stay() using Sync.handleReplyShortChannelIdsEnd(d, RemoteGossip(peerConnection, remoteNodeId), r)
- case Event(RouteCouldRelay(route), d) =>
+ case Event(RouteCouldRelay(route), d: Data) =>
stay() using d.copy(graphWithBalances = d.graphWithBalances.routeCouldRelay(route))
- case Event(RouteDidRelay(route), d) =>
+ case Event(RouteDidRelay(route), d: Data) =>
stay() using d.copy(graphWithBalances = d.graphWithBalances.routeDidRelay(route))
- case Event(ChannelCouldNotRelay(amount, hop), d) =>
+ case Event(ChannelCouldNotRelay(amount, hop), d: Data) =>
stay() using d.copy(graphWithBalances = d.graphWithBalances.channelCouldNotSend(hop, amount))
}
@@ -367,7 +365,9 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
object Router {
- def props(nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], initialized: Option[Promise[Done]] = None) = Props(new Router(nodeParams, watcher, initialized))
+ def props(nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command]) = Props(new Router(nodeParams, watcher))
+
+ case class Init(publicChannels: SortedMap[RealShortChannelId, Router.PublicChannel], publicNodes: Seq[NodeAnnouncement], initialized: Option[Promise[Done]])
case class SearchBoundaries(maxFeeFlat: MilliSatoshi,
maxFeeProportional: Double,
@@ -814,6 +814,20 @@ object Router {
def started: Boolean = totalQueries > 0
}
+ sealed trait RouterData {
+ // @formatter:off
+ def nodes: Map[PublicKey, NodeAnnouncement]
+ def channels: SortedMap[RealShortChannelId, PublicChannel]
+ def privateChannels: Map[ByteVector32, PrivateChannel]
+ // @formatter:on
+ }
+
+ case object Nothing extends RouterData {
+ override val nodes: Map[PublicKey, NodeAnnouncement] = Map.empty
+ override val channels: SortedMap[RealShortChannelId, PublicChannel] = SortedMap.empty
+ override val privateChannels: Map[ByteVector32, PrivateChannel] = Map.empty
+ }
+
case class Data(nodes: Map[PublicKey, NodeAnnouncement],
channels: SortedMap[RealShortChannelId, PublicChannel],
prunedChannels: SortedMap[RealShortChannelId, PublicChannel],
@@ -826,7 +840,7 @@ object Router {
graphWithBalances: GraphWithBalanceEstimates,
sync: Map[PublicKey, Syncing], // keep tracks of channel range queries sent to each peer. If there is an entry in the map, it means that there is an ongoing query for which we have not yet received an 'end' message
spentChannels: Map[TxId, Set[RealShortChannelId]], // transactions that spend funding txs that are not yet deeply buried
- ) {
+ ) extends RouterData {
def resolve(scid: ShortChannelId): Option[KnownChannel] = {
// let's assume this is a real scid
channels.get(RealShortChannelId(scid.toLong)) match {
@@ -847,6 +861,7 @@ object Router {
// @formatter:off
sealed trait State
+ case object WAIT_FOR_INIT extends State
case object NORMAL extends State
case object TickBroadcast
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala
index b90df36..5a93b17 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala
@@ -28,7 +28,6 @@ import fr.acinq.eclair.payment.offer.{DefaultOfferHandler, OfferManager}
import fr.acinq.eclair.payment.receive.{MultiPartHandler, PaymentHandler}
import fr.acinq.eclair.payment.relay.{ChannelRelayer, PostRestartHtlcCleaner, Relayer}
import fr.acinq.eclair.payment.send.PaymentInitiator
-import fr.acinq.eclair.reputation.ReputationRecorder
import fr.acinq.eclair.router.Router
import fr.acinq.eclair.wire.protocol.IPAddress
import fr.acinq.eclair.{BlockHeight, MilliSatoshi, MilliSatoshiLong, NodeParams, RealShortChannelId, SubscriptionsComplete, TestBitcoinCoreClient, TestDatabases}
@@ -38,6 +37,7 @@ import org.scalatest.{Assertions, EitherValues}
import java.net.InetAddress
import java.util.UUID
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
+import scala.collection.immutable.SortedMap
import scala.concurrent.duration.DurationInt
import scala.util.Random
@@ -60,8 +60,8 @@ case class MinimalNodeFixture private(nodeParams: NodeParams,
watcher: TestProbe,
wallet: SingleKeyOnChainWallet,
bitcoinClient: TestBitcoinCoreClient) {
- val nodeId = nodeParams.nodeId
- val routeParams = nodeParams.routerConf.pathFindingExperimentConf.experiments.values.head.getDefaultRouteParams
+ val nodeId: PublicKey = nodeParams.nodeId
+ val routeParams: Router.RouteParams = nodeParams.routerConf.pathFindingExperimentConf.experiments.values.head.getDefaultRouteParams
}
object MinimalNodeFixture extends Assertions with Eventually with IntegrationPatience with EitherValues {
@@ -95,6 +95,7 @@ object MinimalNodeFixture extends Assertions with Eventually with IntegrationPat
val watcherTyped = watcher.ref.toTyped[ZmqWatcher.Command]
val register = system.actorOf(Register.props(), "register")
val router = system.actorOf(Router.props(nodeParams, watcherTyped), "router")
+ router ! Router.Init(SortedMap.empty, Nil, None)
val offerManager = system.spawn(OfferManager(nodeParams, 1 minute), "offer-manager")
val defaultOfferHandler = system.spawn(DefaultOfferHandler(nodeParams, router), "default-offer-handler")
val paymentHandler = system.actorOf(PaymentHandler.props(nodeParams, register, offerManager), "payment-handler")
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala
index a9aa317..598bc1e 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala
@@ -187,7 +187,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
transport.expectMsgType[TransportHandler.Listener]
transport.expectMsgType[protocol.Init]
// remote activated MPP but forgot payment secret
- transport.send(peerConnection, Init(Features(BasicMultiPartPayment -> Optional, VariableLengthOnion -> Optional)))
+ transport.send(peerConnection, protocol.Init(Features(BasicMultiPartPayment -> Optional, VariableLengthOnion -> Optional)))
transport.expectMsgType[TransportHandler.ReadAck]
probe.expectTerminated(transport.ref)
origin.expectMsg(PeerConnection.ConnectionResult.InitializationFailed("basic_mpp is set but is missing a dependency (payment_secret)"))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala
index 7ee6c32..8dd645c 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala
@@ -28,7 +28,7 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateReque
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.crypto.TransportHandler
-import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalChannelKeyManager, LocalNodeKeyManager}
+import fr.acinq.eclair.crypto.keymanager.{LocalChannelKeyManager, LocalNodeKeyManager}
import fr.acinq.eclair.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.payment.send.BlindedPathsResolver.{FullBlindedRoute, ResolvedPath}
import fr.acinq.eclair.payment.send.BlindedRecipient
@@ -43,6 +43,7 @@ import org.scalatest.Outcome
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import scodec.bits.ByteVector
+import scala.collection.immutable.SortedMap
import scala.concurrent.duration._
/**
@@ -141,6 +142,7 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi
val nodeParams = Alice.nodeParams
.modify(_.nodeKeyManager).setTo(testNodeKeyManager)
val router = system.actorOf(Router.props(nodeParams, watcher.ref))
+ router ! Router.Init(SortedMap.empty, Nil, None)
// we announce channels
peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ab))
peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_bc))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala
index d4d12c2..41a70db 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala
@@ -1,5 +1,6 @@
package fr.acinq.eclair.router
+import akka.Done
import akka.actor.ActorSystem
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.scalacompat.{SatoshiLong, Script, Transaction, TxOut}
@@ -15,6 +16,8 @@ import org.scalatest.OptionValues.convertOptionToValuable
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
+import scala.collection.immutable.SortedMap
+import scala.concurrent.Promise
import scala.concurrent.duration.DurationInt
/**
@@ -22,23 +25,26 @@ import scala.concurrent.duration.DurationInt
*/
class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase {
- case class FixtureParam(router: TestFSMRef[Router.State, Router.Data, Router], channels: SetupFixture, testTags: Set[String]) {
- //@formatter:off
+ case class FixtureParam(router: TestFSMRef[Router.State, Router.RouterData, Router], channels: SetupFixture, testTags: Set[String]) {
+ // @formatter:off
/** there is only one channel here */
def privateChannel: PrivateChannel = router.stateData.privateChannels.values.head
def publicChannel: PublicChannel = router.stateData.channels.values.head
- //@formatter:on
+ // @formatter:on
}
implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging
override def withFixture(test: OneArgTest): Outcome = {
val channels = init(tags = test.tags)
- val router: TestFSMRef[Router.State, Router.Data, Router] = {
+ val router: TestFSMRef[Router.State, Router.RouterData, Router] = {
// we use alice's actor system so we share the same event stream
implicit val system: ActorSystem = channels.alice.underlying.system
- TestFSMRef(new Router(channels.alice.underlyingActor.nodeParams, channels.alice.underlyingActor.blockchain, initialized = None))
+ TestFSMRef(new Router(channels.alice.underlyingActor.nodeParams, channels.alice.underlyingActor.blockchain))
}
+ val routerInitialized = Promise[Done]()
+ router ! Router.Init(SortedMap.empty, Nil, Some(routerInitialized))
+ awaitAssert(assert(routerInitialized.isCompleted))
withFixture(test.toNoArgTest(FixtureParam(router, channels, test.tags)))
}
@@ -84,11 +90,11 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu
awaitCond(privateChannel.update_1_opt.contains(aliceInitialChannelUpdate) && privateChannel.update_2_opt.contains(bobChannelUpdate1))
// There is nothing for the router to rebroadcast, because the channel is not announced yet.
- assert(router.stateData.rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty))
+ assert(router.stateData.asInstanceOf[Router.Data].rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty))
// The router graph contains a single channel between Alice and Bob.
- assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId))
- assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceInitialChannelUpdate, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel)))
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId))
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceInitialChannelUpdate, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel)))
// The channel now confirms, if it hadn't confirmed already.
if (testTags.contains(ChannelStateTestsTags.ZeroConf)) {
@@ -107,9 +113,9 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu
// The router learns about the announcement and the channel graduates from private to public.
awaitAssert {
- assert(router.stateData.privateChannels.isEmpty)
- assert(router.stateData.scid2PrivateChannels.isEmpty)
- assert(router.stateData.channels.size == 1)
+ assert(router.stateData.asInstanceOf[Router.Data].privateChannels.isEmpty)
+ assert(router.stateData.asInstanceOf[Router.Data].scid2PrivateChannels.isEmpty)
+ assert(router.stateData.asInstanceOf[Router.Data].channels.size == 1)
}
// Alice and Bob won't send their channel_update directly to each other because the channel has been announced
@@ -133,7 +139,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu
assert(publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate1))
// The router will rebroadcast the channel announcement, the local update which uses the real scid, and the first node announcement.
- assert(router.stateData.rebroadcast == Rebroadcast(
+ assert(router.stateData.asInstanceOf[Router.Data].rebroadcast == Rebroadcast(
channels = Map(aliceAnn -> Set[GossipOrigin](LocalGossip)),
updates = Map(aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip)),
nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip)))
@@ -146,7 +152,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu
awaitCond(publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate2))
// The router is now ready to rebroadcast both channel updates.
- assert(router.stateData.rebroadcast == Rebroadcast(
+ assert(router.stateData.asInstanceOf[Router.Data].rebroadcast == Rebroadcast(
channels = Map(aliceAnn -> Set[GossipOrigin](LocalGossip)),
updates = Map(
aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip),
@@ -156,9 +162,9 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu
)
// The router graph still contains a single channel, with public updates.
- assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId))
- assert(router.stateData.graphWithBalances.graph.edgeSet().size == 2)
- assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate2, publicChannel), GraphEdge(bobChannelUpdate2, publicChannel)))
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId))
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.edgeSet().size == 2)
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate2, publicChannel), GraphEdge(bobChannelUpdate2, publicChannel)))
} else {
// This is a private channel: alice and bob won't send a new channel_update to each other, even though the channel
// is confirmed, because they will keep using the scid aliases.
@@ -168,8 +174,8 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu
assert(privateChannel.update_2_opt.get.dontForward)
// The router graph still contains a single private channel.
- assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId))
- assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceInitialChannelUpdate, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel)))
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId))
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceInitialChannelUpdate, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel)))
}
// The channel closes.
@@ -189,11 +195,11 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu
}
// The router can now clean up the closed channel.
awaitAssert {
- assert(router.stateData.nodes == Map.empty)
- assert(router.stateData.channels == Map.empty)
- assert(router.stateData.privateChannels == Map.empty)
- assert(router.stateData.scid2PrivateChannels == Map.empty)
- assert(router.stateData.graphWithBalances.graph.edgeSet().isEmpty)
+ assert(router.stateData.asInstanceOf[Router.Data].nodes == Map.empty)
+ assert(router.stateData.asInstanceOf[Router.Data].channels == Map.empty)
+ assert(router.stateData.asInstanceOf[Router.Data].privateChannels == Map.empty)
+ assert(router.stateData.asInstanceOf[Router.Data].scid2PrivateChannels == Map.empty)
+ assert(router.stateData.asInstanceOf[Router.Data].graphWithBalances.graph.edgeSet().isEmpty)
// TODO: we're not currently pruning nodes without channels from the graph, but we should!
// assert(router.stateData.graphWithBalances.graph.vertexSet().isEmpty)
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala
index 89e8ba6..0292a26 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala
@@ -23,7 +23,6 @@ import com.softwaremill.quicklens.ModifyPimp
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Satoshi, Script, Transaction, TxId, TxIn, TxOut}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
-import fr.acinq.eclair.{RealShortChannelId, _}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult}
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.io.Peer.PeerRoutingMessage
@@ -33,10 +32,11 @@ import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router.Sync._
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire.protocol._
+import fr.acinq.eclair.{RealShortChannelId, _}
import org.scalatest.ParallelTestExecution
import org.scalatest.funsuite.AnyFunSuiteLike
-import scala.collection.immutable.{SortedSet, TreeMap}
+import scala.collection.immutable.{SortedMap, SortedSet, TreeMap}
import scala.collection.mutable
import scala.concurrent.duration._
@@ -73,7 +73,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
def counts: BasicSyncResult = BasicSyncResult(ranges.size, queries.size, channels.size, updates.size, nodes.size)
}
- def sync(src: TestFSMRef[State, Data, Router], tgt: TestFSMRef[State, Data, Router], extendedQueryFlags_opt: Option[QueryChannelRangeTlv]): SyncResult = {
+ def sync(src: TestFSMRef[State, RouterData, Router], tgt: TestFSMRef[State, RouterData, Router], extendedQueryFlags_opt: Option[QueryChannelRangeTlv]): SyncResult = {
val sender = TestProbe()
val pipe = TestProbe()
pipe.ignoreMsg {
@@ -103,7 +103,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
var channels = Vector.empty[ChannelAnnouncement]
var updates = Vector.empty[ChannelUpdate]
var nodes = Vector.empty[NodeAnnouncement]
- while (src.stateData.sync.nonEmpty) {
+ while (src.stateData.asInstanceOf[Data].sync.nonEmpty) {
// for each chunk, src sends a query_short_channel_id
val query = pipe.expectMsgType[QueryShortChannelIds]
pipe.send(tgt, PeerRoutingMessage(pipe.ref, srcId, query))
@@ -135,7 +135,9 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
test("sync with standard channel queries") {
val watcher = system.actorOf(Props(new YesWatcher()))
val alice = TestFSMRef(new Router(Alice.nodeParams, watcher))
+ alice ! Router.Init(SortedMap.empty, Nil, None)
val bob = TestFSMRef(new Router(Bob.nodeParams, watcher))
+ bob ! Router.Init(SortedMap.empty, Nil, None)
val charlieId = randomKey().publicKey
val sender = TestProbe()
val extendedQueryFlags_opt = None
@@ -184,7 +186,9 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
def syncWithExtendedQueries(requestNodeAnnouncements: Boolean): Unit = {
val watcher = system.actorOf(Props(new YesWatcher()))
val alice = TestFSMRef(new Router(Alice.nodeParams.modify(_.routerConf.syncConf.requestNodeAnnouncements).setTo(requestNodeAnnouncements), watcher))
+ alice ! Router.Init(SortedMap.empty, Nil, None)
val bob = TestFSMRef(new Router(Bob.nodeParams, watcher))
+ bob ! Router.Init(SortedMap.empty, Nil, None)
val charlieId = randomKey().publicKey
val sender = TestProbe()
val extendedQueryFlags_opt = Some(QueryChannelRangeTlv.QueryFlags(QueryChannelRangeTlv.QueryFlags.WANT_ALL))
@@ -253,23 +257,24 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
test("reset sync state on reconnection") {
val params = TestConstants.Alice.nodeParams
val router = TestFSMRef(new Router(params, TestProbe().ref))
+ router ! Router.Init(SortedMap.empty, Nil, None)
val peerConnection = TestProbe()
peerConnection.ignoreMsg { case _: TransportHandler.ReadAck => true }
val sender = TestProbe()
sender.ignoreMsg { case _: TransportHandler.ReadAck => true }
val remoteNodeId = TestConstants.Bob.nodeParams.nodeId
- assert(!router.stateData.sync.contains(remoteNodeId))
+ assert(!router.stateData.asInstanceOf[Data].sync.contains(remoteNodeId))
// ask router to send a channel range query
sender.send(router, SendChannelQuery(params.chainHash, remoteNodeId, sender.ref, replacePrevious = true, None))
val QueryChannelRange(chainHash, firstBlockNum, numberOfBlocks, _) = sender.expectMsgType[QueryChannelRange]
sender.expectMsgType[GossipTimestampFilter]
- assert(router.stateData.sync.get(remoteNodeId).contains(Syncing(Nil, 0)))
+ assert(router.stateData.asInstanceOf[Data].sync.get(remoteNodeId).contains(Syncing(Nil, 0)))
// ask router to send another channel range query
sender.send(router, SendChannelQuery(params.chainHash, remoteNodeId, sender.ref, replacePrevious = false, None))
sender.expectNoMessage(100 millis) // it's a duplicate and should be ignored
- assert(router.stateData.sync.get(remoteNodeId).contains(Syncing(Nil, 0)))
+ assert(router.stateData.asInstanceOf[Data].sync.get(remoteNodeId).contains(Syncing(Nil, 0)))
val block1 = ReplyChannelRange(chainHash, firstBlockNum, numberOfBlocks, 1, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, fakeRoutingInfo.take(params.routerConf.syncConf.channelQueryChunkSize).keys.toList), None, None)
@@ -279,7 +284,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
// router should ask for our first block of ids
assert(peerConnection.expectMsgType[QueryShortChannelIds] == QueryShortChannelIds(chainHash, block1.shortChannelIds, TlvStream.empty))
// router should think that it is missing 100 channels, in one request
- val Some(sync) = router.stateData.sync.get(remoteNodeId)
+ val Some(sync) = router.stateData.asInstanceOf[Data].sync.get(remoteNodeId)
assert(sync.remainingQueries.isEmpty) // the request was sent already
assert(sync.totalQueries == 1)
@@ -287,18 +292,19 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
sender.send(router, SendChannelQuery(params.chainHash, remoteNodeId, sender.ref, replacePrevious = true, None))
sender.expectMsgType[QueryChannelRange]
sender.expectMsgType[GossipTimestampFilter]
- assert(router.stateData.sync.get(remoteNodeId).contains(Syncing(Nil, 0)))
+ assert(router.stateData.asInstanceOf[Data].sync.get(remoteNodeId).contains(Syncing(Nil, 0)))
}
test("reject unsolicited sync") {
val params = TestConstants.Alice.nodeParams
val router = TestFSMRef(new Router(params, TestProbe().ref))
+ router ! Router.Init(SortedMap.empty, Nil, None)
val peerConnection = TestProbe()
peerConnection.ignoreMsg { case _: TransportHandler.ReadAck => true }
val sender = TestProbe()
sender.ignoreMsg { case _: TransportHandler.ReadAck => true }
val remoteNodeId = TestConstants.Bob.nodeParams.nodeId
- assert(!router.stateData.sync.contains(remoteNodeId))
+ assert(!router.stateData.asInstanceOf[Data].sync.contains(remoteNodeId))
// we didn't send a corresponding query_channel_range, but peer sends us a reply_channel_range
val unsolicitedBlocks = ReplyChannelRange(params.chainHash, BlockHeight(10), 5, 0, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, fakeRoutingInfo.take(5).keys.toList), None, None)
@@ -306,7 +312,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle
// it will be simply ignored
peerConnection.expectNoMessage(100 millis)
- assert(!router.stateData.sync.contains(remoteNodeId))
+ assert(!router.stateData.asInstanceOf[Data].sync.contains(remoteNodeId))
}
test("sync progress") {
diff --git a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala
index 0501dfc..45eaf09 100644
--- a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala
+++ b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala
@@ -33,6 +33,8 @@ import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire.protocol.Color
import org.scalatest.funsuite.AnyFunSuiteLike
+import scala.collection.immutable.SortedMap
+
class FrontRouterSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteLike {
import FrontRouterSpec._
@@ -42,6 +44,7 @@ class FrontRouterSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteLike
val watcher = TestProbe()
val router = system.actorOf(Router.props(nodeParams, watcher.ref))
+ router ! Router.Init(SortedMap.empty, Nil, None)
val system1 = ActorSystem("front-system-1")
val system2 = ActorSystem("front-system-2")
@@ -143,6 +146,7 @@ class FrontRouterSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteLike
val watcher = TestProbe()
val router = system.actorOf(Router.props(nodeParams, watcher.ref))
+ router ! Router.Init(SortedMap.empty, Nil, None)
val system1 = ActorSystem("front-system-1")
val system2 = ActorSystem("front-system-2")
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.