Skip to content

Commit be5cef1

Browse files
committed
fix(service-disco): maxBuckets scaling
1 parent 0f347b9 commit be5cef1

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

libp2p/protocols/kademlia/routing_table.nim

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ proc new*(
4040
selfId: selfId, localNodeId: localNodeId.get(selfId), buckets: @[], config: config
4141
)
4242

43+
func bucketCount(maxBuckets: int): int =
44+
## Buckets past the key length can never be reached, and at least one bucket
45+
## is always needed.
46+
clamp(maxBuckets, 1, IdLength * 8)
47+
4348
proc bucketIndex*(rtable: RoutingTable, key: Key): int =
49+
## Bucket index is the shared prefix length with `selfId`. Tables with fewer
50+
## buckets than the key length don't rescale it: the last bucket absorbs every
51+
## peer sharing at least that long a prefix.
4452
let
4553
selfHash =
4654
if rtable.config.selfIdPreHashed:
@@ -50,10 +58,7 @@ proc bucketIndex*(rtable: RoutingTable, key: Key): int =
5058
keyHash = key.hashFor(rtable.config.hasher)
5159
lz = xorDistance(selfHash, keyHash).leadingZeros
5260

53-
if rtable.config.maxBuckets <= 1:
54-
return 0
55-
56-
return min(((lz * rtable.config.maxBuckets) div 256), rtable.config.maxBuckets - 1)
61+
min(lz, bucketCount(rtable.config.maxBuckets) - 1)
5762

5863
proc peerIndexInBucket(bucket: Bucket, nodeId: Key): Opt[int] =
5964
for i, p in bucket.peers:
@@ -209,9 +214,7 @@ proc randomKeyInBucket*(
209214
selfId: Key, bucketIndex: int, rng: Rng, maxBuckets: int = DefaultMaxBuckets
210215
): Key =
211216
let
212-
index = clamp(bucketIndex, 0, (maxBuckets - 1))
213-
buckets = max(1, maxBuckets)
214-
leadingZeros = index * 256 div buckets
217+
leadingZeros = clamp(bucketIndex, 0, bucketCount(maxBuckets) - 1)
215218
(byteIdx, rem) = divmod(leadingZeros, 8)
216219
boundBitIdx = 7 - rem
217220

tests/libp2p/kademlia/test_routing_table.nim

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,33 @@ suite "KadDHT Routing Table":
169169
idx == TargetBucket
170170
rid != selfId
171171

172+
test "bucketIndex is the prefix length, not a rescale of it":
173+
let selfId = testKey(0)
174+
var rt =
175+
RoutingTable.new(selfId, RoutingTableConfig.new(hasher = Opt.some(noOpHasher)))
176+
177+
for lz in 0 .. 8:
178+
let key = randomKeyInBucket(selfId, lz, rng())
179+
check rt.bucketIndex(key) == lz
180+
181+
test "small maxBuckets keeps prefix resolution and saturates the last bucket":
182+
let selfId = testKey(0)
183+
const MaxBuckets = 16
184+
var rt = RoutingTable.new(
185+
selfId,
186+
RoutingTableConfig.new(hasher = Opt.some(noOpHasher), maxBuckets = MaxBuckets),
187+
)
188+
189+
# Prefix lengths below the cap each get their own bucket.
190+
for lz in 0 ..< MaxBuckets - 1:
191+
let key = randomKeyInBucket(selfId, lz, rng(), MaxBuckets)
192+
check rt.bucketIndex(key) == lz
193+
194+
# Deeper prefixes all fall into the last bucket.
195+
for lz in MaxBuckets - 1 .. MaxBuckets + 8:
196+
let key = randomKeyInBucket(selfId, lz, rng(), DefaultMaxBuckets)
197+
check rt.bucketIndex(key) == MaxBuckets - 1
198+
172199
test "randomKey returns none for empty bucket":
173200
var bucket: Bucket
174201
check randomKey(bucket, rng()).isNone()

0 commit comments

Comments
 (0)