@@ -226,6 +226,87 @@ suite "Service Discovery Registrar - Waiting Time Calculation":
226226 # ipSim ≈ 0.97 for same /24; w should be in the hundreds of seconds
227227 check w > 500 .seconds
228228
229+ suite " Service Discovery Registrar - advertExpiry cap" :
230+ test " registration caps the offered tWaitFor at advertExpiry" :
231+ let advertExpiry = 100 .secs
232+ let conf = ServiceDiscoveryConfig .new (
233+ advertExpiry = advertExpiry, safetyParam = 1.0 , advertCacheCap = 10
234+ )
235+ let disco = setupServiceDiscoveryNode (discoConfig = conf)
236+ let serviceName = " service"
237+ let serviceId = serviceName.hashServiceId ()
238+ let advertiserKey = PrivateKey .random (rng ()).get ()
239+ let advertiserId = PeerId .init (advertiserKey).get ()
240+ let adBytes = makeAdvertisement (serviceName, advertiserKey).encode ().get ()
241+
242+ # Saturate the cache: occupancy pins at 100.0, so w = 100*100*1.0 = 10000s.
243+ for i in 0 ..< 10 :
244+ disco.registrar.cacheTimestamps[(peerId: randomPeerId (), seqNo: uint64 (i))] =
245+ Moment .now ()
246+
247+ let inMsg = kadprotobuf.Message (
248+ msgType: kadprotobuf.MessageType .register,
249+ key: serviceId,
250+ register: Opt .some (
251+ kadprotobuf.RegisterMessage (
252+ advertisement: adBytes,
253+ status: Opt .none (kadprotobuf.RegistrationStatus ),
254+ ticket: Opt .none (Ticket ),
255+ )
256+ ),
257+ )
258+ let reply = disco.registration (advertiserId, inMsg).register.get ()
259+
260+ check reply.status.get () == kadprotobuf.RegistrationStatus .Wait
261+ check reply.ticket.get ().tWaitFor.get () == advertExpiry
262+
263+ test " sustained overload keeps offering advertExpiry-length waits across retries" :
264+ let advertExpiry = 100 .secs
265+ let registrationWindow = 10 .secs
266+ let conf = ServiceDiscoveryConfig .new (
267+ advertExpiry = advertExpiry,
268+ safetyParam = 1.0 ,
269+ advertCacheCap = 10 ,
270+ registrationWindow = registrationWindow,
271+ )
272+ let disco = setupServiceDiscoveryNode (discoConfig = conf)
273+ let serviceName = " service"
274+ let serviceId = serviceName.hashServiceId ()
275+ let advertiserKey = PrivateKey .random (rng ()).get ()
276+ let advertiserId = PeerId .init (advertiserKey).get ()
277+ let adBytes = makeAdvertisement (serviceName, advertiserKey).encode ().get ()
278+
279+ for i in 0 ..< 10 :
280+ disco.registrar.cacheTimestamps[(peerId: randomPeerId (), seqNo: uint64 (i))] =
281+ Moment .now ()
282+
283+ let firstAttemptTime =
284+ Moment .init ((Moment .now () - advertExpiry).epochSeconds, Second )
285+ var retryTicket = Ticket (
286+ advertisement: adBytes,
287+ tInit: firstAttemptTime,
288+ tMod: firstAttemptTime,
289+ tWaitFor: advertExpiry,
290+ signature: Opt .none (seq [byte ]),
291+ )
292+ check retryTicket.sign (disco.switch.peerInfo.privateKey).isOk ()
293+
294+ let inMsg = kadprotobuf.Message (
295+ msgType: kadprotobuf.MessageType .register,
296+ key: serviceId,
297+ register: Opt .some (
298+ kadprotobuf.RegisterMessage (
299+ advertisement: adBytes,
300+ status: Opt .none (kadprotobuf.RegistrationStatus ),
301+ ticket: Opt .some (retryTicket),
302+ )
303+ ),
304+ )
305+ let reply = disco.registration (advertiserId, inMsg).register.get ()
306+
307+ check reply.status.get () == kadprotobuf.RegistrationStatus .Wait
308+ check reply.ticket.get ().tWaitFor.get () == advertExpiry
309+
229310suite " Service Discovery Registrar - Lower Bound Enforcement" :
230311 test " waitingTime enforces service lower bound when exists" :
231312 let registrar = Registrar .new ()
@@ -278,7 +359,7 @@ suite "Service Discovery Registrar - Lower Bound Enforcement":
278359
279360 test " waitingTime uses most restrictive lower bound" :
280361 let registrar = Registrar .new ()
281- let discoConfig = ServiceDiscoveryConfig .new ()
362+ let discoConfig = ServiceDiscoveryConfig .new (advertExpiry = 2500 .secs )
282363 let serviceId = makeServiceId ()
283364 let ip1 = " 192.168.1.1"
284365 let ip2 = " 10.0.0.1"
@@ -1291,6 +1372,49 @@ suite "Service Discovery Registrar - registration response":
12911372 ticket.tWaitFor.get () > ZeroDuration
12921373 ticket.verify (registrarPubKey)
12931374
1375+ test " registration quantizes now to whole-second granularity" :
1376+ # `now` is read once in registration() and truncated to whole seconds, so
1377+ # every time value it emits — the ticket's tInit/tMod and the recorded
1378+ # service timestamp — must land exactly on a second boundary. Using the
1379+ # raw Moment.now() (sub-second nanoseconds) would break these checks.
1380+ let config = ServiceDiscoveryConfig .new (safetyParam = 1.0 ) # forces a Wait
1381+ let disco = setupServiceDiscoveryNode (discoConfig = config)
1382+ let serviceName = " service"
1383+ let serviceId = serviceName.hashServiceId ()
1384+ let ad = makeAdvertisement (serviceName)
1385+ let adBytes = ad.encode ().get ()
1386+ let advertiserId = ad.data.peerId
1387+
1388+ let inMsg = kadprotobuf.Message (
1389+ msgType: kadprotobuf.MessageType .register,
1390+ key: serviceId,
1391+ register: Opt .some (
1392+ kadprotobuf.RegisterMessage (
1393+ advertisement: adBytes,
1394+ status: Opt .none (kadprotobuf.RegistrationStatus ),
1395+ ticket: Opt .none (Ticket ),
1396+ )
1397+ ),
1398+ )
1399+
1400+ let reply = disco.registration (advertiserId, inMsg).register.get ()
1401+
1402+ check reply.status.get () == kadprotobuf.RegistrationStatus .Wait
1403+ check reply.ticket.isSome ()
1404+
1405+ # Reconstructing a Moment from its own epochSeconds is a fixed point only
1406+ # when the moment already has no sub-second part.
1407+ let ticket = reply.ticket.get ()
1408+ let tInit = ticket.tInit.get ()
1409+ let tMod = ticket.tMod.get ()
1410+ check tInit == Moment .init (tInit.epochSeconds, Second )
1411+ check tMod == Moment .init (tMod.epochSeconds, Second )
1412+
1413+ # The recorded service timestamp mirrors the same truncated `now`.
1414+ check serviceId in disco.registrar.timestampService
1415+ let ts = disco.registrar.timestampService[serviceId]
1416+ check ts == Moment .init (ts.epochSeconds, Second )
1417+
12941418 test " retrying with a valid ticket inside the window caches the ad" :
12951419 let conf = ServiceDiscoveryConfig .new (registrationWindow = 10 .secs)
12961420 let disco = setupServiceDiscoveryNode (discoConfig = conf)
0 commit comments