fixup(mix): address PR #3807 review + sim alignment

- 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.
This commit is contained in:
Prem Chaitanya Prathi 2026-05-21 10:38:31 +05:30
parent fc6af2fdf1
commit 4365df4b56
No known key found for this signature in database
4 changed files with 33 additions and 10 deletions

View File

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

View File

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

View File

@ -21,7 +21,6 @@ import
const const
KeystorePassword = "mix-rln-password" # Must match protocol.nim KeystorePassword = "mix-rln-password" # Must match protocol.nim
DefaultUserMessageLimit = 4'u64 # R=4 slots per 10s epoch 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 # Peer IDs derived from nodekeys in config files
# config.toml: nodekey = "f98e3fba96c32e8d1967d460f1b79457380e1a895f7971cecc8528abe733781a" # config.toml: nodekey = "f98e3fba96c32e8d1967d460f1b79457380e1a895f7971cecc8528abe733781a"
@ -131,7 +130,6 @@ proc setupCredentialsAndTree() {.async.} =
echo " Keystores: rln_keystore_{peerId}.json" echo " Keystores: rln_keystore_{peerId}.json"
echo " Password: ", KeystorePassword echo " Password: ", KeystorePassword
echo " Default rate limit: ", DefaultUserMessageLimit echo " Default rate limit: ", DefaultUserMessageLimit
echo " Spammer rate limit: ", SpammerUserMessageLimit
echo "" echo ""
echo "Note: All nodes must use the same rln_tree.db file." echo "Note: All nodes must use the same rln_tree.db file."

View File

@ -29,6 +29,23 @@ logScope:
const minMixPoolSize = 4 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 type
PublishMessage* = proc(message: WakuMessage): Future[Result[void, string]] {. PublishMessage* = proc(message: WakuMessage): Future[Result[void, string]] {.
async, gcsafe, raises: [] async, gcsafe, raises: []
@ -103,12 +120,11 @@ proc new*(
peermgr.switch.peerInfo.publicKey.skkey, peermgr.switch.peerInfo.privateKey.skkey, peermgr.switch.peerInfo.publicKey.skkey, peermgr.switch.peerInfo.privateKey.skkey,
) )
let totalSlots = userMessageLimit.get(2) # Start with waku's no-RLN cover-traffic defaults. The spam-protection
let ct = ConstantRateCoverTraffic.new( # branch below overrides these from the plugin's config so cover emission
totalSlots = totalSlots, # can't outpace proof minting when RLN is on.
epochDuration = 10.seconds, var ctTotalSlots = WakuCoverTrafficTotalSlots
useInternalEpochTimer = disableSpamProtection, var ctEpochDuration = WakuCoverTrafficEpochDuration
)
var spamProtectionOpt = default(Opt[SpamProtection]) var spamProtectionOpt = default(Opt[SpamProtection])
if not disableSpamProtection: if not disableSpamProtection:
@ -120,12 +136,21 @@ proc new*(
if userMessageLimit.isSome(): if userMessageLimit.isSome():
spamProtectionConfig.userMessageLimit = userMessageLimit.get() spamProtectionConfig.userMessageLimit = userMessageLimit.get()
ctTotalSlots = spamProtectionConfig.userMessageLimit
ctEpochDuration = spamProtectionConfig.epochDurationSeconds.int.seconds
let spamProtection = newMixRlnSpamProtection(spamProtectionConfig).valueOr: let spamProtection = newMixRlnSpamProtection(spamProtectionConfig).valueOr:
return err("failed to create spam protection: " & error) return err("failed to create spam protection: " & error)
spamProtectionOpt = Opt.some(SpamProtection(spamProtection)) spamProtectionOpt = Opt.some(SpamProtection(spamProtection))
else: else:
info "mix spam protection disabled" info "mix spam protection disabled"
let ct = ConstantRateCoverTraffic.new(
totalSlots = ctTotalSlots,
epochDuration = ctEpochDuration,
useInternalEpochTimer = disableSpamProtection,
)
var mixRlnSpam: MixRlnSpamProtection var mixRlnSpam: MixRlnSpamProtection
if spamProtectionOpt.isSome(): if spamProtectionOpt.isSome():
mixRlnSpam = MixRlnSpamProtection(spamProtectionOpt.get()) mixRlnSpam = MixRlnSpamProtection(spamProtectionOpt.get())