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
This commit is contained in:
Prem Chaitanya Prathi 2026-05-20 18:29:27 +05:30
parent f8ebf4dead
commit 92ea373520
No known key found for this signature in database
43 changed files with 245 additions and 53 deletions

View File

@ -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,

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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"]

View File

@ -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"]

View File

@ -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"]

View File

@ -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"

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[sequtils, strutils, net],
std/[options, sequtils, strutils, net],
stew/byteutils,
testutils/unittests,
chronicles,

View File

@ -1,7 +1,7 @@
{.used.}
import
std/[os, strutils, sequtils, sysrand, math],
std/[options, os, strutils, sequtils, sysrand, math],
stew/byteutils,
testutils/unittests,
chronos,

View File

@ -1,7 +1,7 @@
{.used.}
import
std/sequtils,
std/[options, sequtils],
testutils/unittests,
chronicles,
chronos,

View File

@ -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)

View File

@ -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":

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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,
)
)
)

View File

@ -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(),

View File

@ -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])]

View File

@ -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]

View File

@ -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

View File

@ -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"

View File

@ -31,6 +31,7 @@ import
waku_mix,
node/waku_node,
node/peer_manager,
common/option_shims,
events/message_events,
]

View File

@ -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,

View File

@ -10,6 +10,7 @@ import
libp2p/crypto/curve25519
import
../../common/option_shims,
../../waku_core,
../../waku_enr/sharding,
../../waku_enr/capabilities,

View File

@ -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

View File

@ -10,6 +10,7 @@ import
presto/route,
presto/common
import
../../../common/option_shims,
../../../waku_core,
../../../waku_node,
../../../node/peer_manager,

View File

@ -10,6 +10,7 @@ import
presto/common
import
waku/common/option_shims,
waku/node/peer_manager,
waku/waku_lightpush_legacy/common,
../../../waku_node,

View File

@ -10,6 +10,7 @@ import
presto/common
import
waku/common/option_shims,
waku/node/peer_manager,
waku/waku_lightpush/common,
../../../waku_node,

View File

@ -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,

View File

@ -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"

View File

@ -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,

View File

@ -9,6 +9,7 @@ import
metrics,
bearssl/rand
import
../common/option_shims,
../node/peer_manager/peer_manager,
../waku_core,
../waku_core/topics/sharding,

View File

@ -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,

View File

@ -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()

View File

@ -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,

View File

@ -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,

View File

@ -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"

View File

@ -13,6 +13,7 @@ import
import
../common/databases/db_sqlite,
../common/option_shims,
../waku_core,
../waku_archive,
../common/nimchronos,

View File

@ -14,6 +14,7 @@ import
eth/p2p/discoveryv5/enr
import
../common/nimchronos,
../common/option_shims,
../common/protobuf,
../common/paging,
../waku_enr,

View File

@ -13,6 +13,7 @@ import
eth/p2p/discoveryv5/enr
import
../common/nimchronos,
../common/option_shims,
../common/protobuf,
../waku_enr,
../waku_core/codecs,