logos-delivery/waku/node/waku_mix_coordination.nim
Prem Chaitanya Prathi a6dc13d4b1
feat(mix): integrate mix protocol with extended kademlia + RLN spam protection
Rebased poc/mix-spam-protection onto origin/master. Bundles:

- Extended kademlia discovery integration for mix node pool
  (waku/discovery/waku_kademlia.nim, tools/confutils/cli_args.nim)
- RLN spam protection plugin (vacp2p/mix-rln-spam-protection) wired in:
  WakuMix gains mixRlnSpamProtection + publishMessage callback,
  per-hop proof generation / verification, membership coordination
  via /mix/rln/metadata/v1 content topic
- chat2mix sim app: filter-subscribes to spam-protection coordination
  topic, defers publishing until mix node pool is populated
- Makefile: automated librln_mix_v2.0.0.a build via
  scripts/build_rln_mix.sh and mix-librln target
- simulations/mixnet: 5-node mixnet sim infrastructure
  (config1-4.toml, run_*.sh, build_setup.sh, setup_credentials.nim,
  README, roundtrip_check.sh automated round-trip verification)

Rebase fixes:
- Plugin previously vendored as submodule; now a nimble requires entry
  pinned to logos-co/mix-rln-spam-protection-plugin@037f8e10
- waku.nimble: zlib < 0.2 pin to keep nimble lock resolution stable
  (upstream zlib HEAD bumped to 0.2.0)
- apps/chat2mix/config_chat2mix.nim: replace
  `defaultValue: parseIpAddress("...")` with IpAddress literal,
  works around confutils macro generating
  `defaultValueHelpName(): string {.raises: [].}` that violates the
  raises pragma when stringifying a parseIpAddress call
- config.nims: nimble setup --noNimblePath reordering

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 17:26:01 +05:30

126 lines
4.4 KiB
Nim

## Mix spam protection coordination via filter protocol
## This module handles filter-based subscription for spam protection coordination
## when relay is not available.
{.push raises: [].}
import chronos, chronicles, std/options
import
../waku_core,
../waku_core/topics/sharding,
../waku_filter_v2/common,
./peer_manager,
../waku_filter_v2/client,
../waku_mix/protocol
logScope:
topics = "waku node mix_coordination"
# Type aliases for callbacks to avoid circular imports
type
FilterSubscribeProc* = proc(
pubsubTopic: Option[PubsubTopic],
contentTopics: seq[ContentTopic],
peer: RemotePeerInfo,
): Future[FilterSubscribeResult] {.async, gcsafe.}
FilterPingProc* =
proc(peer: RemotePeerInfo): Future[FilterSubscribeResult] {.async, gcsafe.}
# Forward declaration
proc subscribeSpamProtectionViaFilter(
wakuMix: WakuMix,
peerManager: PeerManager,
filterClient: WakuFilterClient,
filterSubscribe: FilterSubscribeProc,
contentTopics: seq[ContentTopic],
) {.async.}
proc setupSpamProtectionViaFilter*(
wakuMix: WakuMix,
peerManager: PeerManager,
filterClient: WakuFilterClient,
filterSubscribe: FilterSubscribeProc,
) =
## Set up filter-based spam protection coordination.
## Registers message handler and spawns subscription maintenance task.
let spamTopics = wakuMix.getSpamProtectionContentTopics()
if spamTopics.len == 0:
return
info "Relay not available, subscribing to spam protection via filter",
topics = spamTopics
# Register handler for spam protection messages
filterClient.registerPushHandler(
proc(pubsubTopic: PubsubTopic, message: WakuMessage) {.async, gcsafe.} =
if message.contentTopic in spamTopics:
await wakuMix.handleMessage(pubsubTopic, message)
)
# Wait for filter peer to be available and maintain subscription
asyncSpawn subscribeSpamProtectionViaFilter(
wakuMix, peerManager, filterClient, filterSubscribe, spamTopics
)
proc subscribeSpamProtectionViaFilter(
wakuMix: WakuMix,
peerManager: PeerManager,
filterClient: WakuFilterClient,
filterSubscribe: FilterSubscribeProc,
contentTopics: seq[ContentTopic],
) {.async.} =
## Subscribe to spam protection topics via filter and maintain the subscription.
## Waits for a filter peer to be available before subscribing.
## Continuously monitors the subscription health with periodic pings.
const RetryInterval = chronos.seconds(5)
const SubscriptionMaintenance = chronos.seconds(30)
const MaxFailedSubscribes = 3
var currentFilterPeer: Option[RemotePeerInfo] = none(RemotePeerInfo)
var noFailedSubscribes = 0
while true:
# Select or reuse filter peer
if currentFilterPeer.isNone():
let filterPeerOpt = peerManager.selectPeer(WakuFilterSubscribeCodec)
if filterPeerOpt.isNone():
debug "No filter peer available yet for spam protection, retrying..."
await sleepAsync(RetryInterval)
continue
currentFilterPeer = some(filterPeerOpt.get())
info "Selected filter peer for spam protection",
peer = currentFilterPeer.get().peerId
# Check if subscription is still alive with ping
let pingErr = (await filterClient.ping(currentFilterPeer.get())).errorOr:
# Subscription is alive, wait before next check
await sleepAsync(SubscriptionMaintenance)
if noFailedSubscribes > 0:
noFailedSubscribes = 0
continue
# Subscription lost, need to re-subscribe
warn "Spam protection filter subscription ping failed, re-subscribing",
error = pingErr, peer = currentFilterPeer.get().peerId
# Subscribe to spam protection topics
let res =
await filterSubscribe(none(PubsubTopic), contentTopics, currentFilterPeer.get())
if res.isErr():
noFailedSubscribes += 1
warn "Failed to subscribe to spam protection topics via filter",
error = res.error, topics = contentTopics, failCount = noFailedSubscribes
if noFailedSubscribes >= MaxFailedSubscribes:
# Try with a different peer
warn "Max subscription failures reached, selecting new filter peer"
currentFilterPeer = none(RemotePeerInfo)
noFailedSubscribes = 0
await sleepAsync(RetryInterval)
else:
info "Successfully subscribed to spam protection topics via filter",
topics = contentTopics, peer = currentFilterPeer.get().peerId
noFailedSubscribes = 0
await sleepAsync(SubscriptionMaintenance)