Skip to content

Commit 49ba7d3

Browse files
committed
[ETCM-540] Improve peer discovery algorithm
1 parent ff9f305 commit 49ba7d3

File tree

3 files changed

+71
-11
lines changed

3 files changed

+71
-11
lines changed

src/main/resources/application.conf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,12 @@ mantis {
165165
# Newly discovered nodes connect attempt interval
166166
update-nodes-interval = 30.seconds
167167

168-
# Peer which disconnect during tcp connection becouse of too many peers will not be retried for this short duration
168+
# Peer which disconnect during tcp connection because of too many peers will not be retried for this short duration
169169
short-blacklist-duration = 6.minutes
170170

171-
# Peer which disconnect during tcp connection becouse of other reasons will not be retried for this long duration
171+
# Peer which disconnect during tcp connection because of other reasons will not be retried for this long duration
172172
# other reasons include: timeout during connection, wrong protocol, incompatible network
173-
long-blacklist-duration = 30.minutes
173+
long-blacklist-duration = 600.minutes
174174

175175
# Resolution of moving window of peer statistics.
176176
# Will be multiplied by `stat-slot-count` to give the overall length of peer statistics availability.

src/main/scala/io/iohk/ethereum/network/PeerManagerActor.scala

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.iohk.ethereum.network
22

3-
import java.net.{InetSocketAddress, URI}
43
import akka.actor.SupervisorStrategy.Stop
54
import akka.actor._
65
import akka.util.{ByteString, Timeout}
@@ -22,7 +21,12 @@ import io.iohk.ethereum.network.rlpx.RLPxConnectionHandler.RLPxConfiguration
2221
import monix.eval.Task
2322
import monix.execution.{Scheduler => MonixScheduler}
2423
import org.bouncycastle.util.encoders.Hex
24+
25+
import java.net.{InetSocketAddress, URI}
26+
import java.util.Collections.newSetFromMap
27+
import scala.collection.mutable
2528
import scala.concurrent.duration._
29+
import scala.jdk.CollectionConverters._
2630

2731
class PeerManagerActor(
2832
peerEventBus: ActorRef,
@@ -51,6 +55,8 @@ class PeerManagerActor(
5155
import PeerManagerActor._
5256
import akka.pattern.pipe
5357

58+
val triedNodes: mutable.Set[ByteString] = lruSet[ByteString](maxBlacklistedNodes)
59+
5460
implicit class ConnectedPeersOps(connectedPeers: ConnectedPeers) {
5561

5662
/** Number of new connections the node should try to open at any given time. */
@@ -126,6 +132,7 @@ class PeerManagerActor(
126132
private def maybeConnectToRandomNode(connectedPeers: ConnectedPeers, node: Node): Unit = {
127133
if (connectedPeers.outgoingConnectionDemand > 0) {
128134
if (connectedPeers.canConnectTo(node)) {
135+
triedNodes.add(node.id)
129136
self ! ConnectToPeer(node.toUri)
130137
} else {
131138
peerDiscoveryManager ! PeerDiscoveryManager.GetRandomNodeInfo
@@ -134,9 +141,15 @@ class PeerManagerActor(
134141
}
135142

136143
private def maybeConnectToDiscoveredNodes(connectedPeers: ConnectedPeers, nodes: Set[Node]): Unit = {
137-
val nodesToConnect = nodes
144+
val discoveredNodes = nodes
138145
.filter(connectedPeers.canConnectTo)
139-
.take(connectedPeers.outgoingConnectionDemand)
146+
147+
val nodesToConnect = discoveredNodes
148+
.filterNot(n => triedNodes.contains(n.id)) match {
149+
case seq if seq.size >= connectedPeers.outgoingConnectionDemand =>
150+
seq.take(connectedPeers.outgoingConnectionDemand)
151+
case _ => discoveredNodes.take(connectedPeers.outgoingConnectionDemand)
152+
}
140153

141154
NetworkMetrics.DiscoveredPeersSize.set(nodes.size)
142155
NetworkMetrics.BlacklistedPeersSize.set(blacklistedPeers.size)
@@ -152,14 +165,17 @@ class PeerManagerActor(
152165

153166
if (nodesToConnect.nonEmpty) {
154167
log.debug("Trying to connect to {} nodes", nodesToConnect.size)
155-
nodesToConnect.foreach(n => self ! ConnectToPeer(n.toUri))
168+
nodesToConnect.foreach(n => {
169+
triedNodes.add(n.id)
170+
self ! ConnectToPeer(n.toUri)
171+
})
156172
} else {
157173
log.debug("The nodes list is empty, no new nodes to connect to")
158174
}
159175

160176
// Make sure the background lookups keep going and we don't get stuck with 0
161177
// nodes to connect to until the next discovery scan loop. Only sending 1
162-
// request so we don't rack up too many pending futures, just trigger a a
178+
// request so we don't rack up too many pending futures, just trigger a
163179
// search if needed.
164180
if (connectedPeers.outgoingConnectionDemand > nodesToConnect.size) {
165181
peerDiscoveryManager ! PeerDiscoveryManager.GetRandomNodeInfo
@@ -184,7 +200,7 @@ class PeerManagerActor(
184200
private def getBlacklistDuration(reason: Long): FiniteDuration = {
185201
import Disconnect.Reasons._
186202
reason match {
187-
case TooManyPeers => peerConfiguration.shortBlacklistDuration
203+
case TooManyPeers | AlreadyConnected | ClientQuitting => peerConfiguration.shortBlacklistDuration
188204
case _ => peerConfiguration.longBlacklistDuration
189205
}
190206
}
@@ -552,4 +568,9 @@ object PeerManagerActor {
552568
}
553569
.getOrElse(0.0)
554570
}
571+
572+
def lruSet[A](maxEntries: Int): mutable.Set[A] =
573+
newSetFromMap[A](new java.util.LinkedHashMap[A, java.lang.Boolean]() {
574+
override def removeEldestEntry(eldest: java.util.Map.Entry[A, java.lang.Boolean]): Boolean = size > maxEntries
575+
}).asScala
555576
}

src/test/scala/io/iohk/ethereum/network/PeerManagerSpec.scala

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import org.scalatest.concurrent.Eventually
2525
import org.scalatest.flatspec.AnyFlatSpecLike
2626
import org.scalatest.matchers.should.Matchers
2727
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
28-
import org.scalacheck.{Arbitrary, Gen, Shrink}, Arbitrary.arbitrary
28+
import org.scalacheck.{Arbitrary, Gen}, Arbitrary.arbitrary
2929
import scala.concurrent.duration._
3030

3131
// scalastyle:off magic.number
@@ -274,7 +274,7 @@ class PeerManagerSpec
274274

275275
behavior of "outgoingConnectionDemand"
276276

277-
it should "try to connect to at least min-outgoing-peers but no longer than max-outgoing-peers" in new ConnectedPeersFixture {
277+
it should "try to connect to at least min-outgoing-peers but no more than max-outgoing-peers" in new ConnectedPeersFixture {
278278
forAll { (connectedPeers: ConnectedPeers) =>
279279
val demand = PeerManagerActor.outgoingConnectionDemand(connectedPeers, peerConfiguration)
280280
demand shouldBe >=(0)
@@ -286,6 +286,45 @@ class PeerManagerSpec
286286
}
287287
}
288288

289+
it should "try to connect to discovered nodes if there's an outgoing demand: new nodes first, retried last" in new TestSetup {
290+
start()
291+
val discoveredNodes: Set[Node] = Set(
292+
"enode://111bd28d5b2c1378d748383fd83ff59572967c317c3063a9f475a26ad3f1517642a164338fb5268d4e32ea1cc48e663bd627dec572f1d201c7198518e5a506b1@88.99.216.30:45834?discport=45834",
293+
"enode://2b69a3926f36a7748c9021c34050be5e0b64346225e477fe7377070f6289bd363b2be73a06010fd516e6ea3ee90778dd0399bc007bb1281923a79374f842675a@51.15.116.226:30303?discport=30303"
294+
).map(new java.net.URI(_)).map(Node.fromUri)
295+
296+
peerManager ! PeerDiscoveryManager.DiscoveredNodesInfo(discoveredNodes)
297+
298+
peerDiscoveryManager.expectMsg(PeerDiscoveryManager.GetRandomNodeInfo)
299+
300+
val probe: TestProbe = createdPeers(0).probe
301+
probe.expectMsgClass(classOf[PeerActor.ConnectTo])
302+
303+
val probe2: TestProbe = createdPeers(1).probe
304+
probe2.expectMsgClass(classOf[PeerActor.ConnectTo])
305+
306+
peerManager ! PeerClosedConnection(discoveredNodes.head.addr.getHostAddress, Disconnect.Reasons.TooManyPeers)
307+
308+
peerManager.underlyingActor.blacklistedPeers.size shouldEqual 1
309+
peerManager.underlyingActor.triedNodes.size shouldEqual 2
310+
311+
time.advance(360000) // wait till the peer is out of the blacklist
312+
313+
val newRoundDiscoveredNodes = discoveredNodes + Node.fromUri(new java.net.URI(
314+
"enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.158.191.43:38556?discport=38556"))
315+
316+
peerManager ! PeerDiscoveryManager.DiscoveredNodesInfo(newRoundDiscoveredNodes)
317+
318+
probe.expectNoMessage()
319+
probe2.expectNoMessage()
320+
321+
val probe3: TestProbe = createdPeers(2).probe
322+
probe3.expectMsgClass(classOf[PeerActor.ConnectTo])
323+
324+
peerManager.underlyingActor.blacklistedPeers.size shouldEqual 0
325+
peerManager.underlyingActor.triedNodes.size shouldEqual 3
326+
}
327+
289328
behavior of "numberOfIncomingConnectionsToPrune"
290329

291330
it should "try to prune incoming connections down to the minimum allowed number" in new ConnectedPeersFixture {

0 commit comments

Comments
 (0)