parent
1c99aca054
commit
32233d36c8
|
@ -0,0 +1,163 @@
|
||||||
|
# Nim-LibP2P
|
||||||
|
# Copyright (c) 2022 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.
|
||||||
|
|
||||||
|
when (NimMajor, NimMinor) < (1, 4):
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
else:
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
import std/sequtils
|
||||||
|
import chronos, chronicles, stew/results
|
||||||
|
import ../errors
|
||||||
|
|
||||||
|
type
|
||||||
|
BaseAttr = ref object of RootObj
|
||||||
|
comparator: proc(f, c: BaseAttr): bool {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
|
Attribute[T] = ref object of BaseAttr
|
||||||
|
value: T
|
||||||
|
|
||||||
|
PeerAttributes* = object
|
||||||
|
attributes: seq[BaseAttr]
|
||||||
|
|
||||||
|
DiscoveryService* = distinct string
|
||||||
|
|
||||||
|
proc `==`*(a, b: DiscoveryService): bool {.borrow.}
|
||||||
|
|
||||||
|
proc ofType*[T](f: BaseAttr, _: type[T]): bool =
|
||||||
|
return f of Attribute[T]
|
||||||
|
|
||||||
|
proc to*[T](f: BaseAttr, _: type[T]): T =
|
||||||
|
Attribute[T](f).value
|
||||||
|
|
||||||
|
proc add*[T](pa: var PeerAttributes,
|
||||||
|
value: T) =
|
||||||
|
pa.attributes.add(Attribute[T](
|
||||||
|
value: value,
|
||||||
|
comparator: proc(f: BaseAttr, c: BaseAttr): bool =
|
||||||
|
f.ofType(T) and c.ofType(T) and f.to(T) == c.to(T)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
iterator items*(pa: PeerAttributes): BaseAttr =
|
||||||
|
for f in pa.attributes:
|
||||||
|
yield f
|
||||||
|
|
||||||
|
proc getAll*[T](pa: PeerAttributes, t: typedesc[T]): seq[T] =
|
||||||
|
for f in pa.attributes:
|
||||||
|
if f.ofType(T):
|
||||||
|
result.add(f.to(T))
|
||||||
|
|
||||||
|
proc `{}`*[T](pa: PeerAttributes, t: typedesc[T]): Opt[T] =
|
||||||
|
for f in pa.attributes:
|
||||||
|
if f.ofType(T):
|
||||||
|
return Opt.some(f.to(T))
|
||||||
|
Opt.none(T)
|
||||||
|
|
||||||
|
proc `[]`*[T](pa: PeerAttributes, t: typedesc[T]): T {.raises: [Defect, KeyError].} =
|
||||||
|
pa{T}.valueOr: raise newException(KeyError, "Attritute not found")
|
||||||
|
|
||||||
|
proc match*(pa, candidate: PeerAttributes): bool =
|
||||||
|
for f in pa.attributes:
|
||||||
|
block oneAttribute:
|
||||||
|
for field in candidate.attributes:
|
||||||
|
if field.comparator(field, f):
|
||||||
|
break oneAttribute
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
type
|
||||||
|
PeerFoundCallback* = proc(pa: PeerAttributes) {.raises: [Defect], gcsafe.}
|
||||||
|
|
||||||
|
DiscoveryInterface* = ref object of RootObj
|
||||||
|
onPeerFound*: PeerFoundCallback
|
||||||
|
toAdvertise*: PeerAttributes
|
||||||
|
advertisementUpdated*: AsyncEvent
|
||||||
|
advertiseLoop*: Future[void]
|
||||||
|
|
||||||
|
method request*(self: DiscoveryInterface, pa: PeerAttributes) {.async, base.} =
|
||||||
|
doAssert(false, "Not implemented!")
|
||||||
|
|
||||||
|
method advertise*(self: DiscoveryInterface) {.async, base.} =
|
||||||
|
doAssert(false, "Not implemented!")
|
||||||
|
|
||||||
|
type
|
||||||
|
DiscoveryError* = object of LPError
|
||||||
|
|
||||||
|
DiscoveryQuery* = ref object
|
||||||
|
attr: PeerAttributes
|
||||||
|
peers: AsyncQueue[PeerAttributes]
|
||||||
|
futs: seq[Future[void]]
|
||||||
|
|
||||||
|
DiscoveryManager* = ref object
|
||||||
|
interfaces: seq[DiscoveryInterface]
|
||||||
|
queries: seq[DiscoveryQuery]
|
||||||
|
|
||||||
|
proc add*(dm: DiscoveryManager, di: DiscoveryInterface) =
|
||||||
|
dm.interfaces &= di
|
||||||
|
|
||||||
|
di.onPeerFound = proc (pa: PeerAttributes) =
|
||||||
|
for query in dm.queries:
|
||||||
|
if query.attr.match(pa):
|
||||||
|
try:
|
||||||
|
query.peers.putNoWait(pa)
|
||||||
|
except AsyncQueueFullError as exc:
|
||||||
|
debug "Cannot push discovered peer to queue"
|
||||||
|
|
||||||
|
proc request*(dm: DiscoveryManager, pa: PeerAttributes): DiscoveryQuery =
|
||||||
|
var query = DiscoveryQuery(attr: pa, peers: newAsyncQueue[PeerAttributes]())
|
||||||
|
for i in dm.interfaces:
|
||||||
|
query.futs.add(i.request(pa))
|
||||||
|
dm.queries.add(query)
|
||||||
|
dm.queries.keepItIf(it.futs.anyIt(not it.finished()))
|
||||||
|
return query
|
||||||
|
|
||||||
|
proc request*[T](dm: DiscoveryManager, value: T): DiscoveryQuery =
|
||||||
|
var pa: PeerAttributes
|
||||||
|
pa.add(value)
|
||||||
|
return dm.request(pa)
|
||||||
|
|
||||||
|
proc advertise*(dm: DiscoveryManager, pa: PeerAttributes) =
|
||||||
|
for i in dm.interfaces:
|
||||||
|
i.toAdvertise = pa
|
||||||
|
if i.advertiseLoop.isNil:
|
||||||
|
i.advertisementUpdated = newAsyncEvent()
|
||||||
|
i.advertiseLoop = i.advertise()
|
||||||
|
else:
|
||||||
|
i.advertisementUpdated.fire()
|
||||||
|
|
||||||
|
proc advertise*[T](dm: DiscoveryManager, value: T) =
|
||||||
|
var pa: PeerAttributes
|
||||||
|
pa.add(value)
|
||||||
|
dm.advertise(pa)
|
||||||
|
|
||||||
|
proc stop*(query: DiscoveryQuery) =
|
||||||
|
for r in query.futs:
|
||||||
|
if not r.finished(): r.cancel()
|
||||||
|
|
||||||
|
proc stop*(dm: DiscoveryManager) =
|
||||||
|
for q in dm.queries:
|
||||||
|
q.stop()
|
||||||
|
for i in dm.interfaces:
|
||||||
|
if isNil(i.advertiseLoop): continue
|
||||||
|
i.advertiseLoop.cancel()
|
||||||
|
|
||||||
|
proc getPeer*(query: DiscoveryQuery): Future[PeerAttributes] {.async.} =
|
||||||
|
let getter = query.peers.popFirst()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await getter or allFinished(query.futs)
|
||||||
|
except CancelledError as exc:
|
||||||
|
getter.cancel()
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
if not finished(getter):
|
||||||
|
# discovery loops only finish when they don't handle the query
|
||||||
|
raise newException(DiscoveryError, "Unable to find any peer matching this request")
|
||||||
|
return await getter
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Nim-LibP2P
|
||||||
|
# Copyright (c) 2022 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.
|
||||||
|
|
||||||
|
when (NimMajor, NimMinor) < (1, 4):
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
else:
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
import sequtils
|
||||||
|
import chronos
|
||||||
|
import ./discoverymngr,
|
||||||
|
../protocols/rendezvous,
|
||||||
|
../peerid
|
||||||
|
|
||||||
|
type
|
||||||
|
RendezVousInterface* = ref object of DiscoveryInterface
|
||||||
|
rdv*: RendezVous
|
||||||
|
timeToRequest: Duration
|
||||||
|
timeToAdvertise: Duration
|
||||||
|
|
||||||
|
RdvNamespace* = distinct string
|
||||||
|
|
||||||
|
proc `==`*(a, b: RdvNamespace): bool {.borrow.}
|
||||||
|
|
||||||
|
method request*(self: RendezVousInterface, pa: PeerAttributes) {.async.} =
|
||||||
|
var namespace = ""
|
||||||
|
for attr in pa:
|
||||||
|
if attr.ofType(RdvNamespace):
|
||||||
|
namespace = string attr.to(RdvNamespace)
|
||||||
|
elif attr.ofType(DiscoveryService):
|
||||||
|
namespace = string attr.to(DiscoveryService)
|
||||||
|
elif attr.ofType(PeerId):
|
||||||
|
namespace = $attr.to(PeerId)
|
||||||
|
else:
|
||||||
|
# unhandled type
|
||||||
|
return
|
||||||
|
while true:
|
||||||
|
for pr in await self.rdv.request(namespace):
|
||||||
|
var peer: PeerAttributes
|
||||||
|
peer.add(pr.peerId)
|
||||||
|
for address in pr.addresses:
|
||||||
|
peer.add(address.address)
|
||||||
|
|
||||||
|
peer.add(DiscoveryService(namespace))
|
||||||
|
peer.add(RdvNamespace(namespace))
|
||||||
|
self.onPeerFound(peer)
|
||||||
|
|
||||||
|
await sleepAsync(self.timeToRequest)
|
||||||
|
|
||||||
|
method advertise*(self: RendezVousInterface) {.async.} =
|
||||||
|
while true:
|
||||||
|
var toAdvertise: seq[string]
|
||||||
|
for attr in self.toAdvertise:
|
||||||
|
if attr.ofType(RdvNamespace):
|
||||||
|
toAdvertise.add string attr.to(RdvNamespace)
|
||||||
|
elif attr.ofType(DiscoveryService):
|
||||||
|
toAdvertise.add string attr.to(DiscoveryService)
|
||||||
|
elif attr.ofType(PeerId):
|
||||||
|
toAdvertise.add $attr.to(PeerId)
|
||||||
|
|
||||||
|
self.advertisementUpdated.clear()
|
||||||
|
for toAdv in toAdvertise:
|
||||||
|
await self.rdv.advertise(toAdv, self.timeToAdvertise)
|
||||||
|
|
||||||
|
await sleepAsync(self.timeToAdvertise) or self.advertisementUpdated.wait()
|
||||||
|
|
||||||
|
proc new*(T: typedesc[RendezVousInterface],
|
||||||
|
rdv: RendezVous,
|
||||||
|
ttr: Duration = 1.minutes,
|
||||||
|
tta: Duration = MinimumDuration): RendezVousInterface =
|
||||||
|
T(rdv: rdv, timeToRequest: ttr, timeToAdvertise: tta)
|
|
@ -32,7 +32,7 @@ logScope:
|
||||||
|
|
||||||
const
|
const
|
||||||
RendezVousCodec* = "/rendezvous/1.0.0"
|
RendezVousCodec* = "/rendezvous/1.0.0"
|
||||||
MinimumDuration = 2.hours
|
MinimumDuration* = 2.hours
|
||||||
MaximumDuration = 72.hours
|
MaximumDuration = 72.hours
|
||||||
MinimumTTL = MinimumDuration.seconds.uint64
|
MinimumTTL = MinimumDuration.seconds.uint64
|
||||||
MaximumTTL = MaximumDuration.seconds.uint64
|
MaximumTTL = MaximumDuration.seconds.uint64
|
||||||
|
@ -284,10 +284,6 @@ type
|
||||||
peerId: PeerId
|
peerId: PeerId
|
||||||
data: Register
|
data: Register
|
||||||
|
|
||||||
RegisteredSeq = object
|
|
||||||
s: seq[RegisteredData]
|
|
||||||
offset: uint64
|
|
||||||
|
|
||||||
RendezVous* = ref object of LPProtocol
|
RendezVous* = ref object of LPProtocol
|
||||||
# Registered needs to be an offsetted sequence
|
# Registered needs to be an offsetted sequence
|
||||||
# because we need stable index for the cookies.
|
# because we need stable index for the cookies.
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
{.used.}
|
||||||
|
|
||||||
|
import options, chronos, sets
|
||||||
|
import stew/byteutils
|
||||||
|
import ../libp2p/[protocols/rendezvous,
|
||||||
|
switch,
|
||||||
|
builders,
|
||||||
|
discovery/discoverymngr,
|
||||||
|
discovery/rendezvousinterface,]
|
||||||
|
import ./helpers
|
||||||
|
|
||||||
|
proc createSwitch(rdv: RendezVous = RendezVous.new()): Switch =
|
||||||
|
SwitchBuilder.new()
|
||||||
|
.withRng(newRng())
|
||||||
|
.withAddresses(@[ MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet() ])
|
||||||
|
.withTcpTransport()
|
||||||
|
.withMplex()
|
||||||
|
.withNoise()
|
||||||
|
.withRendezVous(rdv)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
suite "Discovery":
|
||||||
|
teardown:
|
||||||
|
checkTrackers()
|
||||||
|
asyncTest "RendezVous test":
|
||||||
|
let
|
||||||
|
rdvA = RendezVous.new()
|
||||||
|
rdvB = RendezVous.new()
|
||||||
|
clientA = createSwitch(rdvA)
|
||||||
|
clientB = createSwitch(rdvB)
|
||||||
|
remoteNode = createSwitch()
|
||||||
|
dmA = DiscoveryManager()
|
||||||
|
dmB = DiscoveryManager()
|
||||||
|
dmA.add(RendezVousInterface.new(rdvA, ttr = 500.milliseconds))
|
||||||
|
dmB.add(RendezVousInterface.new(rdvB))
|
||||||
|
await allFutures(clientA.start(), clientB.start(), remoteNode.start())
|
||||||
|
|
||||||
|
await clientB.connect(remoteNode.peerInfo.peerId, remoteNode.peerInfo.addrs)
|
||||||
|
await clientA.connect(remoteNode.peerInfo.peerId, remoteNode.peerInfo.addrs)
|
||||||
|
|
||||||
|
dmB.advertise(RdvNamespace("foo"))
|
||||||
|
|
||||||
|
let
|
||||||
|
query = dmA.request(RdvNamespace("foo"))
|
||||||
|
res = await query.getPeer()
|
||||||
|
check:
|
||||||
|
res{PeerId}.get() == clientB.peerInfo.peerId
|
||||||
|
res[PeerId] == clientB.peerInfo.peerId
|
||||||
|
res.getAll(PeerId) == @[clientB.peerInfo.peerId]
|
||||||
|
toHashSet(res.getAll(MultiAddress)) == toHashSet(clientB.peerInfo.addrs)
|
||||||
|
await allFutures(clientA.stop(), clientB.stop(), remoteNode.stop())
|
|
@ -38,5 +38,6 @@ import testtcptransport,
|
||||||
testrelayv1,
|
testrelayv1,
|
||||||
testrelayv2,
|
testrelayv2,
|
||||||
testrendezvous,
|
testrendezvous,
|
||||||
|
testdiscovery,
|
||||||
testyamux,
|
testyamux,
|
||||||
testautonat
|
testautonat
|
||||||
|
|
Loading…
Reference in New Issue