@@ -92,7 +92,9 @@ class Peer(val nodeParams: NodeParams,
9292 } else {
9393 None
9494 }
95- goto(DISCONNECTED ) using DisconnectedData (channels, activeChannels = Set .empty, PeerStorage (peerStorageData, written = true )) // when we restart, we will attempt to reconnect right away, but then we'll wait
95+ // When we restart, we will attempt to reconnect right away, but then we'll wait.
96+ // We don't fetch our peer's features from the DB: if the connection succeeds, we will get them from their init message, which saves a DB call.
97+ goto(DISCONNECTED ) using DisconnectedData (channels, activeChannels = Set .empty, PeerStorage (peerStorageData, written = true ), remoteFeatures_opt = None )
9698 }
9799
98100 when(DISCONNECTED ) {
@@ -154,7 +156,14 @@ class Peer(val nodeParams: NodeParams,
154156 if (! d.peerStorage.written && ! isTimerActive(WritePeerStorageTimerKey )) {
155157 startSingleTimer(WritePeerStorageTimerKey , WritePeerStorage , nodeParams.peerStorageConfig.writeDelay)
156158 }
157- stay() using d.copy(activeChannels = d.activeChannels + e.channelId)
159+ val remoteFeatures_opt = d.remoteFeatures_opt match {
160+ case Some (remoteFeatures) if ! remoteFeatures.written =>
161+ // We have a channel, so we can write to the DB without any DoS risk.
162+ nodeParams.db.peers.addOrUpdatePeer(remoteNodeId, NodeInfo (remoteFeatures.features, None ))
163+ Some (remoteFeatures.copy(written = true ))
164+ case _ => d.remoteFeatures_opt
165+ }
166+ stay() using d.copy(activeChannels = d.activeChannels + e.channelId, remoteFeatures_opt = remoteFeatures_opt)
158167
159168 case Event (e : LocalChannelDown , d : DisconnectedData ) =>
160169 stay() using d.copy(activeChannels = d.activeChannels - e.channelId)
@@ -452,7 +461,11 @@ class Peer(val nodeParams: NodeParams,
452461 if (! d.peerStorage.written && ! isTimerActive(WritePeerStorageTimerKey )) {
453462 startSingleTimer(WritePeerStorageTimerKey , WritePeerStorage , nodeParams.peerStorageConfig.writeDelay)
454463 }
455- stay() using d.copy(activeChannels = d.activeChannels + e.channelId)
464+ if (! d.remoteFeaturesWritten) {
465+ // We have a channel, so we can write to the DB without any DoS risk.
466+ nodeParams.db.peers.addOrUpdatePeer(remoteNodeId, NodeInfo (d.remoteFeatures, None ))
467+ }
468+ stay() using d.copy(activeChannels = d.activeChannels + e.channelId, remoteFeaturesWritten = true )
456469
457470 case Event (e : LocalChannelDown , d : ConnectedData ) =>
458471 stay() using d.copy(activeChannels = d.activeChannels - e.channelId)
@@ -497,7 +510,8 @@ class Peer(val nodeParams: NodeParams,
497510 stopPeer(d.peerStorage)
498511 } else {
499512 d.channels.values.toSet[ActorRef ].foreach(_ ! INPUT_DISCONNECTED ) // we deduplicate with toSet because there might be two entries per channel (tmp id and final id)
500- goto(DISCONNECTED ) using DisconnectedData (d.channels.collect { case (k : FinalChannelId , v) => (k, v) }, d.activeChannels, d.peerStorage)
513+ val lastRemoteFeatures = LastRemoteFeatures (d.remoteFeatures, d.remoteFeaturesWritten)
514+ goto(DISCONNECTED ) using DisconnectedData (d.channels.collect { case (k : FinalChannelId , v) => (k, v) }, d.activeChannels, d.peerStorage, Some (lastRemoteFeatures))
501515 }
502516
503517 case Event (ChannelTerminated (actor), d : ConnectedData ) =>
@@ -600,12 +614,22 @@ class Peer(val nodeParams: NodeParams,
600614
601615 case Event (r : GetPeerInfo , d) =>
602616 val replyTo = r.replyTo.getOrElse(sender().toTyped)
603- val peerInfo = d match {
604- case c : ConnectedData => PeerInfo (self, remoteNodeId, stateName, Some (c.remoteFeatures), Some (c.address), c.channels.values.toSet)
605- case _ => PeerInfo (self, remoteNodeId, stateName, None , None , d.channels.values.toSet)
617+ d match {
618+ case c : ConnectedData =>
619+ replyTo ! PeerInfo (self, remoteNodeId, stateName, Some (c.remoteFeatures), Some (c.address), c.channels.values.toSet)
620+ stay()
621+ case d : DisconnectedData =>
622+ // If we haven't reconnected since our last restart, we fetch the latest remote features from our DB.
623+ val remoteFeatures_opt = d.remoteFeatures_opt match {
624+ case Some (remoteFeatures) => Some (remoteFeatures)
625+ case None => nodeParams.db.peers.getPeer(remoteNodeId).map(nodeInfo => LastRemoteFeatures (nodeInfo.features, written = true ))
626+ }
627+ replyTo ! PeerInfo (self, remoteNodeId, stateName, remoteFeatures_opt.map(_.features), None , d.channels.values.toSet)
628+ stay() using d.copy(remoteFeatures_opt = remoteFeatures_opt)
629+ case _ =>
630+ replyTo ! PeerInfo (self, remoteNodeId, stateName, None , None , d.channels.values.toSet)
631+ stay()
606632 }
607- replyTo ! peerInfo
608- stay()
609633
610634 case Event (r : GetPeerChannels , d) =>
611635 if (d.channels.isEmpty) {
@@ -813,7 +837,13 @@ class Peer(val nodeParams: NodeParams,
813837 // We store the node address and features upon successful outgoing connection, so we can reconnect later.
814838 // The previous address is overwritten: we don't need it since the current one works.
815839 nodeParams.db.peers.addOrUpdatePeer(remoteNodeId, NodeInfo (connectionReady.remoteInit.features, Some (connectionReady.address)))
840+ } else if (channels.nonEmpty) {
841+ // If this is an incoming connection, we only store the peer details in our DB if we have channels with them.
842+ // Otherwise nodes could DoS by simply connecting to us to force us to store data in our DB.
843+ // We don't update the remote address, we don't know if we would successfully connect using the current one.
844+ nodeParams.db.peers.addOrUpdatePeer(remoteNodeId, NodeInfo (connectionReady.remoteInit.features, None ))
816845 }
846+ val remoteFeaturesWritten = connectionReady.outgoing || channels.nonEmpty
817847
818848 // If we have some data stored from our peer, we send it to them before doing anything else.
819849 peerStorage.data.foreach(connectionReady.peerConnection ! PeerStorageRetrieval (_))
@@ -835,7 +865,7 @@ class Peer(val nodeParams: NodeParams,
835865 connectionReady.peerConnection ! CurrentFeeCredit (nodeParams.chainHash, feeCredit.getOrElse(0 msat))
836866 }
837867
838- goto(CONNECTED ) using ConnectedData (connectionReady.address, connectionReady.peerConnection, connectionReady.localInit, connectionReady.remoteInit, channels, activeChannels, feerates, None , peerStorage)
868+ goto(CONNECTED ) using ConnectedData (connectionReady.address, connectionReady.peerConnection, connectionReady.localInit, connectionReady.remoteInit, channels, activeChannels, feerates, None , peerStorage, remoteFeaturesWritten )
839869 }
840870
841871 /**
@@ -976,6 +1006,8 @@ object Peer {
9761006
9771007 case class PeerStorage (data : Option [ByteVector ], written : Boolean )
9781008
1009+ case class LastRemoteFeatures (features : Features [InitFeature ], written : Boolean )
1010+
9791011 sealed trait Data {
9801012 def channels : Map [_ <: ChannelId , ActorRef ] // will be overridden by Map[FinalChannelId, ActorRef] or Map[ChannelId, ActorRef]
9811013 def activeChannels : Set [ByteVector32 ] // channels that are available to process payments
@@ -986,8 +1018,8 @@ object Peer {
9861018 override def activeChannels : Set [ByteVector32 ] = Set .empty
9871019 override def peerStorage : PeerStorage = PeerStorage (None , written = true )
9881020 }
989- case class DisconnectedData (channels : Map [FinalChannelId , ActorRef ], activeChannels : Set [ByteVector32 ], peerStorage : PeerStorage ) extends Data
990- case class ConnectedData (address : NodeAddress , peerConnection : ActorRef , localInit : protocol.Init , remoteInit : protocol.Init , channels : Map [ChannelId , ActorRef ], activeChannels : Set [ByteVector32 ], currentFeerates : RecommendedFeerates , previousFeerates_opt : Option [RecommendedFeerates ], peerStorage : PeerStorage ) extends Data {
1021+ case class DisconnectedData (channels : Map [FinalChannelId , ActorRef ], activeChannels : Set [ByteVector32 ], peerStorage : PeerStorage , remoteFeatures_opt : Option [ LastRemoteFeatures ] ) extends Data
1022+ case class ConnectedData (address : NodeAddress , peerConnection : ActorRef , localInit : protocol.Init , remoteInit : protocol.Init , channels : Map [ChannelId , ActorRef ], activeChannels : Set [ByteVector32 ], currentFeerates : RecommendedFeerates , previousFeerates_opt : Option [RecommendedFeerates ], peerStorage : PeerStorage , remoteFeaturesWritten : Boolean ) extends Data {
9911023 val connectionInfo : ConnectionInfo = ConnectionInfo (address, peerConnection, localInit, remoteInit)
9921024 def localFeatures : Features [InitFeature ] = localInit.features
9931025 def remoteFeatures : Features [InitFeature ] = remoteInit.features
0 commit comments