@@ -95,7 +95,8 @@ object DiscoveryService {
95
95
// Start handling requests, we need them during enrolling so the peers can ping and bond with us.
96
96
cancelToken <- network.startHandling(service)
97
97
// Contact the bootstrap nodes.
98
- enroll = service.enroll()
98
+ // Setting the enrolled status here because we could potentially repeat enrollment until it succeeds.
99
+ enroll = service.enroll().guarantee(stateRef.update(_.setEnrolled))
99
100
// Periodically discover new nodes.
100
101
discover = service.lookupRandom.delayExecution(config.discoveryPeriod).loopForever
101
102
// Enrollment can be run in the background if it takes very long.
@@ -152,7 +153,9 @@ object DiscoveryService {
152
153
// Deferred results so we can ensure there's only one concurrent Ping to a given peer.
153
154
bondingResultsMap : Map [Peer [A ], BondingResults ],
154
155
// Deferred ENR fetches so we only do one at a time to a given peer.
155
- fetchEnrMap : Map [Peer [A ], FetchEnrResult ]
156
+ fetchEnrMap : Map [Peer [A ], FetchEnrResult ],
157
+ // Indicate whether enrollment hash finished.
158
+ hasEnrolled : Boolean
156
159
) {
157
160
def isSelf (peer : Peer [A ]): Boolean =
158
161
peer.id == node.id
@@ -191,6 +194,9 @@ object DiscoveryService {
191
194
def clearBondingResults (peer : Peer [A ]): State [A ] =
192
195
copy(bondingResultsMap = bondingResultsMap - peer)
193
196
197
+ def clearLastPongTimestamp (peer : Peer [A ]): State [A ] =
198
+ copy(lastPongTimestampMap = lastPongTimestampMap - peer)
199
+
194
200
def withEnrFetch (peer : Peer [A ], result : FetchEnrResult ): State [A ] =
195
201
copy(fetchEnrMap = fetchEnrMap.updated(peer, result))
196
202
@@ -230,6 +236,9 @@ object DiscoveryService {
230
236
kBuckets = kBuckets.remove(Node .kademliaId(peerId)),
231
237
kademliaIdToNodeId = kademliaIdToNodeId - Node .kademliaId(peerId)
232
238
)
239
+
240
+ def setEnrolled : State [A ] =
241
+ copy(hasEnrolled = true )
233
242
}
234
243
protected [v4] object State {
235
244
def apply [A ](
@@ -245,7 +254,8 @@ object DiscoveryService {
245
254
enrMap = Map (node.id -> enr),
246
255
lastPongTimestampMap = Map .empty[Peer [A ], Timestamp ],
247
256
bondingResultsMap = Map .empty[Peer [A ], BondingResults ],
248
- fetchEnrMap = Map .empty[Peer [A ], FetchEnrResult ]
257
+ fetchEnrMap = Map .empty[Peer [A ], FetchEnrResult ],
258
+ hasEnrolled = false
249
259
)
250
260
}
251
261
@@ -328,6 +338,10 @@ object DiscoveryService {
328
338
for {
329
339
// Complete any deferred waiting for a ping from this peer, if we initiated the bonding.
330
340
_ <- completePing(caller)
341
+ // To protect against an eclipse attack filling up the k-table after a reboot,
342
+ // only try to bond with an incoming Ping's peer after the initial enrollment
343
+ // hash finished.
344
+ hasEnrolled <- stateRef.get.map(_.hasEnrolled)
331
345
_ <- isBonded(caller)
332
346
.ifM(
333
347
// We may already be bonded but the remote node could have changed its address.
@@ -338,6 +352,7 @@ object DiscoveryService {
338
352
bond(caller)
339
353
)
340
354
.startAndForget
355
+ .whenA(hasEnrolled)
341
356
// Return the latet local ENR sequence.
342
357
enrSeq <- localEnrSeq
343
358
} yield Some (Some (enrSeq))
@@ -745,7 +760,11 @@ object DiscoveryService {
745
760
.findNode(peer)(target)
746
761
.flatMap {
747
762
case None =>
748
- Task (logger.debug(s " Received no response for neighbors for $target from ${peer.address}" )).as(Nil )
763
+ for {
764
+ _ <- Task (logger.debug(s " Received no response for neighbors for $target from ${peer.address}" ))
765
+ // The other node has possibly unbonded from us, or it was still enrolling when we bonded. Try bonding next time.
766
+ _ <- stateRef.update(_.clearLastPongTimestamp(peer))
767
+ } yield Nil
749
768
case Some (neighbors) =>
750
769
Task (logger.debug(s " Received ${neighbors.size} neighbors for $target from ${peer.address}" ))
751
770
.as(neighbors.toList)
0 commit comments