@@ -12,18 +12,25 @@ proc testKey*(x: byte): Key =
1212 buf[31 ] = x
1313 return @ buf
1414
15+ proc randomTestKey (): Key =
16+ var buf = newSeqUninit [byte ](IdLength )
17+ rng ().generate (buf)
18+ buf
19+
20+ proc keyWithLeadingZeros (n: int ): Key =
21+ # # Key whose XOR distance to an all-zero selfId under `noOpHasher` has
22+ # # exactly `n` leading zero bits.
23+ doAssert n < IdLength * 8 , " an all-zero key has no first set bit"
24+ var buf: array [IdLength , byte ]
25+ buf[n div 8 ] = 0x 80 'u8 shr (n mod 8 )
26+ return @ buf
27+
1528suite " KadDHT Routing Table" :
1629 const TargetBucket = 6
1730
1831 proc keyInBucket (rt: RoutingTable , bucket: int ): Key =
1932 randomKeyInBucket (rt, bucket, rng ()).expect (" bucket is reachable" )
2033
21- proc keyWithPrefix (leadingZeros: int ): Key =
22- # # Key whose distance from `testKey(0)` has exactly `leadingZeros` leading zeros.
23- var buf: array [IdLength , byte ]
24- buf[leadingZeros div 8 ] = 0b 1000_0000 'u8 shr (leadingZeros mod 8 )
25- return @ buf
26-
2734 test " inserts single key in correct bucket" :
2835 let selfId = testKey (0 )
2936 var rt = RoutingTable .new (selfId)
@@ -184,7 +191,7 @@ suite "KadDHT Routing Table":
184191 RoutingTable .new (selfId, RoutingTableConfig .new (hasher = Opt .some (noOpHasher)))
185192
186193 for lz in 0 .. 8 :
187- check rt.bucketIndex (keyWithPrefix (lz)) == lz
194+ check rt.bucketIndex (keyWithLeadingZeros (lz)) == lz
188195
189196 test " small maxBuckets keeps prefix resolution and saturates the last bucket" :
190197 let selfId = testKey (0 )
@@ -196,11 +203,11 @@ suite "KadDHT Routing Table":
196203
197204 # Prefix lengths below the cap each get their own bucket.
198205 for lz in 0 ..< MaxBuckets - 1 :
199- check rt.bucketIndex (keyWithPrefix (lz)) == lz
206+ check rt.bucketIndex (keyWithLeadingZeros (lz)) == lz
200207
201208 # Deeper prefixes all fall into the last bucket.
202209 for lz in MaxBuckets - 1 .. MaxBuckets + 8 :
203- check rt.bucketIndex (keyWithPrefix (lz)) == MaxBuckets - 1
210+ check rt.bucketIndex (keyWithLeadingZeros (lz)) == MaxBuckets - 1
204211
205212 test " randomKeyInBucket targets the bucket under the default (hashing) hasher" :
206213 # The bucket of a key is decided by its *hash*, so a target built by
@@ -232,6 +239,44 @@ suite "KadDHT Routing Table":
232239 # The last bucket needs 15 shared prefix bits — beyond the cap.
233240 check randomKeyInBucket (rt, MaxBuckets - 1 , rng ()).isNone ()
234241
242+ test " refreshTarget falls back to a peer of a bucket too near to search for" :
243+ # Buckets past the cap share too long a prefix with selfId to draw at random.
244+ const NearBucket = MaxRefreshLeadingZeros + 1
245+ let selfId = testKey (0 )
246+ var rt = RoutingTable .new (selfId, RoutingTableConfig .new ())
247+
248+ var inserted: seq [Key ]
249+ while inserted.len < 3 :
250+ let key = randomTestKey ()
251+ if rt.bucketIndex (key) != NearBucket :
252+ continue
253+ discard rt.insert (key)
254+ inserted.add (key)
255+
256+ check randomKeyInBucket (rt, NearBucket , rng ()).isNone ()
257+
258+ let target = rt.refreshTarget (NearBucket , rng ()).expect (" bucket holds peers" )
259+ check:
260+ target in inserted
261+ rt.bucketIndex (target) == NearBucket
262+
263+ test " refreshTarget returns none for an unreachable empty bucket" :
264+ let selfId = testKey (0 )
265+ var rt = RoutingTable .new (selfId, RoutingTableConfig .new ())
266+
267+ check rt.refreshTarget (MaxRefreshLeadingZeros + 1 , rng ()).isNone ()
268+
269+ test " refreshTarget prefers a random key when the bucket is reachable" :
270+ let selfId = testKey (0 )
271+ var rt = RoutingTable .new (selfId, RoutingTableConfig .new ())
272+ let peer = rt.keyInBucket (TargetBucket )
273+ discard rt.insert (peer)
274+
275+ let target = rt.refreshTarget (TargetBucket , rng ()).expect (" bucket is reachable" )
276+ check:
277+ target != peer
278+ rt.bucketIndex (target) == TargetBucket
279+
235280 test " randomKey returns none for empty bucket" :
236281 var bucket: Bucket
237282 check randomKey (bucket, rng ()).isNone ()
0 commit comments