From d2eaf07960f363ed75183e6187ce9471d4639f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kami=C5=84ski?= Date: Tue, 2 Sep 2025 15:43:48 +0100 Subject: [PATCH] test(rendezvous): Registration TTL tests (#1655) --- libp2p/protocols/rendezvous.nim | 21 ++++---- tests/discovery/testrendezvous.nim | 82 ++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/libp2p/protocols/rendezvous.nim b/libp2p/protocols/rendezvous.nim index 382482128..599ffe5b5 100644 --- a/libp2p/protocols/rendezvous.nim +++ b/libp2p/protocols/rendezvous.nim @@ -35,7 +35,10 @@ declareGauge(libp2p_rendezvous_namespaces, "number of registered namespaces") const RendezVousCodec* = "/rendezvous/1.0.0" + # Default minimum TTL per libp2p spec MinimumDuration* = 2.hours + # Lower validation limit to accommodate Waku requirements + MinimumAcceptedDuration = 1.minutes MaximumDuration = 72.hours MaximumMessageLen = 1 shl 22 # 4MB MinimumNamespaceLen = 1 @@ -69,7 +72,7 @@ type Register = object ns: string signedPeerRecord: seq[byte] - ttl: Opt[uint64] # in seconds + ttl*: Opt[uint64] # in seconds RegisterResponse = object status: ResponseStatus @@ -312,7 +315,7 @@ type RegisteredData = object expiration*: Moment peerId*: PeerId - data: Register + data*: Register RendezVous* = ref object of LPProtocol # Registered needs to be an offsetted sequence @@ -324,7 +327,7 @@ type namespaces*: Table[string, seq[int]] rng: ref HmacDrbgContext salt: string - defaultDT: Moment + expiredDT: Moment registerDeletionLoop: Future[void] #registerEvent: AsyncEvent # TODO: to raise during the heartbeat # + make the heartbeat sleep duration "smarter" @@ -409,7 +412,7 @@ proc save( if rdv.registered[index].peerId == peerId: if update == false: return - rdv.registered[index].expiration = rdv.defaultDT + rdv.registered[index].expiration = rdv.expiredDT rdv.registered.add( RegisteredData( peerId: peerId, @@ -446,7 +449,7 @@ proc unregister(rdv: RendezVous, conn: Connection, u: Unregister) = try: for index in rdv.namespaces[nsSalted]: if rdv.registered[index].peerId == conn.peerId: - rdv.registered[index].expiration = rdv.defaultDT + rdv.registered[index].expiration = rdv.expiredDT libp2p_rendezvous_registered.dec() except KeyError: return @@ -689,7 +692,7 @@ proc unsubscribeLocally*(rdv: RendezVous, ns: string) = try: for index in rdv.namespaces[nsSalted]: if rdv.registered[index].peerId == rdv.switch.peerInfo.peerId: - rdv.registered[index].expiration = rdv.defaultDT + rdv.registered[index].expiration = rdv.expiredDT except KeyError: return @@ -744,10 +747,10 @@ proc new*( minDuration = MinimumDuration, maxDuration = MaximumDuration, ): T {.raises: [RendezVousError].} = - if minDuration < 1.minutes: + if minDuration < MinimumAcceptedDuration: raise newException(RendezVousError, "TTL too short: 1 minute minimum") - if maxDuration > 72.hours: + if maxDuration > MaximumDuration: raise newException(RendezVousError, "TTL too long: 72 hours maximum") if minDuration >= maxDuration: @@ -761,7 +764,7 @@ proc new*( rng: rng, salt: string.fromBytes(generateBytes(rng[], 8)), registered: initOffsettedSeq[RegisteredData](), - defaultDT: Moment.now() - 1.days, + expiredDT: Moment.now() - 1.days, #registerEvent: newAsyncEvent(), sema: newAsyncSemaphore(SemaphoreDefaultSize), minDuration: minDuration, diff --git a/tests/discovery/testrendezvous.nim b/tests/discovery/testrendezvous.nim index 8da3c083e..f36841b3d 100644 --- a/tests/discovery/testrendezvous.nim +++ b/tests/discovery/testrendezvous.nim @@ -342,6 +342,88 @@ suite "RendezVous": check (await peerRdvs[0].request(Opt.some(namespace))).len == 3 + asyncTest "Peer default TTL is saved when advertised": + let (rendezvousNode, peerNodes, peerRdvs, rendezvousRdv) = + setupRendezvousNodeWithPeerNodes(1) + (rendezvousNode & peerNodes).startAndDeferStop() + + await connectNodes(peerNodes[0], rendezvousNode) + + const namespace = "foo" + let timeBefore = Moment.now() + await peerRdvs[0].advertise(namespace) + let timeAfter = Moment.now() + + # expiration within [timeBefore + 2hours, timeAfter + 2hours] + check: + # Peer Node side + peerRdvs[0].registered.s[0].data.ttl.get == MinimumDuration.seconds.uint64 + peerRdvs[0].registered.s[0].expiration >= timeBefore + MinimumDuration + peerRdvs[0].registered.s[0].expiration <= timeAfter + MinimumDuration + # Rendezvous Node side + rendezvousRdv.registered.s[0].data.ttl.get == MinimumDuration.seconds.uint64 + rendezvousRdv.registered.s[0].expiration >= timeBefore + MinimumDuration + rendezvousRdv.registered.s[0].expiration <= timeAfter + MinimumDuration + + asyncTest "Peer TTL is saved when advertised with TTL": + let (rendezvousNode, peerNodes, peerRdvs, rendezvousRdv) = + setupRendezvousNodeWithPeerNodes(1) + (rendezvousNode & peerNodes).startAndDeferStop() + + await connectNodes(peerNodes[0], rendezvousNode) + + const + namespace = "foo" + ttl = 3.hours + let timeBefore = Moment.now() + await peerRdvs[0].advertise(namespace, ttl) + let timeAfter = Moment.now() + + # expiration within [timeBefore + ttl, timeAfter + ttl] + check: + # Peer Node side + peerRdvs[0].registered.s[0].data.ttl.get == ttl.seconds.uint64 + peerRdvs[0].registered.s[0].expiration >= timeBefore + ttl + peerRdvs[0].registered.s[0].expiration <= timeAfter + ttl + # Rendezvous Node side + rendezvousRdv.registered.s[0].data.ttl.get == ttl.seconds.uint64 + rendezvousRdv.registered.s[0].expiration >= timeBefore + ttl + rendezvousRdv.registered.s[0].expiration <= timeAfter + ttl + + asyncTest "Peer can reregister to update its TTL before previous TTL expires": + let (rendezvousNode, peerNodes, peerRdvs, rendezvousRdv) = + setupRendezvousNodeWithPeerNodes(1) + (rendezvousNode & peerNodes).startAndDeferStop() + + await connectNodes(peerNodes[0], rendezvousNode) + + const namespace = "foo" + let now = Moment.now() + + await peerRdvs[0].advertise(namespace) + check: + # Peer Node side + peerRdvs[0].registered.s.len == 1 + peerRdvs[0].registered.s[0].expiration > now + # Rendezvous Node side + rendezvousRdv.registered.s.len == 1 + rendezvousRdv.registered.s[0].expiration > now + + await peerRdvs[0].advertise(namespace, 5.hours) + check: + # Added 2nd registration + # Updated expiration of the 1st one to the past + # Will be deleted on deletion heartbeat + # Peer Node side + peerRdvs[0].registered.s.len == 2 + peerRdvs[0].registered.s[0].expiration < now + # Rendezvous Node side + rendezvousRdv.registered.s.len == 2 + rendezvousRdv.registered.s[0].expiration < now + + # Returns only one record + check (await peerRdvs[0].request(Opt.some(namespace))).len == 1 + asyncTest "Various local error": let rdv = RendezVous.new(minDuration = 1.minutes, maxDuration = 72.hours) expect AdvertiseError: