Skip to content

Commit cd2a8d5

Browse files
authored
fix(service-disco): don't rescale bucket index by maxBuckets (#2841)
1 parent ef57a93 commit cd2a8d5

3 files changed

Lines changed: 37 additions & 8 deletions

File tree

libp2p/protocols/kademlia/routing_table.nim

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ proc new*(
2020
maxBuckets: int = DefaultMaxBuckets,
2121
selfIdPreHashed = false,
2222
): T =
23+
doAssert maxBuckets > 0 and maxBuckets <= MaxBucketsLimit,
24+
"maxBuckets must be in 1 .. " & $MaxBucketsLimit
2325
RoutingTableConfig(
2426
replication: replication,
2527
hasher: hasher,
@@ -40,6 +42,9 @@ proc new*(
4042
selfId: selfId, localNodeId: localNodeId.get(selfId), buckets: @[], config: config
4143
)
4244

45+
func bucketCount(maxBuckets: int): int =
46+
clamp(maxBuckets, 1, MaxBucketsLimit)
47+
4348
proc bucketIndex*(rtable: RoutingTable, key: Key): int =
4449
let
4550
selfHash =
@@ -50,10 +55,7 @@ proc bucketIndex*(rtable: RoutingTable, key: Key): int =
5055
keyHash = key.hashFor(rtable.config.hasher)
5156
lz = xorDistance(selfHash, keyHash).leadingZeros
5257

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

5860
proc peerIndexInBucket(bucket: Bucket, nodeId: Key): Opt[int] =
5961
for i, p in bucket.peers:
@@ -209,9 +211,7 @@ proc randomKeyInBucket*(
209211
selfId: Key, bucketIndex: int, rng: Rng, maxBuckets: int = DefaultMaxBuckets
210212
): Key =
211213
let
212-
index = clamp(bucketIndex, 0, (maxBuckets - 1))
213-
buckets = max(1, maxBuckets)
214-
leadingZeros = index * 256 div buckets
214+
leadingZeros = clamp(bucketIndex, 0, bucketCount(maxBuckets) - 1)
215215
(byteIdx, rem) = divmod(leadingZeros, 8)
216216
boundBitIdx = 7 - rem
217217

libp2p/protocols/kademlia/types.nim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import ./protobuf
1111
const
1212
IdLength* = 32 # 256-bit IDs
1313

14-
DefaultMaxBuckets* = 256
14+
MaxBucketsLimit* = IdLength * 8
15+
## a bucket per shared prefix bit; deeper prefixes than the key is long cannot exist
16+
DefaultMaxBuckets* = MaxBucketsLimit
1517
DefaultTimeout* = 5.seconds
1618
DefaultBucketRefreshTime* = 10.minutes
1719
DefaultBucketStaleTime* = 30.minutes

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)