From 92ea373520cf941ae6fb7e43b76215d49b30dd5c Mon Sep 17 00:00:00 2001 From: Prem Chaitanya Prathi Date: Wed, 20 May 2026 18:29:27 +0530 Subject: [PATCH 1/2] feat(mix): cover traffic with constant rate - Integrate ConstantRateCoverTraffic from libp2p mix module with default totalSlots = userMessageLimit (or 2) and 10s epoch - Add --mix-user-message-limit and --mix-disable-spam-protection CLI flags with corresponding MixConfBuilder accessors and MixConf fields - Wrap mixRlnSpamProtection construction so it is skipped when spam protection is disabled, with a nil guard in setupSpamProtectionCallbacks - Add waku/common/option_shims.nim restoring valueOr/withValue templates for std/options (removed upstream by results), and import it across modules that relied on the old behavior - Sink chat2mix logs to textlines (stdout) instead of textlines[file] to work around a chronicles compile-time macro-eval bug under Nim 2.2.4 - Rename ExtendedKademliaDiscoveryParams -> ExtendedServiceDiscoveryParams to match the kad_disco -> service_discovery rename in nim-libp2p - Bump nim-libp2p to e1bbda4f6 (PR #2243 "cover traffic with constant rate") and mix-rln-spam-protection-plugin to 153d0c0 (PR #5 cover traffic epoch change support); both pre-libp2p_mix-extraction - Add simulations/mixnet/check_cover_traffic.sh for monitoring mix_cover_* / mix_slot_* metrics, plus per-node cover-traffic configs --- apps/chat2mix/chat2mix.nim | 6 +- simulations/mixnet/README.md | 33 +++++++++++ simulations/mixnet/check_cover_traffic.sh | 18 ++++++ simulations/mixnet/config.toml | 3 + simulations/mixnet/config1.toml | 3 + simulations/mixnet/config2.toml | 3 + simulations/mixnet/config3.toml | 3 + simulations/mixnet/config4.toml | 3 + simulations/mixnet/setup_credentials.nim | 4 +- tests/test_wakunode.nim | 2 +- tests/waku_relay/test_wakunode_relay.nim | 2 +- tests/waku_store/test_wakunode_store.nim | 2 +- tools/confutils/cli_args.nim | 15 +++++ waku.nimble | 10 +++- waku/common/option_shims.nim | 28 ++++++++++ waku/common/rate_limit/request_limiter.nim | 1 + waku/discovery/waku_discv5.nim | 4 +- waku/discovery/waku_kademlia.nim | 22 ++++---- .../factory/conf_builder/mix_conf_builder.nim | 28 +++++++++- waku/factory/node_factory.nim | 9 ++- waku/factory/waku_conf.nim | 2 + .../send_service/lightpush_processor.nim | 6 +- waku/node/kernel_api/lightpush.nim | 1 + waku/node/kernel_api/ping.nim | 2 +- waku/node/kernel_api/relay.nim | 1 + waku/node/peer_manager/peer_manager.nim | 1 + waku/node/peer_manager/waku_peer_store.nim | 1 + waku/node/waku_node.nim | 4 +- waku/rest_api/endpoint/filter/handlers.nim | 1 + .../endpoint/legacy_lightpush/handlers.nim | 1 + waku/rest_api/endpoint/lightpush/handlers.nim | 1 + waku/rest_api/endpoint/store/handlers.nim | 1 + waku/waku_enr/sharding.nim | 2 +- waku/waku_lightpush/client.nim | 1 + waku/waku_lightpush/protocol.nim | 1 + waku/waku_lightpush_legacy/client.nim | 1 + waku/waku_mix/protocol.nim | 56 +++++++++++++------ waku/waku_rendezvous/client.nim | 3 +- waku/waku_rendezvous/protocol.nim | 3 +- waku/waku_store/client.nim | 7 ++- waku/waku_store/resume.nim | 1 + waku/waku_store_sync/reconciliation.nim | 1 + waku/waku_store_sync/transfer.nim | 1 + 43 files changed, 245 insertions(+), 53 deletions(-) create mode 100755 simulations/mixnet/check_cover_traffic.sh create mode 100644 waku/common/option_shims.nim diff --git a/apps/chat2mix/chat2mix.nim b/apps/chat2mix/chat2mix.nim index ea3cbd376f..acff5fd0e6 100644 --- a/apps/chat2mix/chat2mix.nim +++ b/apps/chat2mix/chat2mix.nim @@ -60,7 +60,6 @@ import ../../waku/waku_rln_relay logScope: topics = "chat2 mix" - ######################### ## Mix Spam Protection ## ######################### @@ -160,8 +159,7 @@ proc maintainSpamProtectionSubscription( noFailedSubscribes = 0 await sleepAsync(SubscriptionMaintenance) -const Help = - """ +const Help = """ Commands: /[?|help|connect|nick|exit] help: Prints this help connect: dials a remote peer @@ -576,7 +574,7 @@ proc processInput(rfd: AsyncFD, rng: ref HmacDrbgContext) {.async.} = if kadBootstrapPeers.len > 0: node.wakuKademlia = WakuKademlia.new( node.switch, - ExtendedKademliaDiscoveryParams( + ExtendedServiceDiscoveryParams( bootstrapNodes: kadBootstrapPeers, mixPubKey: some(mixPubKey), advertiseMix: false, diff --git a/simulations/mixnet/README.md b/simulations/mixnet/README.md index 99b0ba50b5..7db1d05799 100644 --- a/simulations/mixnet/README.md +++ b/simulations/mixnet/README.md @@ -24,6 +24,7 @@ The simulation includes: | `run_chat_mix.sh` | Chat app instance 1 | | `run_chat_mix1.sh` | Chat app instance 2 | | `build_setup.sh` | Build and generate RLN credentials | +| `check_cover_traffic.sh` | Monitor cover traffic metrics from all nodes | ## Prerequisites @@ -130,3 +131,35 @@ To exit the chat apps, enter `/exit`: >> /exit quitting... ``` + +## Running Without DoS Protection + +To test cover traffic without RLN spam protection (avoids heavy proof generation compute), the config files include two flags: + +```toml +mix-user-message-limit=2 # slots per epoch (reduce for lighter testing) +mix-disable-spam-protection=true # skip RLN proof generation/verification +``` + +These are already set in `config.toml` through `config4.toml`. To re-enable RLN, set `mix-disable-spam-protection=false` (or remove the line) and ensure credentials are generated via `./build_setup.sh`. + +When running without DoS protection, cover traffic uses an internal epoch timer and does not require RLN credentials or `rln_tree.db`. + +### Monitoring Cover Traffic + +Use the metrics script to verify cover traffic is working: + +```bash +./check_cover_traffic.sh +``` + +Key metrics to look for: +- `mix_cover_emitted_total` — cover messages generated per node (should increase each epoch) +- `mix_cover_received_total` — cover messages received back at origin after 3-hop mix path +- `mix_slots_exhausted_total` — expected when slots per epoch are low + +### Note on Rate Limit and Expected Errors + +The default `mix-user-message-limit=2` (R=2) with path length L=3 yields a fractional cover target of `R/(1+L) = 0.5` packets per epoch. Because this is not an integer, epoch boundary jitter can cause two cover emissions in one epoch, exhausting all slots and leaving none for forwarding. This produces `SLOT_EXHAUSTED` and `SPAM_PROOF_GEN_FAILED` errors at intermediate hops — these are expected with the default config. + +For a clean setup, R should be a multiple of `(1+L) = 4`. Setting `mix-user-message-limit=4` gives exactly 1 cover packet per epoch with 3 slots remaining for forwarding, eliminating these errors. diff --git a/simulations/mixnet/check_cover_traffic.sh b/simulations/mixnet/check_cover_traffic.sh new file mode 100755 index 0000000000..a5763fbfc7 --- /dev/null +++ b/simulations/mixnet/check_cover_traffic.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Check cover traffic metrics from all mix nodes. +# Ports: 8008 + ports-shift (1-5) = 8009-8013 + +echo "=== Cover Traffic Metrics ===" +echo "" + +for i in 1 2 3 4 5; do + port=$((8008 + i)) + echo "--- Node $i (port $port) ---" + metrics=$(curl -s "http://127.0.0.1:$port/metrics" 2>/dev/null) + if [ -z "$metrics" ]; then + echo " (unreachable)" + else + echo "$metrics" | grep -E "mix_cover_|mix_slot_" | grep -v "^#" || echo " (no cover metrics yet)" + fi + echo "" +done diff --git a/simulations/mixnet/config.toml b/simulations/mixnet/config.toml index 5cd1aa9360..fcf3eda39d 100644 --- a/simulations/mixnet/config.toml +++ b/simulations/mixnet/config.toml @@ -7,6 +7,7 @@ lightpush = true max-connections = 150 peer-exchange = false metrics-logging = false +metrics-server = true cluster-id = 2 discv5-discovery = false discv5-udp-port = 9000 @@ -26,3 +27,5 @@ nat = "extip:127.0.0.1" ext-multiaddr = ["/ip4/127.0.0.1/tcp/60001"] ext-multiaddr-only = true ip-colocation-limit=0 +mix-user-message-limit=4 +mix-disable-spam-protection=false diff --git a/simulations/mixnet/config1.toml b/simulations/mixnet/config1.toml index 73cccb8c6c..a65cc2f5a1 100644 --- a/simulations/mixnet/config1.toml +++ b/simulations/mixnet/config1.toml @@ -7,6 +7,7 @@ lightpush = true max-connections = 150 peer-exchange = false metrics-logging = false +metrics-server = true cluster-id = 2 discv5-discovery = false discv5-udp-port = 9001 @@ -27,4 +28,6 @@ nat = "extip:127.0.0.1" ext-multiaddr = ["/ip4/127.0.0.1/tcp/60002"] ext-multiaddr-only = true ip-colocation-limit=0 +mix-user-message-limit=4 +mix-disable-spam-protection=false #staticnode = ["/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o", "/ip4/127.0.0.1/tcp/60003/p2p/16Uiu2HAmTEDHwAziWUSz6ZE23h5vxG2o4Nn7GazhMor4bVuMXTrA","/ip4/127.0.0.1/tcp/60004/p2p/16Uiu2HAmPwRKZajXtfb1Qsv45VVfRZgK3ENdfmnqzSrVm3BczF6f","/ip4/127.0.0.1/tcp/60005/p2p/16Uiu2HAmRhxmCHBYdXt1RibXrjAUNJbduAhzaTHwFCZT4qWnqZAu"] diff --git a/simulations/mixnet/config2.toml b/simulations/mixnet/config2.toml index 3acd2bf8a2..b37bb9759e 100644 --- a/simulations/mixnet/config2.toml +++ b/simulations/mixnet/config2.toml @@ -7,6 +7,7 @@ lightpush = true max-connections = 150 peer-exchange = false metrics-logging = false +metrics-server = true cluster-id = 2 discv5-discovery = false discv5-udp-port = 9002 @@ -27,4 +28,6 @@ nat = "extip:127.0.0.1" ext-multiaddr = ["/ip4/127.0.0.1/tcp/60003"] ext-multiaddr-only = true ip-colocation-limit=0 +mix-user-message-limit=4 +mix-disable-spam-protection=false #staticnode = ["/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o", "/ip4/127.0.0.1/tcp/60002/p2p/16Uiu2HAmLtKaFaSWDohToWhWUZFLtqzYZGPFuXwKrojFVF6az5UF","/ip4/127.0.0.1/tcp/60004/p2p/16Uiu2HAmPwRKZajXtfb1Qsv45VVfRZgK3ENdfmnqzSrVm3BczF6f","/ip4/127.0.0.1/tcp/60005/p2p/16Uiu2HAmRhxmCHBYdXt1RibXrjAUNJbduAhzaTHwFCZT4qWnqZAu"] diff --git a/simulations/mixnet/config3.toml b/simulations/mixnet/config3.toml index bd8e7c4e98..9b256ea088 100644 --- a/simulations/mixnet/config3.toml +++ b/simulations/mixnet/config3.toml @@ -7,6 +7,7 @@ lightpush = true max-connections = 150 peer-exchange = false metrics-logging = false +metrics-server = true cluster-id = 2 discv5-discovery = false discv5-udp-port = 9003 @@ -27,4 +28,6 @@ nat = "extip:127.0.0.1" ext-multiaddr = ["/ip4/127.0.0.1/tcp/60004"] ext-multiaddr-only = true ip-colocation-limit=0 +mix-user-message-limit=4 +mix-disable-spam-protection=false #staticnode = ["/ip4/127.0.0.1/tcp/60002/p2p/16Uiu2HAmLtKaFaSWDohToWhWUZFLtqzYZGPFuXwKrojFVF6az5UF", "/ip4/127.0.0.1/tcp/60003/p2p/16Uiu2HAmTEDHwAziWUSz6ZE23h5vxG2o4Nn7GazhMor4bVuMXTrA","/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o","/ip4/127.0.0.1/tcp/60005/p2p/16Uiu2HAmRhxmCHBYdXt1RibXrjAUNJbduAhzaTHwFCZT4qWnqZAu"] diff --git a/simulations/mixnet/config4.toml b/simulations/mixnet/config4.toml index f174250d54..d616896e66 100644 --- a/simulations/mixnet/config4.toml +++ b/simulations/mixnet/config4.toml @@ -7,6 +7,7 @@ lightpush = true max-connections = 150 peer-exchange = false metrics-logging = false +metrics-server = true cluster-id = 2 discv5-discovery = false discv5-udp-port = 9004 @@ -27,4 +28,6 @@ nat = "extip:127.0.0.1" ext-multiaddr = ["/ip4/127.0.0.1/tcp/60005"] ext-multiaddr-only = true ip-colocation-limit=0 +mix-user-message-limit=4 +mix-disable-spam-protection=false #staticnode = ["/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o", "/ip4/127.0.0.1/tcp/60003/p2p/16Uiu2HAmTEDHwAziWUSz6ZE23h5vxG2o4Nn7GazhMor4bVuMXTrA","/ip4/127.0.0.1/tcp/60004/p2p/16Uiu2HAmPwRKZajXtfb1Qsv45VVfRZgK3ENdfmnqzSrVm3BczF6f","/ip4/127.0.0.1/tcp/60002/p2p/16Uiu2HAmLtKaFaSWDohToWhWUZFLtqzYZGPFuXwKrojFVF6az5UF"] diff --git a/simulations/mixnet/setup_credentials.nim b/simulations/mixnet/setup_credentials.nim index 77c796354e..cffd60e9e0 100644 --- a/simulations/mixnet/setup_credentials.nim +++ b/simulations/mixnet/setup_credentials.nim @@ -20,8 +20,8 @@ import const KeystorePassword = "mix-rln-password" # Must match protocol.nim - DefaultUserMessageLimit = 100'u64 # Network-wide default rate limit - SpammerUserMessageLimit = 3'u64 # Lower limit for spammer testing + DefaultUserMessageLimit = 4'u64 # R=4 slots per 10s epoch + SpammerUserMessageLimit = 3'u64 # Higher limit for spammer testing # Peer IDs derived from nodekeys in config files # config.toml: nodekey = "f98e3fba96c32e8d1967d460f1b79457380e1a895f7971cecc8528abe733781a" diff --git a/tests/test_wakunode.nim b/tests/test_wakunode.nim index a7f1084fb0..39452890d4 100644 --- a/tests/test_wakunode.nim +++ b/tests/test_wakunode.nim @@ -1,7 +1,7 @@ {.used.} import - std/[sequtils, strutils, net], + std/[options, sequtils, strutils, net], stew/byteutils, testutils/unittests, chronicles, diff --git a/tests/waku_relay/test_wakunode_relay.nim b/tests/waku_relay/test_wakunode_relay.nim index a687119bda..ebfeef22c1 100644 --- a/tests/waku_relay/test_wakunode_relay.nim +++ b/tests/waku_relay/test_wakunode_relay.nim @@ -1,7 +1,7 @@ {.used.} import - std/[os, strutils, sequtils, sysrand, math], + std/[options, os, strutils, sequtils, sysrand, math], stew/byteutils, testutils/unittests, chronos, diff --git a/tests/waku_store/test_wakunode_store.nim b/tests/waku_store/test_wakunode_store.nim index fa73cd16d0..b7732aa65c 100644 --- a/tests/waku_store/test_wakunode_store.nim +++ b/tests/waku_store/test_wakunode_store.nim @@ -1,7 +1,7 @@ {.used.} import - std/sequtils, + std/[options, sequtils], testutils/unittests, chronicles, chronos, diff --git a/tools/confutils/cli_args.nim b/tools/confutils/cli_args.nim index de98b5650e..656db13da2 100644 --- a/tools/confutils/cli_args.nim +++ b/tools/confutils/cli_args.nim @@ -642,6 +642,18 @@ with the drawback of consuming some more bandwidth.""", name: "mixnode" .}: seq[MixNodePubInfo] + mixUserMessageLimit* {. + desc: + "Maximum messages per RLN epoch for mix cover traffic. If not set, uses plugin default.", + name: "mix-user-message-limit" + .}: Option[int] + + mixDisableSpamProtection* {. + desc: "Disable RLN spam protection for mix protocol (for testing only).", + defaultValue: false, + name: "mix-disable-spam-protection" + .}: bool + # Kademlia Discovery config enableKadDiscovery* {. desc: @@ -1077,6 +1089,9 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] = b.withMix(n.mix) if n.mixkey.isSome(): b.mixConf.withMixKey(n.mixkey.get()) + if n.mixUserMessageLimit.isSome(): + b.mixConf.withUserMessageLimit(n.mixUserMessageLimit.get()) + b.mixConf.withDisableSpamProtection(n.mixDisableSpamProtection) b.filterServiceConf.withEnabled(n.filter) b.filterServiceConf.withSubscriptionTimeout(n.filterSubscriptionTimeout) diff --git a/waku.nimble b/waku.nimble index b7ad049095..cf8c73abc6 100644 --- a/waku.nimble +++ b/waku.nimble @@ -27,7 +27,7 @@ requires "nim >= 2.2.4", "toml_serialization", "faststreams", # Networking & P2P - "https://github.com/vacp2p/nim-libp2p.git#ff8d51857b4b79a68468e7bcc27b2026cca02996", + "https://github.com/vacp2p/nim-libp2p.git#e1bbda4f666ae74e5e3909767e83508157c2b97b", "eth", "nat_traversal", "dnsdisc", @@ -60,7 +60,7 @@ requires "nim >= 2.2.4", # Packages not on nimble (use git URLs) requires "https://github.com/logos-messaging/nim-ffi" -requires "https://github.com/logos-co/mix-rln-spam-protection-plugin.git#037f8e100bfedffdbad1c4442e760d10a2437428" +requires "https://github.com/logos-co/mix-rln-spam-protection-plugin.git#153d0c04aec4cce0109c028140189c648180c866" requires "https://github.com/logos-messaging/nim-sds.git#2e9a7683f0e180bf112135fae3a3803eed8490d4" @@ -440,9 +440,13 @@ task chat2mix, "Build example Waku chat mix usage": #buildBinary name, "examples/", "-d:chronicles_log_level=WARN" let name = "chat2mix" + # Sink uses textlines (stdout) instead of textlines[file]: the latter + # currently triggers a chronicles compile-time macro-eval bug with Nim 2.2.4. + # Callers can redirect stdout to a file (the existing run_chat_mix*.sh + # scripts already do this). buildBinary name, "apps/chat2mix/", - "-d:chronicles_sinks=textlines[file] -d:chronicles_log_level=TRACE " + "-d:chronicles_sinks=textlines -d:chronicles_log_level='TRACE' " # -d:ssl - cause unlisted exception error in libp2p/utility... task chat2bridge, "Build chat2bridge": diff --git a/waku/common/option_shims.nim b/waku/common/option_shims.nim new file mode 100644 index 0000000000..6ad67635f1 --- /dev/null +++ b/waku/common/option_shims.nim @@ -0,0 +1,28 @@ +# Compatibility shims for std/options +# The results library removed valueOr/withValue support for Option[T]. +# These templates restore that functionality. + +{.push raises: [].} + +import std/options + +template valueOr*[T](self: Option[T], def: untyped): T = + let tmp = self + if tmp.isSome(): + tmp.get() + else: + def + +template withValue*[T](self: Option[T], value, body: untyped): untyped = + let tmp = self + if tmp.isSome(): + let value {.inject.} = tmp.get() + body + +template withValue*[T](self: Option[T], value, body, elseBody: untyped): untyped = + let tmp = self + if tmp.isSome(): + let value {.inject.} = tmp.get() + body + else: + elseBody diff --git a/waku/common/rate_limit/request_limiter.nim b/waku/common/rate_limit/request_limiter.nim index bc318e1514..8db84f8334 100644 --- a/waku/common/rate_limit/request_limiter.nim +++ b/waku/common/rate_limit/request_limiter.nim @@ -24,6 +24,7 @@ import import std/times except TimeInterval, Duration, seconds, minutes +import ../option_shims import ./[single_token_limiter, service_metrics, timed_map] export token_bucket, setting, service_metrics diff --git a/waku/discovery/waku_discv5.nim b/waku/discovery/waku_discv5.nim index c1b253c8cd..b47018f5c9 100644 --- a/waku/discovery/waku_discv5.nim +++ b/waku/discovery/waku_discv5.nim @@ -10,7 +10,9 @@ import eth/keys as eth_keys, eth/p2p/discoveryv5/node, eth/p2p/discoveryv5/protocol -import waku/[net/auto_port, node/peer_manager/peer_manager, waku_core, waku_enr] +import + waku/[net/auto_port, node/peer_manager/peer_manager, waku_core, waku_enr], + ../common/option_shims export protocol, waku_enr diff --git a/waku/discovery/waku_kademlia.nim b/waku/discovery/waku_kademlia.nim index aab3a98198..77de264f2e 100644 --- a/waku/discovery/waku_kademlia.nim +++ b/waku/discovery/waku_kademlia.nim @@ -9,8 +9,8 @@ import libp2p/[peerid, multiaddress, switch], libp2p/extended_peer_record, libp2p/crypto/curve25519, - libp2p/protocols/[kademlia, kad_disco], - libp2p/protocols/kademlia_discovery/types as kad_types, + libp2p/protocols/[kademlia, service_discovery], + libp2p/protocols/service_discovery/types as kad_types, libp2p/protocols/mix/mix_protocol import waku/waku_core, waku/node/peer_manager @@ -19,20 +19,20 @@ logScope: topics = "waku extended kademlia discovery" const - DefaultExtendedKademliaDiscoveryInterval* = chronos.seconds(5) - ExtendedKademliaDiscoveryStartupDelay* = chronos.seconds(5) + DefaultExtendedServiceDiscoveryInterval* = chronos.seconds(5) + ExtendedServiceDiscoveryStartupDelay* = chronos.seconds(5) type MixNodePoolSizeProvider* = proc(): int {.gcsafe, raises: [].} NodeStartedProvider* = proc(): bool {.gcsafe, raises: [].} - ExtendedKademliaDiscoveryParams* = object + ExtendedServiceDiscoveryParams* = object bootstrapNodes*: seq[(PeerId, seq[MultiAddress])] mixPubKey*: Option[Curve25519Key] advertiseMix*: bool = false WakuKademlia* = ref object - protocol*: KademliaDiscovery + protocol*: ServiceDiscovery peerManager: PeerManager discoveryLoop: Future[void] running*: bool @@ -42,7 +42,7 @@ type proc new*( T: type WakuKademlia, switch: Switch, - params: ExtendedKademliaDiscoveryParams, + params: ExtendedServiceDiscoveryParams, peerManager: PeerManager, getMixNodePoolSize: MixNodePoolSizeProvider = nil, isNodeStarted: NodeStartedProvider = nil, @@ -50,13 +50,13 @@ proc new*( if params.bootstrapNodes.len == 0: info "creating kademlia discovery as seed node (no bootstrap nodes)" - let kademlia = KademliaDiscovery.new( + let kademlia = ServiceDiscovery.new( switch, bootstrapNodes = params.bootstrapNodes, config = KadDHTConfig.new( validator = kad_types.ExtEntryValidator(), selector = kad_types.ExtEntrySelector() ), - codec = ExtendedKademliaDiscoveryCodec, + codec = ExtendedServiceDiscoveryCodec, ) try: @@ -197,7 +197,7 @@ proc runDiscoveryLoop( while wk.running: # Wait for node to be started if not wk.isNodeStarted.isNil() and not wk.isNodeStarted(): - await sleepAsync(ExtendedKademliaDiscoveryStartupDelay) + await sleepAsync(ExtendedServiceDiscoveryStartupDelay) continue var records: seq[ExtendedPeerRecord] @@ -247,7 +247,7 @@ proc runDiscoveryLoop( proc start*( wk: WakuKademlia, - interval: Duration = DefaultExtendedKademliaDiscoveryInterval, + interval: Duration = DefaultExtendedServiceDiscoveryInterval, minMixPeers: int = 0, ): Future[Result[void, string]] {.async: (raises: []).} = if wk.running: diff --git a/waku/factory/conf_builder/mix_conf_builder.nim b/waku/factory/conf_builder/mix_conf_builder.nim index 145ccb76e9..4fbc9462d0 100644 --- a/waku/factory/conf_builder/mix_conf_builder.nim +++ b/waku/factory/conf_builder/mix_conf_builder.nim @@ -12,6 +12,8 @@ type MixConfBuilder* = object enabled: Option[bool] mixKey: Option[string] mixNodes: seq[MixNodePubInfo] + userMessageLimit: Option[int] + disableSpamProtection: bool proc init*(T: type MixConfBuilder): MixConfBuilder = MixConfBuilder() @@ -25,6 +27,12 @@ proc withMixKey*(b: var MixConfBuilder, mixKey: string) = proc withMixNodes*(b: var MixConfBuilder, mixNodes: seq[MixNodePubInfo]) = b.mixNodes = mixNodes +proc withUserMessageLimit*(b: var MixConfBuilder, limit: int) = + b.userMessageLimit = some(limit) + +proc withDisableSpamProtection*(b: var MixConfBuilder, disable: bool) = + b.disableSpamProtection = disable + proc build*(b: MixConfBuilder): Result[Option[MixConf], string] = if not b.enabled.get(false): return ok(none[MixConf]()) @@ -33,11 +41,27 @@ proc build*(b: MixConfBuilder): Result[Option[MixConf], string] = let mixPrivKey = intoCurve25519Key(ncrutils.fromHex(b.mixKey.get())) let mixPubKey = public(mixPrivKey) return ok( - some(MixConf(mixKey: mixPrivKey, mixPubKey: mixPubKey, mixNodes: b.mixNodes)) + some( + MixConf( + mixKey: mixPrivKey, + mixPubKey: mixPubKey, + mixNodes: b.mixNodes, + userMessageLimit: b.userMessageLimit, + disableSpamProtection: b.disableSpamProtection, + ) + ) ) else: let (mixPrivKey, mixPubKey) = generateKeyPair().valueOr: return err("Generate key pair error: " & $error) return ok( - some(MixConf(mixKey: mixPrivKey, mixPubKey: mixPubKey, mixNodes: b.mixNodes)) + some( + MixConf( + mixKey: mixPrivKey, + mixPubKey: mixPubKey, + mixNodes: b.mixNodes, + userMessageLimit: b.userMessageLimit, + disableSpamProtection: b.disableSpamProtection, + ) + ) ) diff --git a/waku/factory/node_factory.nim b/waku/factory/node_factory.nim index 52b719b8fd..bfac7d4843 100644 --- a/waku/factory/node_factory.nim +++ b/waku/factory/node_factory.nim @@ -162,7 +162,12 @@ proc setupProtocols( #mount mix if conf.mixConf.isSome(): let mixConf = conf.mixConf.get() - (await node.mountMix(conf.clusterId, mixConf.mixKey, mixConf.mixnodes)).isOkOr: + ( + await node.mountMix( + conf.clusterId, mixConf.mixKey, mixConf.mixnodes, mixConf.userMessageLimit, + mixConf.disableSpamProtection, + ) + ).isOkOr: return err("failed to mount waku mix protocol: " & $error) # Setup extended kademlia discovery @@ -175,7 +180,7 @@ proc setupProtocols( node.wakuKademlia = WakuKademlia.new( node.switch, - ExtendedKademliaDiscoveryParams( + ExtendedServiceDiscoveryParams( bootstrapNodes: conf.kademliaDiscoveryConf.get().bootstrapNodes, mixPubKey: mixPubKey, advertiseMix: conf.mixConf.isSome(), diff --git a/waku/factory/waku_conf.nim b/waku/factory/waku_conf.nim index 9edc12a44a..d20dc263ca 100644 --- a/waku/factory/waku_conf.nim +++ b/waku/factory/waku_conf.nim @@ -51,6 +51,8 @@ type MixConf* = ref object mixKey*: Curve25519Key mixPubKey*: Curve25519Key mixnodes*: seq[MixNodePubInfo] + userMessageLimit*: Option[int] + disableSpamProtection*: bool type KademliaDiscoveryConf* = object bootstrapNodes*: seq[(PeerId, seq[MultiAddress])] diff --git a/waku/node/delivery_service/send_service/lightpush_processor.nim b/waku/node/delivery_service/send_service/lightpush_processor.nim index 7a9f65c719..c47487c2df 100644 --- a/waku/node/delivery_service/send_service/lightpush_processor.nim +++ b/waku/node/delivery_service/send_service/lightpush_processor.nim @@ -1,7 +1,11 @@ import chronicles, chronos, results import std/options import brokers/broker_context -import waku/node/peer_manager, waku/waku_core, waku/waku_lightpush/[common, client, rpc] +import + waku/common/option_shims, + waku/node/peer_manager, + waku/waku_core, + waku/waku_lightpush/[common, client, rpc] import ./[delivery_task, send_processor] diff --git a/waku/node/kernel_api/lightpush.nim b/waku/node/kernel_api/lightpush.nim index ffe2afdac4..2b7d1064d1 100644 --- a/waku/node/kernel_api/lightpush.nim +++ b/waku/node/kernel_api/lightpush.nim @@ -28,6 +28,7 @@ import ../../waku_lightpush/client as lightpush_client, ../../waku_lightpush as lightpush_protocol, ../peer_manager, + ../../common/option_shims, ../../common/rate_limit/setting, ../../waku_rln_relay diff --git a/waku/node/kernel_api/ping.nim b/waku/node/kernel_api/ping.nim index 9dc649fd84..9acd8de3dd 100644 --- a/waku/node/kernel_api/ping.nim +++ b/waku/node/kernel_api/ping.nim @@ -11,7 +11,7 @@ import libp2p/transports/tcptransport, libp2p/utility -import ../waku_node, ../peer_manager +import ../../common/option_shims, ../waku_node, ../peer_manager logScope: topics = "waku node ping api" diff --git a/waku/node/kernel_api/relay.nim b/waku/node/kernel_api/relay.nim index 16aa8f3ff4..124a709031 100644 --- a/waku/node/kernel_api/relay.nim +++ b/waku/node/kernel_api/relay.nim @@ -31,6 +31,7 @@ import waku_mix, node/waku_node, node/peer_manager, + common/option_shims, events/message_events, ] diff --git a/waku/node/peer_manager/peer_manager.nim b/waku/node/peer_manager/peer_manager.nim index 6602c049b5..dd9aae67e8 100644 --- a/waku/node/peer_manager/peer_manager.nim +++ b/waku/node/peer_manager/peer_manager.nim @@ -22,6 +22,7 @@ import events/peer_events, common/nimchronos, common/enr, + common/option_shims, common/callbacks, common/utils/parse_size_units, node/health_monitor/online_monitor, diff --git a/waku/node/peer_manager/waku_peer_store.nim b/waku/node/peer_manager/waku_peer_store.nim index 93ac9ad2e9..f20f22c3a4 100644 --- a/waku/node/peer_manager/waku_peer_store.nim +++ b/waku/node/peer_manager/waku_peer_store.nim @@ -10,6 +10,7 @@ import libp2p/crypto/curve25519 import + ../../common/option_shims, ../../waku_core, ../../waku_enr/sharding, ../../waku_enr/capabilities, diff --git a/waku/node/waku_node.nim b/waku/node/waku_node.nim index 6dd24fb474..dc875ee65e 100644 --- a/waku/node/waku_node.nim +++ b/waku/node/waku_node.nim @@ -54,6 +54,7 @@ import waku_rln_relay, common/rate_limit/setting, common/callbacks, + common/option_shims, common/nimchronos, waku_mix, requests/node_requests, @@ -316,6 +317,7 @@ proc mountMix*( mixPrivKey: Curve25519Key, mixnodes: seq[MixNodePubInfo], userMessageLimit: Option[int] = none(int), + disableSpamProtection: bool = false, ): Future[Result[void, string]] {.async.} = info "mounting mix protocol", nodeId = node.info #TODO log the config used @@ -348,7 +350,7 @@ proc mountMix*( node.wakuMix = WakuMix.new( localaddrStr, node.peerManager, clusterId, mixPrivKey, mixnodes, publishMessage, - userMessageLimit, + userMessageLimit, disableSpamProtection, ).valueOr: error "Waku Mix protocol initialization failed", err = error return diff --git a/waku/rest_api/endpoint/filter/handlers.nim b/waku/rest_api/endpoint/filter/handlers.nim index 61d7eb96fc..363b9bd1f6 100644 --- a/waku/rest_api/endpoint/filter/handlers.nim +++ b/waku/rest_api/endpoint/filter/handlers.nim @@ -10,6 +10,7 @@ import presto/route, presto/common import + ../../../common/option_shims, ../../../waku_core, ../../../waku_node, ../../../node/peer_manager, diff --git a/waku/rest_api/endpoint/legacy_lightpush/handlers.nim b/waku/rest_api/endpoint/legacy_lightpush/handlers.nim index 7a3c5b1ed1..a2ca0f79b7 100644 --- a/waku/rest_api/endpoint/legacy_lightpush/handlers.nim +++ b/waku/rest_api/endpoint/legacy_lightpush/handlers.nim @@ -10,6 +10,7 @@ import presto/common import + waku/common/option_shims, waku/node/peer_manager, waku/waku_lightpush_legacy/common, ../../../waku_node, diff --git a/waku/rest_api/endpoint/lightpush/handlers.nim b/waku/rest_api/endpoint/lightpush/handlers.nim index 342053e72b..e334b3420d 100644 --- a/waku/rest_api/endpoint/lightpush/handlers.nim +++ b/waku/rest_api/endpoint/lightpush/handlers.nim @@ -10,6 +10,7 @@ import presto/common import + waku/common/option_shims, waku/node/peer_manager, waku/waku_lightpush/common, ../../../waku_node, diff --git a/waku/rest_api/endpoint/store/handlers.nim b/waku/rest_api/endpoint/store/handlers.nim index 7d37191fb0..fc07c8fc85 100644 --- a/waku/rest_api/endpoint/store/handlers.nim +++ b/waku/rest_api/endpoint/store/handlers.nim @@ -3,6 +3,7 @@ import std/[strformat, sugar], results, chronicles, uri, json_serialization, presto/route import + ../../../common/option_shims, ../../../waku_core, ../../../waku_store/common, ../../../waku_store/self_req_handler, diff --git a/waku/waku_enr/sharding.nim b/waku/waku_enr/sharding.nim index 2aeb96a9da..f35bca2751 100644 --- a/waku/waku_enr/sharding.nim +++ b/waku/waku_enr/sharding.nim @@ -8,7 +8,7 @@ import eth/keys, libp2p/[multiaddress, multicodec], libp2p/crypto/crypto -import ../common/enr, ../waku_core/topics/pubsub_topic +import ../common/enr, ../common/option_shims, ../waku_core/topics/pubsub_topic logScope: topics = "waku enr sharding" diff --git a/waku/waku_lightpush/client.nim b/waku/waku_lightpush/client.nim index fd12c49d20..c76aaa680f 100644 --- a/waku/waku_lightpush/client.nim +++ b/waku/waku_lightpush/client.nim @@ -3,6 +3,7 @@ import std/options, results, chronicles, chronos, metrics, bearssl/rand, stew/byteutils import libp2p/peerid, libp2p/stream/connection import + ../common/option_shims, ../waku_core/peers, ../node/peer_manager, ../utils/requests, diff --git a/waku/waku_lightpush/protocol.nim b/waku/waku_lightpush/protocol.nim index 8336f4dfc2..fa9074d5bd 100644 --- a/waku/waku_lightpush/protocol.nim +++ b/waku/waku_lightpush/protocol.nim @@ -9,6 +9,7 @@ import metrics, bearssl/rand import + ../common/option_shims, ../node/peer_manager/peer_manager, ../waku_core, ../waku_core/topics/sharding, diff --git a/waku/waku_lightpush_legacy/client.nim b/waku/waku_lightpush_legacy/client.nim index ab489bec90..4079e24ef2 100644 --- a/waku/waku_lightpush_legacy/client.nim +++ b/waku/waku_lightpush_legacy/client.nim @@ -3,6 +3,7 @@ import std/options, results, chronicles, chronos, metrics, bearssl/rand, stew/byteutils import libp2p/peerid import + ../common/option_shims, ../waku_core/peers, ../node/peer_manager, ../utils/requests, diff --git a/waku/waku_mix/protocol.nim b/waku/waku_mix/protocol.nim index 8a12250a38..db2d04f9ea 100644 --- a/waku/waku_mix/protocol.nim +++ b/waku/waku_mix/protocol.nim @@ -11,6 +11,7 @@ import libp2p/protocols/mix/mix_metrics, libp2p/protocols/mix/delay_strategy, libp2p/protocols/mix/spam_protection, + libp2p/protocols/mix/cover_traffic, libp2p/[multiaddress, multicodec, peerid, peerinfo], eth/common/keys @@ -91,6 +92,7 @@ proc new*( bootnodes: seq[MixNodePubInfo], publishMessage: PublishMessage, userMessageLimit: Option[int] = none(int), + disableSpamProtection: bool = false, ): WakuMixResult[T] = let mixPubKey = public(mixPrivKey) trace "mixPubKey", mixPubKey = mixPubKey @@ -101,33 +103,50 @@ proc new*( peermgr.switch.peerInfo.publicKey.skkey, peermgr.switch.peerInfo.privateKey.skkey, ) - # Initialize spam protection with persistent credentials - # Use peerID in keystore path so multiple peers can run from same directory - # Tree path is shared across all nodes to maintain the full membership set - let peerId = peermgr.switch.peerInfo.peerId - var spamProtectionConfig = defaultConfig() - spamProtectionConfig.keystorePath = "rln_keystore_" & $peerId & ".json" - spamProtectionConfig.keystorePassword = "mix-rln-password" - if userMessageLimit.isSome(): - spamProtectionConfig.userMessageLimit = userMessageLimit.get() - # rlnResourcesPath left empty to use bundled resources (via "tree_height_/" placeholder) + let totalSlots = userMessageLimit.get(2) + let ct = ConstantRateCoverTraffic.new( + totalSlots = totalSlots, + epochDuration = 10.seconds, + useInternalEpochTimer = disableSpamProtection, + ) - let spamProtection = newMixRlnSpamProtection(spamProtectionConfig).valueOr: - return err("failed to create spam protection: " & error) + var spamProtectionOpt = default(Opt[SpamProtection]) + if not disableSpamProtection: + # Initialize spam protection with persistent credentials + let peerId = peermgr.switch.peerInfo.peerId + var spamProtectionConfig = defaultConfig() + spamProtectionConfig.keystorePath = "rln_keystore_" & $peerId & ".json" + spamProtectionConfig.keystorePassword = "mix-rln-password" + if userMessageLimit.isSome(): + spamProtectionConfig.userMessageLimit = userMessageLimit.get() + + let spamProtection = newMixRlnSpamProtection(spamProtectionConfig).valueOr: + return err("failed to create spam protection: " & error) + spamProtectionOpt = Opt.some(SpamProtection(spamProtection)) + else: + info "mix spam protection disabled" + + var mixRlnSpam: MixRlnSpamProtection + if spamProtectionOpt.isSome(): + mixRlnSpam = MixRlnSpamProtection(spamProtectionOpt.get()) var m = WakuMix( peerManager: peermgr, clusterId: clusterId, pubKey: mixPubKey, - mixRlnSpamProtection: spamProtection, publishMessage: publishMessage, + mixRlnSpamProtection: mixRlnSpam, ) procCall MixProtocol(m).init( localMixNodeInfo, peermgr.switch, - spamProtection = Opt.some(SpamProtection(spamProtection)), - delayStrategy = - ExponentialDelayStrategy.new(meanDelayMs = 100, rng = crypto.newRng()), + spamProtection = spamProtectionOpt, + delayStrategy = Opt.some( + DelayStrategy( + ExponentialDelayStrategy.new(meanDelay = 100, rng = crypto.newRng()) + ) + ), + coverTraffic = Opt.some(CoverTraffic(ct)), ) processBootNodes(bootnodes, peermgr, m) @@ -144,6 +163,8 @@ proc setupSpamProtectionCallbacks(mix: WakuMix) = ## Set up the publish callback for spam protection coordination. ## This enables the plugin to broadcast membership updates and proof metadata ## via Waku relay. + if mix.mixRlnSpamProtection.isNil(): + return if mix.publishMessage.isNil(): warn "PublishMessage callback not available, spam protection coordination disabled" return @@ -300,8 +321,7 @@ proc dosRegistrationRetryLoop(mix: WakuMix) {.async.} = try: let registerRes = await mix.mixRlnSpamProtection.registerSelf() if registerRes.isOk(): - debug "DoS-protection self-registration succeeded", - index = registerRes.get() + debug "DoS-protection self-registration succeeded", index = registerRes.get() # Persist tree only after a successful register — for fresh nodes this # captures the new index; for keystore nodes it's a harmless no-op. let saveRes = mix.mixRlnSpamProtection.saveTree() diff --git a/waku/waku_rendezvous/client.nim b/waku/waku_rendezvous/client.nim index 09e7897741..59a3a8086f 100644 --- a/waku/waku_rendezvous/client.nim +++ b/waku/waku_rendezvous/client.nim @@ -8,11 +8,12 @@ import libp2p/protocols/rendezvous, libp2p/crypto/curve25519, libp2p/switch, - libp2p/utils/semaphore + chronos/asyncsync import metrics except collect import + waku/common/option_shims, waku/node/peer_manager, waku/waku_core/peers, waku/waku_core/codecs, diff --git a/waku/waku_rendezvous/protocol.nim b/waku/waku_rendezvous/protocol.nim index 89433f533b..d29d267415 100644 --- a/waku/waku_rendezvous/protocol.nim +++ b/waku/waku_rendezvous/protocol.nim @@ -8,7 +8,7 @@ import stew/byteutils, libp2p/protocols/rendezvous, libp2p/protocols/rendezvous/protobuf, - libp2p/utils/semaphore, + chronos/asyncsync, libp2p/utils/offsettedseq, libp2p/crypto/curve25519, libp2p/switch, @@ -19,6 +19,7 @@ import metrics except collect import ../node/peer_manager, ../common/callbacks, + ../common/option_shims, ../waku_enr/capabilities, ../waku_core/peers, ../waku_core/codecs, diff --git a/waku/waku_store/client.nim b/waku/waku_store/client.nim index b496628114..fab1e4abc8 100644 --- a/waku/waku_store/client.nim +++ b/waku/waku_store/client.nim @@ -8,7 +8,12 @@ import metrics, bearssl/rand import - ../node/peer_manager, ../utils/requests, ./protocol_metrics, ./common, ./rpc_codec + ../common/option_shims, + ../node/peer_manager, + ../utils/requests, + ./protocol_metrics, + ./common, + ./rpc_codec logScope: topics = "waku store client" diff --git a/waku/waku_store/resume.nim b/waku/waku_store/resume.nim index b7864da94a..04ef7e858e 100644 --- a/waku/waku_store/resume.nim +++ b/waku/waku_store/resume.nim @@ -13,6 +13,7 @@ import import ../common/databases/db_sqlite, + ../common/option_shims, ../waku_core, ../waku_archive, ../common/nimchronos, diff --git a/waku/waku_store_sync/reconciliation.nim b/waku/waku_store_sync/reconciliation.nim index b18251fff8..c73b5867ea 100644 --- a/waku/waku_store_sync/reconciliation.nim +++ b/waku/waku_store_sync/reconciliation.nim @@ -14,6 +14,7 @@ import eth/p2p/discoveryv5/enr import ../common/nimchronos, + ../common/option_shims, ../common/protobuf, ../common/paging, ../waku_enr, diff --git a/waku/waku_store_sync/transfer.nim b/waku/waku_store_sync/transfer.nim index 5d20afb186..c3ee7af5bb 100644 --- a/waku/waku_store_sync/transfer.nim +++ b/waku/waku_store_sync/transfer.nim @@ -13,6 +13,7 @@ import eth/p2p/discoveryv5/enr import ../common/nimchronos, + ../common/option_shims, ../common/protobuf, ../waku_enr, ../waku_core/codecs, From 3886ac1139b4c1dec25003335904929853442b92 Mon Sep 17 00:00:00 2001 From: Prem Chaitanya Prathi Date: Thu, 21 May 2026 10:38:31 +0530 Subject: [PATCH 2/2] fixup(mix): address PR #3807 review + sim alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - waku/waku_mix/protocol.nim: drop the magic-2 cover-traffic fallback and the hardcoded 10s epoch. Source cover-traffic totalSlots and epochDuration from spamProtectionConfig when RLN is on (so cover emission can't outpace proof minting), and from named waku constants (WakuCoverTrafficTotalSlots=40, WakuCoverTrafficEpochDuration=60s, ~10 emissions/min/node) when RLN is disabled. Single ConstantRate CoverTraffic.new call site at the end of the block; the if/else only sets up spam protection. Addresses PR review comment on protocol.nim line 102. - waku.nimble: bump mix-rln plugin to 8ec5dc24 (latest on feat/cover-traffic-epoch-support: messageId guard + drift-corrected epoch timer) and pin nim-lsquic to #6d2bc489 (v0.2.0) so libp2p 1.15.3's certificate_ffi keeps finding EVP_PKEY in lsquic_ffi.nim. - waku/common/option_shims: explain in a header comment that the file exists because libp2p 1.15.3 dropped Option[T] overloads of valueOr/withValue from libp2p/utility; can be removed once those are restored upstream. Addresses PR review comment. - simulations/mixnet/setup_credentials: drop the unused SpammerUserMessageLimit constant (the "Higher" comment was wrong since 3 < DefaultUserMessageLimit=4, and zerokit blocks proof-gen past the per-user limit anyway, so it could never simulate a spammer). Addresses PR review comment. - simulations/mixnet/run_chat_mix{,1}.sh: pass --rln-user-message-limit=4 so the chat client's RLN budget matches the keystores baked at limit=4, otherwise cover-traffic totalSlots vs RLN-budget mismatch jams the sim. Sim verified end-to-end: - RLN-on (default sim config): PASS, ≥2 proof-verified per node, cover-traffic metrics non-zero. - RLN-off (mix nodes only, ad-hoc config): cover-traffic emits at ~13/min/node from the new waku defaults (target ~10/min); the no-RLN code path is exercised correctly. --- simulations/mixnet/run_chat_mix.sh | 2 +- simulations/mixnet/run_chat_mix1.sh | 2 +- simulations/mixnet/setup_credentials.nim | 2 -- waku.nimble | 4 +-- waku/common/option_shims.nim | 9 ++++-- waku/waku_mix/protocol.nim | 37 ++++++++++++++++++++---- 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/simulations/mixnet/run_chat_mix.sh b/simulations/mixnet/run_chat_mix.sh index ef05753755..e4841e2da7 100755 --- a/simulations/mixnet/run_chat_mix.sh +++ b/simulations/mixnet/run_chat_mix.sh @@ -1,2 +1,2 @@ -../../build/chat2mix --cluster-id=2 --num-shards-in-network=1 --shard=0 --servicenode="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o" --log-level=TRACE --nodekey="cb6fe589db0e5d5b48f7e82d33093e4d9d35456f4aaffc2322c473a173b2ac49" --kad-bootstrap-node="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o" --fleet="none" +../../build/chat2mix --cluster-id=2 --num-shards-in-network=1 --shard=0 --servicenode="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o" --log-level=TRACE --nodekey="cb6fe589db0e5d5b48f7e82d33093e4d9d35456f4aaffc2322c473a173b2ac49" --kad-bootstrap-node="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o" --rln-user-message-limit=4 --fleet="none" #--mixnode="/ip4/127.0.0.1/tcp/60002/p2p/16Uiu2HAmLtKaFaSWDohToWhWUZFLtqzYZGPFuXwKrojFVF6az5UF:9231e86da6432502900a84f867004ce78632ab52cd8e30b1ec322cd795710c2a" --mixnode="/ip4/127.0.0.1/tcp/60003/p2p/16Uiu2HAmTEDHwAziWUSz6ZE23h5vxG2o4Nn7GazhMor4bVuMXTrA:275cd6889e1f29ca48e5b9edb800d1a94f49f13d393a0ecf1a07af753506de6c" --mixnode="/ip4/127.0.0.1/tcp/60004/p2p/16Uiu2HAmPwRKZajXtfb1Qsv45VVfRZgK3ENdfmnqzSrVm3BczF6f:e0ed594a8d506681be075e8e23723478388fb182477f7a469309a25e7076fc18" --mixnode="/ip4/127.0.0.1/tcp/60005/p2p/16Uiu2HAmRhxmCHBYdXt1RibXrjAUNJbduAhzaTHwFCZT4qWnqZAu:8fd7a1a7c19b403d231452a9b1ea40eb1cc76f455d918ef8980e7685f9eeeb1f" diff --git a/simulations/mixnet/run_chat_mix1.sh b/simulations/mixnet/run_chat_mix1.sh index 5961fce457..a0aa4813be 100755 --- a/simulations/mixnet/run_chat_mix1.sh +++ b/simulations/mixnet/run_chat_mix1.sh @@ -1 +1 @@ -../../build/chat2mix --cluster-id=2 --num-shards-in-network=1 --shard=0 --servicenode="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o" --log-level=TRACE --nodekey="35eace7ccb246f20c487e05015ca77273d8ecaed0ed683de3d39bf4f69336feb" --mixnode="/ip4/127.0.0.1/tcp/60002/p2p/16Uiu2HAmLtKaFaSWDohToWhWUZFLtqzYZGPFuXwKrojFVF6az5UF:9231e86da6432502900a84f867004ce78632ab52cd8e30b1ec322cd795710c2a" --mixnode="/ip4/127.0.0.1/tcp/60003/p2p/16Uiu2HAmTEDHwAziWUSz6ZE23h5vxG2o4Nn7GazhMor4bVuMXTrA:275cd6889e1f29ca48e5b9edb800d1a94f49f13d393a0ecf1a07af753506de6c" --mixnode="/ip4/127.0.0.1/tcp/60004/p2p/16Uiu2HAmPwRKZajXtfb1Qsv45VVfRZgK3ENdfmnqzSrVm3BczF6f:e0ed594a8d506681be075e8e23723478388fb182477f7a469309a25e7076fc18" --mixnode="/ip4/127.0.0.1/tcp/60005/p2p/16Uiu2HAmRhxmCHBYdXt1RibXrjAUNJbduAhzaTHwFCZT4qWnqZAu:8fd7a1a7c19b403d231452a9b1ea40eb1cc76f455d918ef8980e7685f9eeeb1f" --mixnode="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o:9d09ce624f76e8f606265edb9cca2b7de9b41772a6d784bddaf92ffa8fba7d2c" --fleet="none" +../../build/chat2mix --cluster-id=2 --num-shards-in-network=1 --shard=0 --servicenode="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o" --log-level=TRACE --nodekey="35eace7ccb246f20c487e05015ca77273d8ecaed0ed683de3d39bf4f69336feb" --mixnode="/ip4/127.0.0.1/tcp/60002/p2p/16Uiu2HAmLtKaFaSWDohToWhWUZFLtqzYZGPFuXwKrojFVF6az5UF:9231e86da6432502900a84f867004ce78632ab52cd8e30b1ec322cd795710c2a" --mixnode="/ip4/127.0.0.1/tcp/60003/p2p/16Uiu2HAmTEDHwAziWUSz6ZE23h5vxG2o4Nn7GazhMor4bVuMXTrA:275cd6889e1f29ca48e5b9edb800d1a94f49f13d393a0ecf1a07af753506de6c" --mixnode="/ip4/127.0.0.1/tcp/60004/p2p/16Uiu2HAmPwRKZajXtfb1Qsv45VVfRZgK3ENdfmnqzSrVm3BczF6f:e0ed594a8d506681be075e8e23723478388fb182477f7a469309a25e7076fc18" --mixnode="/ip4/127.0.0.1/tcp/60005/p2p/16Uiu2HAmRhxmCHBYdXt1RibXrjAUNJbduAhzaTHwFCZT4qWnqZAu:8fd7a1a7c19b403d231452a9b1ea40eb1cc76f455d918ef8980e7685f9eeeb1f" --mixnode="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o:9d09ce624f76e8f606265edb9cca2b7de9b41772a6d784bddaf92ffa8fba7d2c" --rln-user-message-limit=4 --fleet="none" diff --git a/simulations/mixnet/setup_credentials.nim b/simulations/mixnet/setup_credentials.nim index cffd60e9e0..f1f053d80c 100644 --- a/simulations/mixnet/setup_credentials.nim +++ b/simulations/mixnet/setup_credentials.nim @@ -21,7 +21,6 @@ import const KeystorePassword = "mix-rln-password" # Must match protocol.nim DefaultUserMessageLimit = 4'u64 # R=4 slots per 10s epoch - SpammerUserMessageLimit = 3'u64 # Higher limit for spammer testing # Peer IDs derived from nodekeys in config files # config.toml: nodekey = "f98e3fba96c32e8d1967d460f1b79457380e1a895f7971cecc8528abe733781a" @@ -131,7 +130,6 @@ proc setupCredentialsAndTree() {.async.} = echo " Keystores: rln_keystore_{peerId}.json" echo " Password: ", KeystorePassword echo " Default rate limit: ", DefaultUserMessageLimit - echo " Spammer rate limit: ", SpammerUserMessageLimit echo "" echo "Note: All nodes must use the same rln_tree.db file." diff --git a/waku.nimble b/waku.nimble index cf8c73abc6..d365134966 100644 --- a/waku.nimble +++ b/waku.nimble @@ -60,7 +60,7 @@ requires "nim >= 2.2.4", # Packages not on nimble (use git URLs) requires "https://github.com/logos-messaging/nim-ffi" -requires "https://github.com/logos-co/mix-rln-spam-protection-plugin.git#153d0c04aec4cce0109c028140189c648180c866" +requires "https://github.com/logos-co/mix-rln-spam-protection-plugin.git#8ec5dc24e9779a75494431e269c107b5de732c1d" requires "https://github.com/logos-messaging/nim-sds.git#2e9a7683f0e180bf112135fae3a3803eed8490d4" @@ -74,7 +74,7 @@ requires "https://github.com/logos-messaging/nim-sds.git#2e9a7683f0e180bf112135f # updated. requires "https://github.com/NagyZoltanPeter/nim-brokers.git#v2.0.1" -requires "https://github.com/vacp2p/nim-lsquic" +requires "https://github.com/vacp2p/nim-lsquic#6d2bc489d05a0a33636a144d48bd99c707285a42" requires "https://github.com/vacp2p/nim-jwt.git#057ec95eb5af0eea9c49bfe9025b3312c95dc5f2" proc getMyCPU(): string = diff --git a/waku/common/option_shims.nim b/waku/common/option_shims.nim index 6ad67635f1..18e4335741 100644 --- a/waku/common/option_shims.nim +++ b/waku/common/option_shims.nim @@ -1,6 +1,9 @@ -# Compatibility shims for std/options -# The results library removed valueOr/withValue support for Option[T]. -# These templates restore that functionality. +# Compatibility shims for std/options. +# libp2p 1.15.2 (and earlier) exposed `valueOr` / `withValue` for std `Option[T]` +# via `libp2p/utility`. libp2p 1.15.3 dropped those overloads (only `Opt[T]` and +# `Result[T, E]` remain), which breaks existing waku code that still uses +# std `Option`. Restore the templates locally so the codebase keeps compiling +# while the upstream API gap is resolved. {.push raises: [].} diff --git a/waku/waku_mix/protocol.nim b/waku/waku_mix/protocol.nim index db2d04f9ea..1f95ef09b5 100644 --- a/waku/waku_mix/protocol.nim +++ b/waku/waku_mix/protocol.nim @@ -29,6 +29,23 @@ logScope: const minMixPoolSize = 4 +# Waku-side cover-traffic defaults for the no-RLN path (i.e. when +# `disableSpamProtection = true`). When RLN is enabled, both values are +# overridden below by the spam-protection plugin's config so cover +# emission can never outpace proof minting. +# +# Emission rate is given by: +# emissionInterval = epochDuration * (1 + PathLength) / totalSlots +# With PathLength = 3 and the values below: 60s * 4 / 40 = 6s, i.e. ~10 +# cover packets per minute per node. Tuned to be light enough not to +# saturate a small testnet while still exercising cover-traffic flow. +const + WakuCoverTrafficTotalSlots = 40 + ## Cover-traffic budget per epoch when RLN is disabled (slot pool size). + WakuCoverTrafficEpochDuration = 60.seconds + ## Cover-traffic epoch duration when RLN is disabled. Slot pool resets + ## at this cadence; the internal epoch timer fires on this interval. + type PublishMessage* = proc(message: WakuMessage): Future[Result[void, string]] {. async, gcsafe, raises: [] @@ -103,12 +120,11 @@ proc new*( peermgr.switch.peerInfo.publicKey.skkey, peermgr.switch.peerInfo.privateKey.skkey, ) - let totalSlots = userMessageLimit.get(2) - let ct = ConstantRateCoverTraffic.new( - totalSlots = totalSlots, - epochDuration = 10.seconds, - useInternalEpochTimer = disableSpamProtection, - ) + # Start with waku's no-RLN cover-traffic defaults. The spam-protection + # branch below overrides these from the plugin's config so cover emission + # can't outpace proof minting when RLN is on. + var ctTotalSlots = WakuCoverTrafficTotalSlots + var ctEpochDuration = WakuCoverTrafficEpochDuration var spamProtectionOpt = default(Opt[SpamProtection]) if not disableSpamProtection: @@ -120,12 +136,21 @@ proc new*( if userMessageLimit.isSome(): spamProtectionConfig.userMessageLimit = userMessageLimit.get() + ctTotalSlots = spamProtectionConfig.userMessageLimit + ctEpochDuration = spamProtectionConfig.epochDurationSeconds.int.seconds + let spamProtection = newMixRlnSpamProtection(spamProtectionConfig).valueOr: return err("failed to create spam protection: " & error) spamProtectionOpt = Opt.some(SpamProtection(spamProtection)) else: info "mix spam protection disabled" + let ct = ConstantRateCoverTraffic.new( + totalSlots = ctTotalSlots, + epochDuration = ctEpochDuration, + useInternalEpochTimer = disableSpamProtection, + ) + var mixRlnSpam: MixRlnSpamProtection if spamProtectionOpt.isSome(): mixRlnSpam = MixRlnSpamProtection(spamProtectionOpt.get())