mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-01-02 14:03:06 +00:00
306 lines
7.8 KiB
Nim
306 lines
7.8 KiB
Nim
{.push raises: [].}
|
|
|
|
import
|
|
std/[sugar, options],
|
|
results,
|
|
chronos,
|
|
chronicles,
|
|
metrics,
|
|
libp2p/protocols/rendezvous,
|
|
libp2p/switch,
|
|
libp2p/utility
|
|
|
|
import
|
|
../node/peer_manager,
|
|
../common/callbacks,
|
|
../waku_enr/capabilities,
|
|
../waku_core/peers,
|
|
../waku_core/topics,
|
|
../waku_core/topics/pubsub_topic,
|
|
./common
|
|
|
|
logScope:
|
|
topics = "waku rendezvous"
|
|
|
|
declarePublicCounter rendezvousPeerFoundTotal,
|
|
"total number of peers found via rendezvous"
|
|
|
|
type WakuRendezVous* = ref object
|
|
rendezvous: Rendezvous
|
|
peerManager: PeerManager
|
|
clusterId: uint16
|
|
getShards: GetShards
|
|
getCapabilities: GetCapabilities
|
|
|
|
registrationInterval: timer.Duration
|
|
periodicRegistrationFut: Future[void]
|
|
|
|
requestInterval: timer.Duration
|
|
periodicRequestFut: Future[void]
|
|
|
|
proc batchAdvertise*(
|
|
self: WakuRendezVous,
|
|
namespace: string,
|
|
ttl: Duration = DefaultRegistrationTTL,
|
|
peers: seq[PeerId],
|
|
): Future[Result[void, string]] {.async: (raises: []).} =
|
|
## Register with all rendezvous peers under a namespace
|
|
|
|
# rendezvous.advertise expects already opened connections
|
|
# must dial first
|
|
var futs = collect(newSeq):
|
|
for peerId in peers:
|
|
self.peerManager.dialPeer(peerId, RendezVousCodec)
|
|
|
|
let dialCatch = catch:
|
|
await allFinished(futs)
|
|
|
|
futs = dialCatch.valueOr:
|
|
return err("batchAdvertise: " & error.msg)
|
|
|
|
let conns = collect(newSeq):
|
|
for fut in futs:
|
|
let catchable = catch:
|
|
fut.read()
|
|
|
|
catchable.isOkOr:
|
|
warn "a rendezvous dial failed", cause = error.msg
|
|
continue
|
|
|
|
let connOpt = catchable.get()
|
|
|
|
let conn = connOpt.valueOr:
|
|
continue
|
|
|
|
conn
|
|
|
|
let advertCatch = catch:
|
|
await self.rendezvous.advertise(namespace, Opt.some(ttl))
|
|
|
|
for conn in conns:
|
|
await conn.close()
|
|
|
|
advertCatch.isOkOr:
|
|
return err("batchAdvertise: " & error.msg)
|
|
|
|
return ok()
|
|
|
|
proc batchRequest*(
|
|
self: WakuRendezVous,
|
|
namespace: string,
|
|
count: int = DiscoverLimit,
|
|
peers: seq[PeerId],
|
|
): Future[Result[seq[PeerRecord], string]] {.async: (raises: []).} =
|
|
## Request all records from all rendezvous peers matching a namespace
|
|
|
|
# rendezvous.request expects already opened connections
|
|
# must dial first
|
|
var futs = collect(newSeq):
|
|
for peerId in peers:
|
|
self.peerManager.dialPeer(peerId, RendezVousCodec)
|
|
|
|
let dialCatch = catch:
|
|
await allFinished(futs)
|
|
|
|
futs = dialCatch.valueOr:
|
|
return err("batchRequest: " & error.msg)
|
|
|
|
let conns = collect(newSeq):
|
|
for fut in futs:
|
|
let catchable = catch:
|
|
fut.read()
|
|
|
|
catchable.isOkOr:
|
|
warn "a rendezvous dial failed", cause = error.msg
|
|
continue
|
|
|
|
let connOpt = catchable.get()
|
|
|
|
let conn = connOpt.valueOr:
|
|
continue
|
|
|
|
conn
|
|
|
|
let reqCatch = catch:
|
|
await self.rendezvous.request(Opt.some(namespace), Opt.some(count), Opt.some(peers))
|
|
|
|
for conn in conns:
|
|
await conn.close()
|
|
|
|
reqCatch.isOkOr:
|
|
return err("batchRequest: " & error.msg)
|
|
|
|
return ok(reqCatch.get())
|
|
|
|
proc advertiseAll(
|
|
self: WakuRendezVous
|
|
): Future[Result[void, string]] {.async: (raises: []).} =
|
|
info "waku rendezvous advertisements started"
|
|
|
|
let shards = self.getShards()
|
|
|
|
let futs = collect(newSeq):
|
|
for shardId in shards:
|
|
# Get a random RDV peer for that shard
|
|
|
|
let pubsub =
|
|
toPubsubTopic(RelayShard(clusterId: self.clusterId, shardId: shardId))
|
|
|
|
let rpi = self.peerManager.selectPeer(RendezVousCodec, some(pubsub)).valueOr:
|
|
continue
|
|
|
|
let namespace = computeNamespace(self.clusterId, shardId)
|
|
|
|
# Advertise yourself on that peer
|
|
self.batchAdvertise(namespace, DefaultRegistrationTTL, @[rpi.peerId])
|
|
|
|
if futs.len < 1:
|
|
return err("could not get a peer supporting RendezVousCodec")
|
|
|
|
let catchable = catch:
|
|
await allFinished(futs)
|
|
|
|
catchable.isOkOr:
|
|
return err(error.msg)
|
|
|
|
for fut in catchable.get():
|
|
if fut.failed():
|
|
warn "a rendezvous advertisement failed", cause = fut.error.msg
|
|
|
|
info "waku rendezvous advertisements finished"
|
|
|
|
return ok()
|
|
|
|
proc initialRequestAll*(
|
|
self: WakuRendezVous
|
|
): Future[Result[void, string]] {.async: (raises: []).} =
|
|
info "waku rendezvous initial requests started"
|
|
|
|
let shards = self.getShards()
|
|
|
|
let futs = collect(newSeq):
|
|
for shardId in shards:
|
|
let namespace = computeNamespace(self.clusterId, shardId)
|
|
# Get a random RDV peer for that shard
|
|
let rpi = self.peerManager.selectPeer(
|
|
RendezVousCodec,
|
|
some(toPubsubTopic(RelayShard(clusterId: self.clusterId, shardId: shardId))),
|
|
).valueOr:
|
|
continue
|
|
|
|
# Ask for peer records for that shard
|
|
self.batchRequest(namespace, PeersRequestedCount, @[rpi.peerId])
|
|
|
|
if futs.len < 1:
|
|
return err("could not get a peer supporting RendezVousCodec")
|
|
|
|
let catchable = catch:
|
|
await allFinished(futs)
|
|
|
|
catchable.isOkOr:
|
|
return err(error.msg)
|
|
|
|
for fut in catchable.get():
|
|
if fut.failed():
|
|
warn "a rendezvous request failed", cause = fut.error.msg
|
|
elif fut.finished():
|
|
let res = fut.value()
|
|
|
|
let records = res.valueOr:
|
|
warn "a rendezvous request failed", cause = $error
|
|
continue
|
|
|
|
for record in records:
|
|
rendezvousPeerFoundTotal.inc()
|
|
self.peerManager.addPeer(record)
|
|
|
|
info "waku rendezvous initial request finished"
|
|
|
|
return ok()
|
|
|
|
proc periodicRegistration(self: WakuRendezVous) {.async.} =
|
|
info "waku rendezvous periodic registration started",
|
|
interval = self.registrationInterval
|
|
|
|
# infinite loop
|
|
while true:
|
|
await sleepAsync(self.registrationInterval)
|
|
|
|
(await self.advertiseAll()).isOkOr:
|
|
info "waku rendezvous advertisements failed", error = error
|
|
|
|
if self.registrationInterval > MaxRegistrationInterval:
|
|
self.registrationInterval = MaxRegistrationInterval
|
|
else:
|
|
self.registrationInterval += self.registrationInterval
|
|
|
|
# Back to normal interval if no errors
|
|
self.registrationInterval = DefaultRegistrationInterval
|
|
|
|
proc periodicRequests(self: WakuRendezVous) {.async.} =
|
|
info "waku rendezvous periodic requests started", interval = self.requestInterval
|
|
|
|
# infinite loop
|
|
while true:
|
|
(await self.initialRequestAll()).isOkOr:
|
|
error "waku rendezvous requests failed", error = error
|
|
|
|
await sleepAsync(self.requestInterval)
|
|
|
|
# Exponential backoff
|
|
self.requestInterval += self.requestInterval
|
|
|
|
if self.requestInterval >= 1.days:
|
|
break
|
|
|
|
proc new*(
|
|
T: type WakuRendezVous,
|
|
switch: Switch,
|
|
peerManager: PeerManager,
|
|
clusterId: uint16,
|
|
getShards: GetShards,
|
|
getCapabilities: GetCapabilities,
|
|
): Result[T, string] {.raises: [].} =
|
|
let rvCatchable = catch:
|
|
RendezVous.new(switch = switch, minDuration = DefaultRegistrationTTL)
|
|
|
|
let rv = rvCatchable.valueOr:
|
|
return err(error.msg)
|
|
|
|
let mountCatchable = catch:
|
|
switch.mount(rv)
|
|
|
|
mountCatchable.isOkOr:
|
|
return err(error.msg)
|
|
|
|
var wrv = WakuRendezVous()
|
|
wrv.rendezvous = rv
|
|
wrv.peerManager = peerManager
|
|
wrv.clusterId = clusterId
|
|
wrv.getShards = getShards
|
|
wrv.getCapabilities = getCapabilities
|
|
wrv.registrationInterval = DefaultRegistrationInterval
|
|
wrv.requestInterval = DefaultRequestsInterval
|
|
|
|
info "waku rendezvous initialized",
|
|
clusterId = clusterId, shards = getShards(), capabilities = getCapabilities()
|
|
|
|
return ok(wrv)
|
|
|
|
proc start*(self: WakuRendezVous) {.async: (raises: []).} =
|
|
# start registering forever
|
|
self.periodicRegistrationFut = self.periodicRegistration()
|
|
|
|
self.periodicRequestFut = self.periodicRequests()
|
|
|
|
info "waku rendezvous discovery started"
|
|
|
|
proc stopWait*(self: WakuRendezVous) {.async: (raises: []).} =
|
|
if not self.periodicRegistrationFut.isNil():
|
|
await self.periodicRegistrationFut.cancelAndWait()
|
|
|
|
if not self.periodicRequestFut.isNil():
|
|
await self.periodicRequestFut.cancelAndWait()
|
|
|
|
info "waku rendezvous discovery stopped"
|