Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/chat2mix/chat2mix.nim
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,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,
Expand Down
33 changes: 33 additions & 0 deletions simulations/mixnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
18 changes: 18 additions & 0 deletions simulations/mixnet/check_cover_traffic.sh
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions simulations/mixnet/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
3 changes: 3 additions & 0 deletions simulations/mixnet/config1.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
3 changes: 3 additions & 0 deletions simulations/mixnet/config2.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
3 changes: 3 additions & 0 deletions simulations/mixnet/config3.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
3 changes: 3 additions & 0 deletions simulations/mixnet/config4.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
4 changes: 2 additions & 2 deletions simulations/mixnet/setup_credentials.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Truly a nitpick, but I'm trying to parse the configuration here and elsewhere, and I don't understand the change of "Lower" to "Higher" here. 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this was something i added initially to simulate a spammer but later realized that there is no way to simulate it as zerokit itself seems to have a check once a user crosses his own message limit, it doesnt generate proofs. So, right now this is more of useless code, will try to remove it.


# Peer IDs derived from nodekeys in config files
# config.toml: nodekey = "f98e3fba96c32e8d1967d460f1b79457380e1a895f7971cecc8528abe733781a"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_wakunode.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{.used.}

import
std/[sequtils, strutils, net],
std/[options, sequtils, strutils, net],
stew/byteutils,
testutils/unittests,
chronicles,
Expand Down
2 changes: 1 addition & 1 deletion tests/waku_relay/test_wakunode_relay.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{.used.}

import
std/[os, strutils, sequtils, sysrand, math],
std/[options, os, strutils, sequtils, sysrand, math],
stew/byteutils,
testutils/unittests,
chronos,
Expand Down
2 changes: 1 addition & 1 deletion tests/waku_store/test_wakunode_store.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{.used.}

import
std/sequtils,
std/[options, sequtils],
testutils/unittests,
chronicles,
chronos,
Expand Down
2 changes: 1 addition & 1 deletion tests/waku_store_legacy/test_wakunode_store.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{.used.}

import
std/net,
std/[net, options],
testutils/unittests,
chronos,
libp2p/crypto/crypto,
Expand Down
14 changes: 14 additions & 0 deletions tools/confutils/cli_args.nim
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,17 @@ 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:
Expand Down Expand Up @@ -1073,6 +1084,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)
Expand Down
2 changes: 1 addition & 1 deletion vendor/nim-libp2p
Submodule nim-libp2p updated 263 files
6 changes: 5 additions & 1 deletion waku.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,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":
Expand Down
28 changes: 28 additions & 0 deletions waku/common/option_shims.nim
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting. Any reason why these functions were removed? Shouldn't we adapt to whatever is considered the most idiomatic, or use a different library?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to mention that this was. temporary hack that was done to avoid changes to be made in rest of logos delivery code.
I think the original issue came from libp2p migrating to a newer version which was causing issues in code no related to changes in this PR.

This file should be removed before merging as i assume this would get fixed by some other PR in master.

Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions waku/common/rate_limit/request_limiter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion waku/discovery/waku_discv5.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import
eth/keys as eth_keys,
eth/p2p/discoveryv5/node,
eth/p2p/discoveryv5/protocol
import ../node/peer_manager/peer_manager, ../waku_core, ../waku_enr
import ../common/option_shims, ../node/peer_manager/peer_manager, ../waku_core, ../waku_enr

export protocol, waku_enr

Expand Down
22 changes: 11 additions & 11 deletions waku/discovery/waku_kademlia.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -42,21 +42,21 @@ type
proc new*(
T: type WakuKademlia,
switch: Switch,
params: ExtendedKademliaDiscoveryParams,
params: ExtendedServiceDiscoveryParams,
peerManager: PeerManager,
getMixNodePoolSize: MixNodePoolSizeProvider = nil,
isNodeStarted: NodeStartedProvider = nil,
): Result[T, string] =
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:
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down
Loading
Loading