Prem Chaitanya Prathi bd65c116bb
feat(mix): DoS protection + libp2p v2.0.0 + stateless RLN + tests (rebased onto #3935)
Squash of 13 commits from feat/mix-dos-protection-libp2p-v2.0.0 onto the
logos_delivery/ folder-restructure base from #3935 (build-messaging-folder).

Original commit history (squashed):
- d8e6dcef feat(mix): integrate mix protocol with extended kademlia + RLN spam protection
- fb72f18d refactor(mix): split DoS-protection self-registration into background retry
- d8bbef0c feat(mix): bump libp2p stack to v2.0.0 + adopt stateless RLN spam protection
- 2f24448a fix(tests): use HmacDrbgContext.new() instead of crypto.newRng()
- 5a21455c fix(ci): regen nimble.lock for v2.0.0 + disambiguate rng in wakucore
- 03ef02a2 fix(tests): wrap HmacDrbgContext via newBearSslRng for libp2p v2.0.0
- 167ab1df fix(nix): regenerate deps.nix from updated nimble.lock
- 97a27222 fix(tests): wrap or pass Rng correctly for 3-arg PrivateKey.random
- 5561fcb5 fix(tests): replace removed newStandardSwitch with SwitchBuilder
- ba39ee4a fix(tests): libp2p v2.0.0 API migrations across test suite
- 328e11df fix: gitignore test binaries + remove accidentally-committed binary
- cc712444 fix(tests): more v2.0.0 API migrations (rng template, PeerId.random, etc.)
- 412d97a9 fix(tests): unblock CI — nph, excise orphan waku_noise, complete v2.0.0 Rng migration

Conflict resolutions (#3935 → ours):
- 11 import-path migrations: waku/X → logos_delivery/waku/X
- waku_node/waku_node/relay.nim: dropped our `registerRelayHandler` proc
  (relocated to subscription_manager.nim by #3935; see cascade fix below)
- factory/builder.nim: combined both sides' new imports (net_config + waku_switch)
- factory/conf_builder/mix_conf_builder.nim: libp2p_mix package (not libp2p/protocols/mix)
- waku_mix/protocol.nim: combined paths + our mix_rln_spam_protection/relay/nimchronos imports
- 3 test files: dropped noise_utils import (replicates noise excision from original PR)
- 2 UA file moves: option_shims.nim and waku_mix_coordination.nim added at new paths

Cascade fixes (#3935 lost our work, restored):
- subscription_manager.nim: added `mixHandler` to #3935's `registerRelayHandler`,
  and added `waku_mix` to its imports. Without this, mix messages were silently
  dropped from the relay handler chain.
- config.nims: option_shims auto-import path migrated to logos_delivery/...

Validation:
- nph check on all 82 staged .nim files: clean (0 reformats needed)
- wakunode2 build: exit 0, 38 MB binary
- (sim PASS confirmed in earlier identical-state run: 5/5 mix init,
  5 RLN proofs gen/verify, 0 errors)

Backup tag at original tip: backup/3931-pre-3935-rebase (412d97a9).
2026-06-22 14:20:58 +05:30

212 lines
7.5 KiB
Nim

import logos_delivery/waku/compat/option_valueor
{.push raises: [].}
import
std/[options, net],
chronos,
chronicles,
metrics,
results,
stew/byteutils,
eth/keys,
eth/p2p/discoveryv5/enr,
libp2p/crypto/crypto,
libp2p/protocols/ping,
libp2p/protocols/pubsub/gossipsub,
libp2p/protocols/pubsub/rpc/messages,
libp2p/builders,
libp2p/transports/tcptransport,
libp2p/transports/wstransport,
libp2p/utility,
brokers/broker_context
import
logos_delivery/waku/[
waku_relay,
waku_core,
waku_core/topics/sharding,
waku_filter_v2,
waku_archive,
waku_store_sync,
waku_rln_relay,
waku_mix,
node/waku_node,
node/subscription_manager,
node/peer_manager,
events/message_events,
]
export waku_relay.WakuRelayHandler
logScope:
topics = "waku node relay api"
## Waku relay
proc getTopicOfSubscriptionEvent(
node: WakuNode, subscription: SubscriptionEvent
): Result[(PubsubTopic, Option[ContentTopic]), string] =
case subscription.kind
of ContentSub, ContentUnsub:
if node.wakuAutoSharding.isSome():
let shard = node.wakuAutoSharding.get().getShard((subscription.topic)).valueOr:
return err("Autosharding error: " & error)
return ok(($shard, some(subscription.topic)))
else:
return
err("Static sharding is used, relay subscriptions must specify a pubsub topic")
of PubsubSub, PubsubUnsub:
return ok((subscription.topic, none[ContentTopic]()))
else:
return err("Unsupported subscription type in relay getTopicOfSubscriptionEvent")
proc subscribe*(
node: WakuNode, subscription: SubscriptionEvent, handler: WakuRelayHandler
): Result[void, string] =
## Subscribes to a PubSub or Content topic. Triggers handler when receiving messages on
## this topic. WakuRelayHandler is a method that takes a topic and a Waku message.
## If `handler` is nil, the API call will subscribe to the topic in the relay mesh
## but no app handler will be registered at this time (it can be registered later with
## another call to this proc for the same gossipsub topic).
if isNil(node.wakuRelay):
error "Invalid API call to `subscribe`. WakuRelay not mounted."
return err("Invalid API call to `subscribe`. WakuRelay not mounted.")
let (pubsubTopic, _) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
error "Failed to decode subscription event", error = error
return err("Failed to decode subscription event: " & error)
# strict version
#if contentTopicOp.isSome():
# return
# node.subscriptionManager.subscribe(pubsubTopic, contentTopicOp.get(), handler)
return node.subscriptionManager.subscribeShard(pubsubTopic, handler)
proc unsubscribe*(
node: WakuNode, subscription: SubscriptionEvent
): Result[void, string] =
## Unsubscribes from a specific PubSub or Content topic.
## This will both unsubscribe from the relay mesh and remove the app handler, if any.
## NOTE: This works because using MAPI and Kernel API at the same time is unsupported.
if isNil(node.wakuRelay):
error "Invalid API call to `unsubscribe`. WakuRelay not mounted."
return err("Invalid API call to `unsubscribe`. WakuRelay not mounted.")
let (pubsubTopic, _) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
error "Failed to decode unsubscribe event", error = error
return err("Failed to decode unsubscribe event: " & error)
# strict version
#if contentTopicOp.isSome():
# return node.subscriptionManager.unsubscribe(pubsubTopic, contentTopicOp.get())
return node.subscriptionManager.unsubscribeAll(pubsubTopic)
proc isSubscribed*(
node: WakuNode, subscription: SubscriptionEvent
): Result[bool, string] =
if node.wakuRelay.isNil():
error "Invalid API call to `isSubscribed`. WakuRelay not mounted."
return err("Invalid API call to `isSubscribed`. WakuRelay not mounted.")
let (pubsubTopic, contentTopicOp) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
error "Failed to decode subscription event", error = error
return err("Failed to decode subscription event: " & error)
return ok(node.wakuRelay.isSubscribed(pubsubTopic))
proc publish*(
node: WakuNode, pubsubTopicOp: Option[PubsubTopic], message: WakuMessage
): Future[Result[int, string]] {.async, gcsafe.} =
## Publish a `WakuMessage`. Pubsub topic contains; none, a named or static shard.
## `WakuMessage` should contain a `contentTopic` field for light node functionality.
## It is also used to determine the shard.
if node.wakuRelay.isNil():
let msg =
"Invalid API call to `publish`. WakuRelay not mounted. Try `lightpush` instead."
error "publish error", err = msg
# TODO: Improve error handling
return err(msg)
let pubsubTopic = pubsubTopicOp.valueOr:
if node.wakuAutoSharding.isNone():
return err("Pubsub topic must be specified when static sharding is enabled.")
node.wakuAutoSharding.get().getShard(message.contentTopic).valueOr:
let msg = "Autosharding error: " & error
return err(msg)
let numPeers = (await node.wakuRelay.publish(pubsubTopic, message)).valueOr:
warn "waku.relay did not publish", error = error
# Todo: If NoPeersToPublish, we might want to return ok(0) instead!!!
return err("publish failed in relay: " & $error)
notice "waku.relay published",
peerId = node.peerId,
pubsubTopic = pubsubTopic,
msg_hash = pubsubTopic.computeMessageHash(message).to0xHex(),
publishTime = getNowInNanosecondTime(),
numPeers = numPeers
# TODO: investigate if we can return error in case numPeers is 0
ok(numPeers)
proc mountRelay*(
node: WakuNode,
peerExchangeHandler = none(RoutingRecordsHandler),
maxMessageSize = int(DefaultMaxWakuMessageSize),
): Future[Result[void, string]] {.async.} =
if not node.wakuRelay.isNil():
error "wakuRelay already mounted, skipping"
return err("wakuRelay already mounted, skipping")
## The default relay topics is the union of all configured topics plus default PubsubTopic(s)
info "mounting relay protocol"
node.wakuRelay = WakuRelay.new(node.switch, maxMessageSize).valueOr:
error "failed mounting relay protocol", error = error
return err("failed mounting relay protocol: " & error)
## Add peer exchange handler
if peerExchangeHandler.isSome():
node.wakuRelay.parameters.enablePX = true
# Feature flag for peer exchange in nim-libp2p
node.wakuRelay.routingRecordsHandler.add(peerExchangeHandler.get())
if node.started:
await node.wakuRelay.start()
await node.reconnectRelayPeers()
node.switch.mount(node.wakuRelay, protocolMatcher(WakuRelayCodec))
info "relay mounted successfully"
return ok()
## Waku RLN Relay
proc mountRlnRelay*(
node: WakuNode,
rlnConf: WakuRlnConfig,
spamHandler = none(SpamHandler),
registrationHandler = none(RegistrationHandler),
) {.async.} =
info "mounting rln relay"
if node.wakuRelay.isNil():
raise newException(
CatchableError, "WakuRelay protocol is not mounted, cannot mount WakuRlnRelay"
)
let rlnRelay = (await WakuRlnRelay.new(rlnConf, registrationHandler)).valueOr:
raise newException(CatchableError, "failed to mount WakuRlnRelay: " & error)
if (rlnConf.userMessageLimit > rlnRelay.groupManager.rlnRelayMaxMessageLimit):
error "rln-relay-user-message-limit can't exceed the MAX_MESSAGE_LIMIT in the rln contract"
let validator = generateRlnValidator(rlnRelay, spamHandler)
# register rln validator as default validator
info "Registering RLN validator"
node.wakuRelay.addValidator(validator, "RLN validation failed")
node.wakuRlnRelay = rlnRelay