Prem Chaitanya Prathi d8bbef0c5b
feat(mix): bump libp2p stack to v2.0.0 + adopt stateless RLN spam protection
Combines five dep-and-build changes that all flow from the libp2p v2.0.0
upgrade and the move to the extracted libp2p_mix / mix-rln plugin stack:

waku.nimble:
  * libp2p: ff8d51857 -> c43199378 (release/v2.0.0 tip; sha-pinned until
    vacp2p cuts a v2.0.0 tag).
  * Drop the bare `zlib < 0.2` cap — no longer needed by the upgraded
    libp2p.
  * websock: bare ">= 0.4.0" — replaces the d4cd68b URL+SHA workaround
    that pinned through a libp2p commit-specific websock SHA.
  * nim-json-rpc: switch to chaitanyaprem/nim-json-rpc#f05fad25 — relaxes
    websock cap to allow >=0.4.0. TODO: revert to status-im/nim-json-rpc
    once status-im/nim-json-rpc#277 merges and a tag is cut.
  * lsquic: bare ">= 0.4.1" (drops URL form).
  * Add mix-rln-spam-protection-plugin pin (23b278b4) and nim-libp2p-mix
    pin (50c4ab4f — PR #14 HEAD); the plugin pins the same libp2p_mix
    SHA so the diamond dep collapses to a single source.

waku/factory/waku.nim:
  * Explicit HPService.setup(switch) / AutonatService.setup(switch)
    calls. libp2p v2.0.0's Service lifecycle refactor (libp2p#2462)
    removed switch.start's auto-setup loop, so any caller that assigns
    directly to switch.services (we do) is responsible for calling
    setup() themselves. Without it, AutonatService.addressMapper stays
    nil and peerInfo.expandAddrs SIGSEGVs during start(). Wrapped in
    try/except for ServiceSetupError so a setup failure surfaces as a
    logged error rather than a crash.

Build / scripts:
  * scripts/build_rln_mix.sh removed and Makefile simplified — librln
    is now a single shared archive built from zerokit's `stateless`
    features (no separate librln_mix archive).
  * simulations/mixnet/build_setup.sh + setup_credentials.nim updated
    to use librln_v2.0.2.a directly and run RLN keystore setup before
    nodes start.

Validated:
  * Cold local-cache nimble setup --localdeps -y.
  * wakunode2 and chat2mix link cleanly.
  * Mixnet roundtrip sim: [PASS] bob received message from alice.
  * RLN proof generation + verification on every in-path mix node:
    5 gen_called == 5 verified, 0 SPAM_PROOF_* errors.
2026-06-04 16:54:44 +05:30

146 lines
4.4 KiB
Nim

{.push raises: [].}
import
std/[options, sequtils, tables],
results,
chronos,
chronicles,
libp2p/protocols/rendezvous,
libp2p/crypto/curve25519,
libp2p/switch
import metrics except collect
import
waku/node/peer_manager,
waku/waku_core/peers,
waku/waku_core/codecs,
./common,
./waku_peer_record
logScope:
topics = "waku rendezvous client"
declarePublicCounter rendezvousPeerFoundTotal,
"total number of peers found via rendezvous"
type WakuRendezVousClient* = ref object
switch: Switch
peerManager: PeerManager
clusterId: uint16
requestInterval: timer.Duration
periodicRequestFut: Future[void]
# Internal rendezvous instance for making requests
rdv: GenericRendezVous[WakuPeerRecord]
const MaxSimultanesousAdvertisements = 5
const RendezVousLookupInterval = 10.seconds
proc requestAll*(
self: WakuRendezVousClient
): Future[Result[void, string]] {.async: (raises: []).} =
trace "waku rendezvous client requests started"
let namespace = computeMixNamespace(self.clusterId)
# Get a random WakuRDV peer
let rpi = self.peerManager.selectPeer(WakuRendezVousCodec).valueOr:
return err("could not get a peer supporting WakuRendezVousCodec")
var records: seq[WakuPeerRecord]
try:
# Use the libp2p rendezvous request method
records = await self.rdv.request(
Opt.some(namespace), Opt.some(PeersRequestedCount), Opt.some(@[rpi.peerId])
)
except CatchableError as e:
return err("rendezvous request failed: " & e.msg)
trace "waku rendezvous client request got peers", count = records.len
for record in records:
if not self.switch.peerStore.peerExists(record.peerId):
rendezvousPeerFoundTotal.inc()
if record.mixKey.len == 0 or record.peerId == self.switch.peerInfo.peerId:
continue
trace "adding peer from rendezvous",
peerId = record.peerId, addresses = $record.addresses, mixKey = record.mixKey
let rInfo = RemotePeerInfo.init(
record.peerId,
record.addresses,
mixPubKey = some(intoCurve25519Key(fromHex(record.mixKey))),
)
self.peerManager.addPeer(rInfo)
trace "waku rendezvous client request finished"
return ok()
proc periodicRequests(self: WakuRendezVousClient) {.async.} =
info "waku rendezvous periodic requests started", interval = self.requestInterval
# infinite loop
while true:
await sleepAsync(self.requestInterval)
(await self.requestAll()).isOkOr:
error "waku rendezvous requests failed", error = error
# Exponential backoff
#[ TODO: Reevaluate for mix, maybe be aggresive in the start until a sizeable pool is built and then backoff
self.requestInterval += self.requestInterval
if self.requestInterval >= 1.days:
break ]#
proc new*(
T: type WakuRendezVousClient,
switch: Switch,
peerManager: PeerManager,
clusterId: uint16,
): Result[T, string] {.raises: [].} =
# Create a minimal GenericRendezVous instance for client-side requests
# We don't need the full server functionality, just the request method
let rng = newRng()
let rdv = GenericRendezVous[WakuPeerRecord](
switch: switch,
rng: rng,
sema: newAsyncSemaphore(MaxSimultanesousAdvertisements),
# libp2p 1.15.3 moved minDuration/maxDuration/minTTL/maxTTL onto
# GenericRendezVous.config (RendezVousConfig).
config: RendezVousConfig(
minDuration: rendezvous.MinimumAcceptedDuration,
maxDuration: rendezvous.MaximumDuration,
minTTL: rendezvous.MinimumAcceptedDuration.seconds.uint64,
maxTTL: rendezvous.MaximumDuration.seconds.uint64,
),
peers: @[], # Will be populated from selectPeer calls
cookiesSaved: initTable[PeerId, Table[string, seq[byte]]](),
peerRecordValidator: checkWakuPeerRecord,
)
# Set codec separately as it's inherited from LPProtocol
rdv.codec = WakuRendezVousCodec
let client = T(
switch: switch,
peerManager: peerManager,
clusterId: clusterId,
requestInterval: RendezVousLookupInterval,
rdv: rdv,
)
info "waku rendezvous client initialized", clusterId = clusterId
return ok(client)
proc start*(self: WakuRendezVousClient) {.async: (raises: []).} =
self.periodicRequestFut = self.periodicRequests()
info "waku rendezvous client started"
proc stopWait*(self: WakuRendezVousClient) {.async: (raises: []).} =
if not self.periodicRequestFut.isNil():
await self.periodicRequestFut.cancelAndWait()
info "waku rendezvous client stopped"