mirror of
https://github.com/vacp2p/nim-libp2p.git
synced 2026-01-09 02:38:19 -05:00
472 lines
17 KiB
Nim
472 lines
17 KiB
Nim
{.used.}
|
|
|
|
# Nim-Libp2p
|
|
# Copyright (c) 2023 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
# at your option.
|
|
# This file may not be copied, modified, or distributed except according to
|
|
# those terms.
|
|
|
|
import sequtils, strutils
|
|
import chronos
|
|
import ../../libp2p/[protocols/rendezvous, switch]
|
|
import ../../libp2p/discovery/discoverymngr
|
|
import ../../libp2p/utils/offsettedseq
|
|
import ../helpers
|
|
import ./utils
|
|
|
|
suite "RendezVous":
|
|
teardown:
|
|
checkTrackers()
|
|
|
|
asyncTest "Request locally returns 0 for empty namespace":
|
|
let (nodes, rdvs) = setupNodes(1)
|
|
nodes.startAndDeferStop()
|
|
|
|
const namespace = ""
|
|
check rdvs[0].requestLocally(namespace).len == 0
|
|
|
|
asyncTest "Request locally returns registered peers":
|
|
let (nodes, rdvs) = setupNodes(1)
|
|
nodes.startAndDeferStop()
|
|
|
|
const namespace = "foo"
|
|
await rdvs[0].advertise(namespace)
|
|
let peerRecords = rdvs[0].requestLocally(namespace)
|
|
|
|
check:
|
|
peerRecords.len == 1
|
|
peerRecords[0] == nodes[0].peerInfo.signedPeerRecord.data
|
|
|
|
asyncTest "Unsubscribe Locally removes registered peer":
|
|
let (nodes, rdvs) = setupNodes(1)
|
|
nodes.startAndDeferStop()
|
|
|
|
const namespace = "foo"
|
|
await rdvs[0].advertise(namespace)
|
|
check rdvs[0].requestLocally(namespace).len == 1
|
|
|
|
rdvs[0].unsubscribeLocally(namespace)
|
|
check rdvs[0].requestLocally(namespace).len == 0
|
|
|
|
asyncTest "Request returns 0 for empty namespace from remote":
|
|
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(1)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodes(peerNodes[0], rendezvousNode)
|
|
|
|
const namespace = "empty"
|
|
check (await peerRdvs[0].request(Opt.some(namespace))).len == 0
|
|
|
|
asyncTest "Request returns registered peers from remote":
|
|
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(1)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodes(peerNodes[0], rendezvousNode)
|
|
|
|
const namespace = "foo"
|
|
await peerRdvs[0].advertise(namespace)
|
|
let peerRecords = await peerRdvs[0].request(Opt.some(namespace))
|
|
check:
|
|
peerRecords.len == 1
|
|
peerRecords[0] == peerNodes[0].peerInfo.signedPeerRecord.data
|
|
|
|
asyncTest "Unsubscribe removes registered peer from remote":
|
|
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(1)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodes(peerNodes[0], rendezvousNode)
|
|
|
|
const namespace = "foo"
|
|
await peerRdvs[0].advertise(namespace)
|
|
|
|
check (await peerRdvs[0].request(Opt.some(namespace))).len == 1
|
|
|
|
await peerRdvs[0].unsubscribe(namespace)
|
|
check (await peerRdvs[0].request(Opt.some(namespace))).len == 0
|
|
|
|
asyncTest "Consecutive requests with namespace returns peers with pagination":
|
|
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(11)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
const namespace = "foo"
|
|
await allFutures(peerRdvs.mapIt(it.advertise(namespace)))
|
|
|
|
var data = peerNodes.mapIt(it.peerInfo.signedPeerRecord.data)
|
|
var peerRecords = await peerRdvs[0].request(Opt.some(namespace), 5)
|
|
check:
|
|
peerRecords.len == 5
|
|
peerRecords.allIt(it in data)
|
|
data.keepItIf(it notin peerRecords)
|
|
|
|
peerRecords = await peerRdvs[0].request(Opt.some(namespace))
|
|
check:
|
|
peerRecords.len == 6
|
|
peerRecords.allIt(it in data)
|
|
|
|
check (await peerRdvs[0].request(Opt.some(namespace))).len == 0
|
|
|
|
asyncTest "Request without namespace returns all registered peers":
|
|
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(10)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
const namespaceFoo = "foo"
|
|
const namespaceBar = "Bar"
|
|
await allFutures(peerRdvs[0 ..< 5].mapIt(it.advertise(namespaceFoo)))
|
|
await allFutures(peerRdvs[5 ..< 10].mapIt(it.advertise(namespaceBar)))
|
|
|
|
check (await peerRdvs[0].request()).len == 10
|
|
|
|
check (await peerRdvs[0].request(Opt.none(string))).len == 10
|
|
|
|
asyncTest "Consecutive requests with namespace keep cookie and retun only new peers":
|
|
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(2)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
let
|
|
rdv0 = peerRdvs[0]
|
|
rdv1 = peerRdvs[1]
|
|
const namespace = "foo"
|
|
|
|
await rdv0.advertise(namespace)
|
|
discard await rdv0.request(Opt.some(namespace))
|
|
|
|
await rdv1.advertise(namespace)
|
|
let peerRecords = await rdv0.request(Opt.some(namespace))
|
|
|
|
check:
|
|
peerRecords.len == 1
|
|
peerRecords[0] == peerNodes[1].peerInfo.signedPeerRecord.data
|
|
|
|
asyncTest "Request with namespace pagination with multiple namespaces":
|
|
let (rendezvousNode, peerNodes, peerRdvs, _) = setupRendezvousNodeWithPeerNodes(30)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
let rdv = peerRdvs[0]
|
|
|
|
# Register peers in different namespaces in mixed order
|
|
const
|
|
namespaceFoo = "foo"
|
|
namespaceBar = "bar"
|
|
await allFutures(peerRdvs[0 ..< 5].mapIt(it.advertise(namespaceFoo)))
|
|
await allFutures(peerRdvs[5 ..< 10].mapIt(it.advertise(namespaceBar)))
|
|
await allFutures(peerRdvs[10 ..< 15].mapIt(it.advertise(namespaceFoo)))
|
|
await allFutures(peerRdvs[15 ..< 20].mapIt(it.advertise(namespaceBar)))
|
|
|
|
var fooRecords = peerNodes[0 ..< 5].concat(peerNodes[10 ..< 15]).mapIt(
|
|
it.peerInfo.signedPeerRecord.data
|
|
)
|
|
var barRecords = peerNodes[5 ..< 10].concat(peerNodes[15 ..< 20]).mapIt(
|
|
it.peerInfo.signedPeerRecord.data
|
|
)
|
|
|
|
# Foo Page 1 with limit
|
|
var peerRecords = await rdv.request(Opt.some(namespaceFoo), 2)
|
|
check:
|
|
peerRecords.len == 2
|
|
peerRecords.allIt(it in fooRecords)
|
|
fooRecords.keepItIf(it notin peerRecords)
|
|
|
|
# Foo Page 2 with limit
|
|
peerRecords = await rdv.request(Opt.some(namespaceFoo), 5)
|
|
check:
|
|
peerRecords.len == 5
|
|
peerRecords.allIt(it in fooRecords)
|
|
fooRecords.keepItIf(it notin peerRecords)
|
|
|
|
# Foo Page 3 with the rest
|
|
peerRecords = await rdv.request(Opt.some(namespaceFoo))
|
|
check:
|
|
peerRecords.len == 3
|
|
peerRecords.allIt(it in fooRecords)
|
|
fooRecords.keepItIf(it notin peerRecords)
|
|
check fooRecords.len == 0
|
|
|
|
# Foo Page 4 empty
|
|
peerRecords = await rdv.request(Opt.some(namespaceFoo))
|
|
check peerRecords.len == 0
|
|
|
|
# Bar Page 1 with all
|
|
peerRecords = await rdv.request(Opt.some(namespaceBar), 30)
|
|
check:
|
|
peerRecords.len == 10
|
|
peerRecords.allIt(it in barRecords)
|
|
barRecords.keepItIf(it notin peerRecords)
|
|
check barRecords.len == 0
|
|
|
|
# Register new peers
|
|
await allFutures(peerRdvs[20 ..< 25].mapIt(it.advertise(namespaceFoo)))
|
|
await allFutures(peerRdvs[25 ..< 30].mapIt(it.advertise(namespaceBar)))
|
|
|
|
# Foo Page 5 only new peers
|
|
peerRecords = await rdv.request(Opt.some(namespaceFoo))
|
|
check:
|
|
peerRecords.len == 5
|
|
peerRecords.allIt(
|
|
it in peerNodes[20 ..< 25].mapIt(it.peerInfo.signedPeerRecord.data)
|
|
)
|
|
|
|
# Bar Page 2 only new peers
|
|
peerRecords = await rdv.request(Opt.some(namespaceBar))
|
|
check:
|
|
peerRecords.len == 5
|
|
peerRecords.allIt(
|
|
it in peerNodes[25 ..< 30].mapIt(it.peerInfo.signedPeerRecord.data)
|
|
)
|
|
|
|
# All records
|
|
peerRecords = await rdv.request(Opt.none(string))
|
|
check peerRecords.len == 30
|
|
|
|
asyncTest "Request with namespace with expired peers":
|
|
let (rendezvousNode, peerNodes, peerRdvs, rdv) =
|
|
setupRendezvousNodeWithPeerNodes(20)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
# Advertise peers
|
|
const
|
|
namespaceFoo = "foo"
|
|
namespaceBar = "bar"
|
|
await allFutures(peerRdvs[0 ..< 5].mapIt(it.advertise(namespaceFoo)))
|
|
await allFutures(peerRdvs[5 ..< 10].mapIt(it.advertise(namespaceBar)))
|
|
|
|
check:
|
|
(await peerRdvs[0].request(Opt.some(namespaceFoo))).len == 5
|
|
(await peerRdvs[0].request(Opt.some(namespaceBar))).len == 5
|
|
|
|
# Overwrite register timeout loop interval
|
|
discard rdv.deletesRegister(1.seconds)
|
|
|
|
# Overwrite expiration times
|
|
let now = Moment.now()
|
|
for reg in rdv.registered.s.mitems:
|
|
reg.expiration = now
|
|
|
|
# Wait for the deletion
|
|
checkUntilTimeout:
|
|
rdv.registered.offset == 10
|
|
rdv.registered.s.len == 0
|
|
(await peerRdvs[0].request(Opt.some(namespaceFoo))).len == 0
|
|
(await peerRdvs[0].request(Opt.some(namespaceBar))).len == 0
|
|
|
|
# Advertise new peers
|
|
await allFutures(peerRdvs[10 ..< 15].mapIt(it.advertise(namespaceFoo)))
|
|
await allFutures(peerRdvs[15 ..< 20].mapIt(it.advertise(namespaceBar)))
|
|
|
|
check:
|
|
rdv.registered.offset == 10
|
|
rdv.registered.s.len == 10
|
|
(await peerRdvs[0].request(Opt.some(namespaceFoo))).len == 5
|
|
(await peerRdvs[0].request(Opt.some(namespaceBar))).len == 5
|
|
|
|
asyncTest "Cookie offset is reset to end (returns empty) then new peers are discoverable":
|
|
let (rendezvousNode, peerNodes, peerRdvs, rdv) = setupRendezvousNodeWithPeerNodes(3)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
const namespace = "foo"
|
|
# Advertise two peers initially
|
|
await allFutures(peerRdvs[0 ..< 2].mapIt(it.advertise(namespace)))
|
|
|
|
# Build and inject overflow cookie: offset past current high()+1
|
|
let offset = (rdv.registered.high + 1000).uint64
|
|
let cookie = buildProtobufCookie(offset, namespace)
|
|
peerRdvs[0].injectCookieForPeer(rendezvousNode.peerInfo.peerId, namespace, cookie)
|
|
|
|
# First request should return empty due to clamping to high()+1
|
|
check (await peerRdvs[0].request(Opt.some(namespace))).len == 0
|
|
|
|
# Advertise a new peer, next request should return only the new one
|
|
await peerRdvs[2].advertise(namespace)
|
|
let peerRecords = await peerRdvs[0].request(Opt.some(namespace))
|
|
check:
|
|
peerRecords.len == 1
|
|
peerRecords[0] == peerNodes[2].peerInfo.signedPeerRecord.data
|
|
|
|
asyncTest "Cookie offset is reset to low after flush (returns current entries)":
|
|
let (rendezvousNode, peerNodes, peerRdvs, rdv) = setupRendezvousNodeWithPeerNodes(8)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
const namespace = "foo"
|
|
# Advertise 4 peers in namespace
|
|
await allFutures(peerRdvs[0 ..< 4].mapIt(it.advertise(namespace)))
|
|
|
|
# Expire all and flush to advance registered.offset
|
|
discard rdv.deletesRegister(1.seconds)
|
|
let now = Moment.now()
|
|
for reg in rdv.registered.s.mitems:
|
|
reg.expiration = now
|
|
|
|
checkUntilTimeout:
|
|
rdv.registered.s.len == 0
|
|
rdv.registered.offset == 4
|
|
|
|
# Advertise 4 new peers
|
|
await allFutures(peerRdvs[4 ..< 8].mapIt(it.advertise(namespace)))
|
|
|
|
# Build and inject underflow cookie: offset behind current low
|
|
let offset = 0'u64
|
|
let cookie = buildProtobufCookie(offset, namespace)
|
|
peerRdvs[0].injectCookieForPeer(rendezvousNode.peerInfo.peerId, namespace, cookie)
|
|
|
|
check (await peerRdvs[0].request(Opt.some(namespace))).len == 4
|
|
|
|
asyncTest "Cookie namespace mismatch resets to low (returns peers despite offset)":
|
|
let (rendezvousNode, peerNodes, peerRdvs, rdv) = setupRendezvousNodeWithPeerNodes(3)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodesToRendezvousNode(peerNodes, rendezvousNode)
|
|
|
|
const namespace = "foo"
|
|
await allFutures(peerRdvs.mapIt(it.advertise(namespace)))
|
|
|
|
# Build and inject cookie with wrong namespace
|
|
let offset = 10.uint64
|
|
let cookie = buildProtobufCookie(offset, "other")
|
|
peerRdvs[0].injectCookieForPeer(rendezvousNode.peerInfo.peerId, namespace, cookie)
|
|
|
|
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 "Peer registration is ignored if limit of 1000 registrations is reached":
|
|
let (rendezvousNode, peerNodes, peerRdvs, rendezvousRdv) =
|
|
setupRendezvousNodeWithPeerNodes(1)
|
|
(rendezvousNode & peerNodes).startAndDeferStop()
|
|
|
|
await connectNodes(peerNodes[0], rendezvousNode)
|
|
|
|
const namespace = "foo"
|
|
let peerRdv = peerRdvs[0]
|
|
|
|
# Create 999 registrations
|
|
await populatePeerRegistrations(
|
|
peerRdv, rendezvousRdv, namespace, RegistrationLimitPerPeer - 1
|
|
)
|
|
|
|
# 1000th registration allowed
|
|
await peerRdv.advertise(namespace)
|
|
check rendezvousRdv.registered.s.len == RegistrationLimitPerPeer
|
|
|
|
# 1001st registration ignored, limit reached
|
|
await peerRdv.advertise(namespace)
|
|
check rendezvousRdv.registered.s.len == RegistrationLimitPerPeer
|
|
|
|
asyncTest "Various local error":
|
|
let rdv = RendezVous.new(minDuration = 1.minutes, maxDuration = 72.hours)
|
|
expect AdvertiseError:
|
|
discard await rdv.request(Opt.some("A".repeat(300)))
|
|
expect AdvertiseError:
|
|
discard await rdv.request(Opt.some("A"), -1)
|
|
expect AdvertiseError:
|
|
discard await rdv.request(Opt.some("A"), 3000)
|
|
expect AdvertiseError:
|
|
await rdv.advertise("A".repeat(300))
|
|
expect AdvertiseError:
|
|
await rdv.advertise("A", 73.hours)
|
|
expect AdvertiseError:
|
|
await rdv.advertise("A", 30.seconds)
|
|
|
|
test "Various config error":
|
|
expect RendezVousError:
|
|
discard RendezVous.new(minDuration = 30.seconds)
|
|
expect RendezVousError:
|
|
discard RendezVous.new(maxDuration = 73.hours)
|
|
expect RendezVousError:
|
|
discard RendezVous.new(minDuration = 15.minutes, maxDuration = 10.minutes)
|