diff --git a/.gitignore b/.gitignore index bfd8f269f3..76c7772974 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,11 @@ # Executables shall be put in an ignored build/ directory /build +# Test binaries (built by `nim c tests/...nim` for local debug compile) +/tests/all_tests_common +/tests/all_tests_waku +/tests/all_tests_wakunode2 + # Generated Files *.generated.nim @@ -41,6 +46,7 @@ node_modules/ # RLN / keystore rlnKeystore.json +rln_keystore*.json *.tar.gz # sqlite db @@ -91,3 +97,6 @@ nimbledeps # Python bytecode from tests/simulator __pycache__/ *.pyc + +# sim driver script (local dev tool, not part of build/CI) +simulations/mixnet/roundtrip_check.sh diff --git a/apps/chat2mix/chat2mix.nim b/apps/chat2mix/chat2mix.nim index 4d2d088375..ee1886fcbb 100644 --- a/apps/chat2mix/chat2mix.nim +++ b/apps/chat2mix/chat2mix.nim @@ -50,6 +50,8 @@ import common/utils/nat, waku_store/common, waku_filter_v2/client, + waku_filter_v2/common as filter_common, + waku_mix/protocol, common/logging, ], ./config_chat2mix @@ -60,6 +62,99 @@ import ../../logos_delivery/waku/rln logScope: topics = "chat2 mix" +######################### +## Mix Spam Protection ## +######################### + +# Forward declaration +proc maintainSpamProtectionSubscription( + node: WakuNode, contentTopics: seq[ContentTopic] +) {.async.} + +proc setupMixSpamProtectionViaFilter(node: WakuNode): Future[void] = + ## Registers the spam-protection push handler and returns the long-lived + ## subscription-maintenance future so the caller can cancel it on shutdown. + # Register message handler for spam protection coordination + let spamTopics = node.wakuMix.getSpamProtectionContentTopics() + + proc handleSpamMessage(pubsubTopic: PubsubTopic, message: WakuMessage) {.async.} = + await node.wakuMix.handleMessage(pubsubTopic, message) + + node.wakuFilterClient.registerPushHandler(handleSpamMessage) + + # Wait for filter peer and maintain subscription + maintainSpamProtectionSubscription(node, spamTopics) + +proc maintainSpamProtectionSubscription( + node: WakuNode, contentTopics: seq[ContentTopic] +) {.async.} = + 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 = node.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 node.wakuFilterClient.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 + + # Determine pubsub topic from content topics (using auto-sharding) + if node.wakuAutoSharding.isNone(): + error "Auto-sharding not configured, cannot determine pubsub topic for spam protection" + await sleepAsync(RetryInterval) + continue + + let shardRes = node.wakuAutoSharding.get().getShard(contentTopics[0]) + if shardRes.isErr(): + error "Failed to determine shard for spam protection", error = shardRes.error + await sleepAsync(RetryInterval) + continue + + let shard = shardRes.get() + let pubsubTopic: PubsubTopic = shard # converter toPubsubTopic + + # Subscribe to spam protection topics + let res = await node.wakuFilterClient.subscribe( + currentFilterPeer.get(), pubsubTopic, contentTopics + ) + 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) + const Help = """ Commands: /[?|help|connect|nick|exit] help: Prints this help @@ -81,6 +176,7 @@ type Chat = ref object prompt: bool # chat prompt is showing contentTopic: string # default content topic for chat messages conf: Chat2Conf # configuration for chat2 + mixSpamProtectionFut: Future[void] # mix spam-protection filter maintenance loop type PrivateKey* = crypto.PrivateKey @@ -212,7 +308,6 @@ proc publish(c: Chat, line: string) {.async.} = try: if not c.node.wakuLightpushClient.isNil(): # Attempt lightpush with mix - ( waitFor c.node.lightpushPublish( Opt.some(c.conf.getPubsubTopic(c.node, c.contentTopic)), @@ -224,8 +319,10 @@ proc publish(c: Chat, line: string) {.async.} = error "failed to publish lightpush message", error = error else: error "failed to publish message as lightpush client is not initialized" + echo "Error: lightpush client is not initialized" except CatchableError: error "caught error publishing message: ", error = getCurrentExceptionMsg() + echo "Error: " & getCurrentExceptionMsg() # TODO This should read or be subscribe handler subscribe proc readAndPrint(c: Chat) {.async.} = @@ -275,6 +372,9 @@ proc writeAndPrint(c: Chat) {.async.} = elif line.startsWith("/exit"): echo "quitting..." + if not c.mixSpamProtectionFut.isNil(): + await c.mixSpamProtectionFut.cancelAndWait() + try: await c.node.stop() except: @@ -454,7 +554,11 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} = error "failed to generate mix key pair", error = error return - (await node.mountMix(conf.clusterId, mixPrivKey, conf.mixnodes)).isOkOr: + ( + await node.mountMix( + conf.clusterId, mixPrivKey, conf.mixnodes, some(conf.rlnUserMessageLimit) + ) + ).isOkOr: error "failed to mount waku mix protocol: ", error = $error quit(QuitFailure) @@ -486,6 +590,11 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} = #await node.mountRendezvousClient(conf.clusterId) + # Subscribe to spam protection coordination topics via filter since chat2mix doesn't use relay + var mixSpamProtectionFut: Future[void] = nil + if not node.wakuFilterClient.isNil(): + mixSpamProtectionFut = setupMixSpamProtectionViaFilter(node) + await node.start() node.peerManager.start() @@ -507,6 +616,7 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} = prompt: false, contentTopic: conf.contentTopic, conf: conf, + mixSpamProtectionFut: mixSpamProtectionFut, ) var dnsDiscoveryUrl = Opt.none(string) diff --git a/apps/chat2mix/config_chat2mix.nim b/apps/chat2mix/config_chat2mix.nim index ed7d5c0450..fc2c37cc2b 100644 --- a/apps/chat2mix/config_chat2mix.nim +++ b/apps/chat2mix/config_chat2mix.nim @@ -240,6 +240,13 @@ type name: "kad-bootstrap-node" .}: seq[string] + ## RLN spam protection config + rlnUserMessageLimit* {. + desc: "Maximum messages per epoch for RLN spam protection.", + defaultValue: 100, + name: "rln-user-message-limit" + .}: int + proc parseCmdArg*(T: type MixNodePubInfo, p: string): T = let elements = p.split(":") if elements.len != 2: diff --git a/logos_delivery.nimble b/logos_delivery.nimble index 275bd1a898..adf39dfe43 100644 --- a/logos_delivery.nimble +++ b/logos_delivery.nimble @@ -28,7 +28,7 @@ requires "nim >= 2.2.4", "toml_serialization", "faststreams", # Networking & P2P - "https://github.com/vacp2p/nim-libp2p.git#v2.0.0", + "libp2p == 2.1.4", "eth", "nat_traversal", "dnsdisc", @@ -62,14 +62,16 @@ requires "nim >= 2.2.4", # Packages not on nimble (use git URLs) requires "https://github.com/logos-messaging/nim-ffi#v0.1.3" +requires "https://github.com/logos-co/mix-rln-spam-protection-plugin.git#135182b72c16d3bd9c2d06087d84303272e4d1eb" + +requires "https://github.com/logos-co/nim-libp2p-mix.git#c387ca67cf477dc53ec6228027c45d8eda067917" requires "https://github.com/logos-messaging/nim-sds.git#b12f5ee07c5b764303b51fb948b32a4ade1de3b5" requires "https://github.com/NagyZoltanPeter/nim-brokers.git#v3.1.4" -requires "https://github.com/vacp2p/nim-lsquic.git#v0.5.1" +requires "https://github.com/vacp2p/nim-lsquic.git#v0.5.6" requires "https://github.com/vacp2p/nim-jwt.git#057ec95eb5af0eea9c49bfe9025b3312c95dc5f2" -requires "https://github.com/logos-co/nim-libp2p-mix#380513117d556bf8f70066f5e72a7fd74fe36ba6" proc getMyCPU(): string = ## Need to set cpu more explicit manner to avoid arch issues between dependencies diff --git a/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim b/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim index 57c7f1c4f9..721540ce21 100644 --- a/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim +++ b/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim @@ -3,7 +3,7 @@ ## import results, std/[tables, sequtils, sets] -import chronos, chronicles, libp2p/utility +import chronos, chronicles, libp2p/utils/shortlog import brokers/broker_context import logos_delivery/waku/[waku_core, waku_core/topics, waku_store/common], diff --git a/logos_delivery/messaging/delivery_service/send_service/send_service.nim b/logos_delivery/messaging/delivery_service/send_service/send_service.nim index 097bb07527..b3df45afb8 100644 --- a/logos_delivery/messaging/delivery_service/send_service/send_service.nim +++ b/logos_delivery/messaging/delivery_service/send_service/send_service.nim @@ -2,7 +2,7 @@ ## import std/[sequtils, tables, typetraits] -import chronos, chronicles, libp2p/utility +import chronos, chronicles, libp2p/utils/shortlog import brokers/broker_context import ./[send_processor, relay_processor, lightpush_processor, delivery_task], diff --git a/logos_delivery/waku/common/rate_limit/request_limiter.nim b/logos_delivery/waku/common/rate_limit/request_limiter.nim index 68e5e31202..fdc50be8c2 100644 --- a/logos_delivery/waku/common/rate_limit/request_limiter.nim +++ b/logos_delivery/waku/common/rate_limit/request_limiter.nim @@ -16,7 +16,12 @@ {.push raises: [].} import - results, std/math, chronicles, chronos/timer, libp2p/stream/connection, libp2p/utility + results, + std/math, + chronicles, + chronos/timer, + libp2p/stream/connection, + libp2p/utils/opt import std/times except TimeInterval, Duration, seconds, minutes diff --git a/logos_delivery/waku/common/rate_limit/single_token_limiter.nim b/logos_delivery/waku/common/rate_limit/single_token_limiter.nim index 954c8445f0..8ef0f72d7e 100644 --- a/logos_delivery/waku/common/rate_limit/single_token_limiter.nim +++ b/logos_delivery/waku/common/rate_limit/single_token_limiter.nim @@ -2,7 +2,7 @@ {.push raises: [].} -import results, chronos/timer, libp2p/stream/connection, libp2p/utility +import results, chronos/timer, libp2p/stream/connection import std/times except TimeInterval, Duration diff --git a/logos_delivery/waku/common/rate_limit/timed_map.nim b/logos_delivery/waku/common/rate_limit/timed_map.nim index b9a5c4cbf9..5bab16de7b 100644 --- a/logos_delivery/waku/common/rate_limit/timed_map.nim +++ b/logos_delivery/waku/common/rate_limit/timed_map.nim @@ -14,7 +14,6 @@ import std/[hashes, sets] import chronos/timer, results -import libp2p/utility export results diff --git a/logos_delivery/waku/factory/node_factory.nim b/logos_delivery/waku/factory/node_factory.nim index 5cd06b4572..c4d4890661 100644 --- a/logos_delivery/waku/factory/node_factory.nim +++ b/logos_delivery/waku/factory/node_factory.nim @@ -183,17 +183,6 @@ proc setupProtocols( node.mountKademlia(kadConf).isOkOr: return err("failed to setup service discovery: " & error) - # Register ServicePeersRequest provider - ServicePeersRequest.setProvider( - node.brokerCtx, - proc(serviceId: string): Future[Result[ServicePeersRequest, string]] {.async.} = - let peers = (await node.wakuKademlia.lookupServicePeers(serviceId)).valueOr: - return err("failed call to lookupServicePeers: " & error) - return ok(ServicePeersRequest(serviceId: serviceId, peers: peers)), - ).isOkOr: - error "Can't set provider for ServicePeersRequest", error = error - return err("Can't set provider for ServicePeersRequest: " & error) - if conf.storeServiceConf.isSome(): let storeServiceConf = conf.storeServiceConf.get() diff --git a/logos_delivery/waku/node/subscription_manager.nim b/logos_delivery/waku/node/subscription_manager.nim index 2a0d5cbac5..c914f22443 100644 --- a/logos_delivery/waku/node/subscription_manager.nim +++ b/logos_delivery/waku/node/subscription_manager.nim @@ -11,6 +11,7 @@ import node/node_telemetry, waku_relay, waku_archive, + waku_mix, waku_store_sync, waku_filter_v2/common as filter_common, waku_filter_v2/client as filter_client, @@ -66,6 +67,12 @@ proc registerRelayHandler( node.wakuStoreReconciliation.messageIngress(topic, msg) + proc mixHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} = + if node.wakuMix.isNil(): + return + + await node.wakuMix.handleMessage(topic, msg) + proc internalHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} = MessageSeenEvent.emit(node.brokerCtx, topic, msg) @@ -76,6 +83,7 @@ proc registerRelayHandler( await filterHandler(topic, msg) await archiveHandler(topic, msg) await syncHandler(topic, msg) + await mixHandler(topic, msg) await internalHandler(topic, msg) if node.legacyAppHandlers.hasKey(topic) and not node.legacyAppHandlers[topic].isNil(): diff --git a/logos_delivery/waku/node/waku_node.nim b/logos_delivery/waku/node/waku_node.nim index b1448eb2a3..2001873f80 100644 --- a/logos_delivery/waku/node/waku_node.nim +++ b/logos_delivery/waku/node/waku_node.nim @@ -21,7 +21,6 @@ import libp2p/transports/transport, libp2p/transports/tcptransport, libp2p/transports/wstransport, - libp2p/utility, libp2p/utils/offsettedseq, libp2p_mix, libp2p_mix/mix_protocol, @@ -60,6 +59,7 @@ import requests/health_requests, api/events/health_events, api/events/peer_events, + api/events/discovery_events, ], logos_delivery/api/events/kernel_events, # MessageSeenEvent logos_delivery/waku/discovery/waku_kademlia, @@ -122,6 +122,7 @@ type libp2pPing*: Ping rng*: crypto.Rng brokerCtx*: BrokerContext + mixTopUpLoop: Future[void] wakuRendezvous*: WakuRendezVous wakuRendezvousClient*: rendezvous_client.WakuRendezVousClient announcedAddresses*: seq[MultiAddress] @@ -190,6 +191,18 @@ proc getShardsGetter(node: WakuNode, configuredShards: seq[uint16]): GetShards = return shards return configuredShards +proc getRelayMixHandler*(node: WakuNode): Option[WakuRelayHandler] = + ## Returns a handler for mix spam protection coordination messages if mix is mounted + if node.wakuMix.isNil(): + return none(WakuRelayHandler) + + let handler: WakuRelayHandler = proc( + pubsubTopic: PubsubTopic, message: WakuMessage + ): Future[void] {.async, gcsafe.} = + await node.wakuMix.handleMessage(pubsubTopic, message) + + return some(handler) + proc getCapabilitiesGetter(node: WakuNode): GetCapabilities = return proc(): seq[Capabilities] {.closure, gcsafe, raises: [].} = if node.wakuRelay.isNil(): @@ -313,6 +326,7 @@ proc mountMix*( clusterId: uint16, mixPrivKey: Curve25519Key, mixnodes: seq[MixNodePubInfo], + userMessageLimit: Option[int] = none(int), ): Future[Result[void, string]] {.async.} = info "mounting mix protocol", nodeId = node.info #TODO log the config used @@ -323,8 +337,30 @@ proc mountMix*( return err("Failed to convert multiaddress to string.") info "local addr", localaddr = localaddrStr + # Create callback to publish coordination messages via relay + let publishMessage: PublishMessage = proc( + message: WakuMessage + ): Future[Result[void, string]] {.async.} = + # Inline implementation of publish logic to avoid circular import + if node.wakuRelay.isNil(): + return err("WakuRelay not mounted") + + # Derive pubsub topic from content topic using auto sharding + let pubsubTopic = + if node.wakuAutoSharding.isNone(): + return err("Auto sharding not configured") + else: + node.wakuAutoSharding.get().getShard(message.contentTopic).valueOr: + return err("Autosharding error: " & error) + + # Publish via relay + (await node.wakuRelay.publish(pubsubTopic, message)).isOkOr: + return err("publish failed in relay: " & $error) + return ok() + node.wakuMix = WakuMix.new( - localaddrStr, node.peerManager, clusterId, mixPrivKey, mixnodes + localaddrStr, node.peerManager, clusterId, mixPrivKey, mixnodes, publishMessage, + userMessageLimit, ).valueOr: error "Waku Mix protocol initialization failed", err = error return @@ -334,6 +370,7 @@ proc mountMix*( node.switch.mount(node.wakuMix) catchRes.isOkOr: return err(error.msg) + return ok() proc mountKademlia*( @@ -359,6 +396,22 @@ proc mountKademlia*( return ok() +proc runServicePeerTopUp( + node: WakuNode, serviceId: string, target: int, interval: Duration +) {.async.} = + ## Adaptive service-peer discovery: while the mix node pool is below `target`, + ## pull more providers for `serviceId` through the broker. The registered + ## ServicePeersRequest provider (kademlia) performs the lookup and fills the + ## pool. + debug "service peer top-up loop started", serviceId, target, interval = $interval + while true: + await sleepAsync(interval) + if node.getMixNodePoolSize() >= target: + continue + (await ServicePeersRequest.request(node.brokerCtx, serviceId)).isOkOr: + debug "service peer top-up request failed", serviceId, error = error + continue + ## Waku Sync proc mountStoreSync*( @@ -585,10 +638,22 @@ proc startProvidersAndListeners*(node: WakuNode) = ).isOkOr: error "Can't set provider for RequestContentTopicsHealth", error = error + # Service-peer lookups are answered by kademlia; register only when it's mounted. + if not node.wakuKademlia.isNil(): + ServicePeersRequest.setProvider( + node.brokerCtx, + proc(serviceId: string): Future[Result[ServicePeersRequest, string]] {.async.} = + let peers = (await node.wakuKademlia.lookupServicePeers(serviceId)).valueOr: + return err("failed call to lookupServicePeers: " & error) + return ok(ServicePeersRequest(serviceId: serviceId, peers: peers)), + ).isOkOr: + error "Can't set provider for ServicePeersRequest", error = error + proc stopProvidersAndListeners*(node: WakuNode) = RequestRelayShard.clearProvider(node.brokerCtx) RequestContentTopicsHealth.clearProvider(node.brokerCtx) RequestShardTopicsHealth.clearProvider(node.brokerCtx) + ServicePeersRequest.clearProvider(node.brokerCtx) proc start*(node: WakuNode) {.async.} = ## Starts a created Waku Node and @@ -620,15 +685,34 @@ proc start*(node: WakuNode) {.async.} = ## NOTE: This will dispatch gossipsub start to the WakuRelay.start method override await node.switch.start() + # Explicit start is required: switch.start() dispatches only as far as + # MixProtocol.start (LPProtocol flag + cover traffic). WakuMix.start, which + # performs the RLN spam-protection init()/loadTree(), is not reached by that + # dispatch, so removing this silently disables spam protection. + if not node.wakuMix.isNil(): + await node.wakuMix.start() + # Reconnect to known relay peers in the background; it waits a prune backoff # and must not block startup. node.relayReconnectFut = node.reconnectRelayPeers() + # Kick off the DoS-protection registration broadcast now that peers are + # reconnected. Fire-and-forget: the proc returns immediately and an + # internal background task retries until the broadcast lands. + if not node.wakuMix.isNil(): + node.wakuMix.registerDoSProtectionWithNetwork() + node.started = true if not node.wakuKademlia.isNil(): await node.wakuKademlia.start() + # Keep the mix pool filled: top up mix service peers via the broker while + # the pool is below the minimum size. + if not node.wakuMix.isNil(): + node.mixTopUpLoop = + node.runServicePeerTopUp(MixProtocolID, minMixPoolSize, chronos.seconds(5)) + if not node.wakuFilterClient.isNil(): node.wakuFilterClient.registerPushHandler( proc(pubsubTopic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} = @@ -659,6 +743,10 @@ proc stop*(node: WakuNode) {.async.} = node.stopProvidersAndListeners() + if not node.mixTopUpLoop.isNil(): + await node.mixTopUpLoop.cancelAndWait() + node.mixTopUpLoop = nil + if not node.wakuKademlia.isNil(): await node.wakuKademlia.stop() diff --git a/logos_delivery/waku/node/waku_node/filter.nim b/logos_delivery/waku/node/waku_node/filter.nim index 80557a3282..ef38a6916c 100644 --- a/logos_delivery/waku/node/waku_node/filter.nim +++ b/logos_delivery/waku/node/waku_node/filter.nim @@ -16,8 +16,7 @@ import libp2p/protocols/pubsub/rpc/messages, libp2p/builders, libp2p/transports/tcptransport, - libp2p/transports/wstransport, - libp2p/utility + libp2p/transports/wstransport import ../waku_node, diff --git a/logos_delivery/waku/node/waku_node/lightpush.nim b/logos_delivery/waku/node/waku_node/lightpush.nim index ff47796fcc..f7510992d6 100644 --- a/logos_delivery/waku/node/waku_node/lightpush.nim +++ b/logos_delivery/waku/node/waku_node/lightpush.nim @@ -16,7 +16,6 @@ import libp2p/builders, libp2p/transports/tcptransport, libp2p/transports/wstransport, - libp2p/utility, libp2p_mix import diff --git a/logos_delivery/waku/node/waku_node/peer_exchange.nim b/logos_delivery/waku/node/waku_node/peer_exchange.nim index b77ef3caca..fd282cfda6 100644 --- a/logos_delivery/waku/node/waku_node/peer_exchange.nim +++ b/logos_delivery/waku/node/waku_node/peer_exchange.nim @@ -14,8 +14,7 @@ import libp2p/protocols/pubsub/rpc/messages, libp2p/builders, libp2p/transports/tcptransport, - libp2p/transports/wstransport, - libp2p/utility + libp2p/transports/wstransport import ../waku_node, diff --git a/logos_delivery/waku/node/waku_node/ping.nim b/logos_delivery/waku/node/waku_node/ping.nim index 20562b8a92..bedbaac03d 100644 --- a/logos_delivery/waku/node/waku_node/ping.nim +++ b/logos_delivery/waku/node/waku_node/ping.nim @@ -7,8 +7,7 @@ import results, libp2p/protocols/ping, libp2p/builders, - libp2p/transports/tcptransport, - libp2p/utility + libp2p/transports/tcptransport import ../waku_node, ../peer_manager diff --git a/logos_delivery/waku/node/waku_node/relay.nim b/logos_delivery/waku/node/waku_node/relay.nim index 715c6e2a90..46c3d27254 100644 --- a/logos_delivery/waku/node/waku_node/relay.nim +++ b/logos_delivery/waku/node/waku_node/relay.nim @@ -17,7 +17,6 @@ import libp2p/builders, libp2p/transports/tcptransport, libp2p/transports/wstransport, - libp2p/utility, brokers/broker_context import @@ -29,6 +28,7 @@ import waku_archive, waku_store_sync, rln, + waku_mix, node/waku_node, node/subscription_manager, node/peer_manager, diff --git a/logos_delivery/waku/node/waku_node/store.nim b/logos_delivery/waku/node/waku_node/store.nim index 92d236a37b..1a8a7cb960 100644 --- a/logos_delivery/waku/node/waku_node/store.nim +++ b/logos_delivery/waku/node/waku_node/store.nim @@ -13,8 +13,7 @@ import libp2p/protocols/pubsub/rpc/messages, libp2p/builders, libp2p/transports/tcptransport, - libp2p/transports/wstransport, - libp2p/utility + libp2p/transports/wstransport import ../waku_node, diff --git a/logos_delivery/waku/waku_mix/protocol.nim b/logos_delivery/waku/waku_mix/protocol.nim index 3ab5bfd666..fe5528c660 100644 --- a/logos_delivery/waku/waku_mix/protocol.nim +++ b/logos_delivery/waku/waku_mix/protocol.nim @@ -1,6 +1,6 @@ {.push raises: [].} -import chronicles, chronos, results, metrics +import chronicles, std/options, chronos, results, metrics import libp2p/crypto/curve25519, @@ -10,25 +10,39 @@ import libp2p_mix/mix_protocol, libp2p_mix/mix_metrics, libp2p_mix/delay_strategy, - libp2p/[multiaddress, peerid], + libp2p_mix/spam_protection, + libp2p/[multiaddress, multicodec, peerid, peerinfo], eth/common/keys import logos_delivery/waku/node/peer_manager, logos_delivery/waku/waku_core, logos_delivery/waku/waku_enr, - logos_delivery/waku/node/peer_manager/waku_peer_store + logos_delivery/waku/node/peer_manager/waku_peer_store, + mix_rln_spam_protection, + logos_delivery/waku/waku_relay, + logos_delivery/waku/common/nimchronos logScope: topics = "waku mix" -const minMixPoolSize = 4 +const minMixPoolSize* = 4 type + PublishMessage* = proc(message: WakuMessage): Future[Result[void, string]] {. + async, gcsafe, raises: [] + .} + WakuMix* = ref object of MixProtocol peerManager*: PeerManager clusterId: uint16 pubKey*: Curve25519Key + mixRlnSpamProtection*: MixRlnSpamProtection + publishMessage*: PublishMessage + dosRegistrationTask: Future[void] + ## Background task that retries DoS-protection self-registration until + ## it succeeds. nil until kicked off via registerDoSProtectionWithNetwork; + ## cancelled in stop(). WakuMixResult*[T] = Result[T, string] @@ -41,11 +55,9 @@ proc processBootNodes( ) = var count = 0 for node in bootnodes: - let pInfo = parsePeerInfo(node.multiAddr).valueOr: - error "Failed to get peer id from multiaddress: ", - error = error, multiAddr = $node.multiAddr + let (peerId, networkAddr) = parseFullAddress(node.multiAddr).valueOr: + error "Failed to parse multiaddress", multiAddr = node.multiAddr, error = error continue - let peerId = pInfo.peerId var peerPubKey: crypto.PublicKey if not peerId.extractPublicKey(peerPubKey): warn "Failed to extract public key from peerId, skipping node", peerId = peerId @@ -65,10 +77,10 @@ proc processBootNodes( count.inc() peermgr.addPeer( - RemotePeerInfo.init(peerId, @[multiAddr], mixPubKey = Opt.some(node.pubKey)) + RemotePeerInfo.init(peerId, @[networkAddr], mixPubKey = Opt.some(node.pubKey)) ) mix_pool_size.set(count) - info "using mix bootstrap nodes ", count = count + debug "using mix bootstrap nodes ", count = count proc new*( T: typedesc[WakuMix], @@ -77,9 +89,11 @@ proc new*( clusterId: uint16, mixPrivKey: Curve25519Key, bootnodes: seq[MixNodePubInfo], + publishMessage: PublishMessage, + userMessageLimit: Option[int] = none(int), ): WakuMixResult[T] = let mixPubKey = public(mixPrivKey) - info "mixPubKey", mixPubKey = mixPubKey + trace "mixPubKey", mixPubKey = mixPubKey let nodeMultiAddr = MultiAddress.init(nodeAddr).valueOr: return err("failed to parse mix node address: " & $nodeAddr & ", error: " & error) let localMixNodeInfo = initMixNodeInfo( @@ -87,13 +101,34 @@ proc new*( peermgr.switch.peerInfo.publicKey.skkey, peermgr.switch.peerInfo.privateKey.skkey, ) - var m = WakuMix(peerManager: peermgr, clusterId: clusterId, pubKey: mixPubKey) + # 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 spamProtection = MixRlnSpamProtection.new(spamProtectionConfig).valueOr: + return err("failed to create spam protection: " & error) + + var m = WakuMix( + peerManager: peermgr, + clusterId: clusterId, + pubKey: mixPubKey, + mixRlnSpamProtection: spamProtection, + publishMessage: publishMessage, + ) procCall MixProtocol(m).init( localMixNodeInfo, peermgr.switch, + spamProtection = Opt.some(SpamProtection(spamProtection)), delayStrategy = Opt.some( DelayStrategy( - ExponentialDelayStrategy.new(meanDelay = 50'u16, rng = crypto.newRng()) + ExponentialDelayStrategy.new(meanDelay = 100, rng = crypto.newRng()) ) ), ) @@ -102,9 +137,209 @@ proc new*( if m.nodePool.len < minMixPoolSize: warn "publishing with mix won't work until atleast 3 mix nodes in node pool" + return ok(m) proc poolSize*(mix: WakuMix): int = mix.nodePool.len +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.publishMessage.isNil(): + warn "PublishMessage callback not available, spam protection coordination disabled" + return + + let publishCallback: PublishCallback = proc( + contentTopic: string, data: seq[byte] + ): Future[Result[void, string]] {.async.} = + # Create a WakuMessage for the coordination data + let msg = WakuMessage( + payload: data, + contentTopic: contentTopic, + ephemeral: true, # Coordination messages don't need to be stored + timestamp: getNowInNanosecondTime(), + ) + + # Delegate to node's publish API which handles topic derivation and relay publishing + let res = await mix.publishMessage(msg) + if res.isErr(): + warn "Failed to publish spam protection coordination message", + contentTopic = contentTopic, error = res.error + return err(res.error) + + trace "Published spam protection coordination message", contentTopic = contentTopic + return ok() + + mix.mixRlnSpamProtection.setPublishCallback(publishCallback) + trace "Spam protection publish callback configured" + +proc handleMessage*( + mix: WakuMix, pubsubTopic: PubsubTopic, message: WakuMessage +) {.async.} = + ## Handle incoming messages for spam protection coordination. + ## This should be called from the relay handler for coordination content topics. + if mix.mixRlnSpamProtection.isNil(): + return + + let contentTopic = message.contentTopic + + if contentTopic == mix.mixRlnSpamProtection.getMembershipContentTopic(): + # Handle membership update + (await mix.mixRlnSpamProtection.handleMembershipUpdate(message.payload)).isOkOr: + warn "Failed to handle membership update", error = error + return + trace "Handled membership update" + + # Persist tree after membership changes (temporary solution) + # TODO: Replace with proper persistence strategy (e.g., periodic snapshots) + mix.mixRlnSpamProtection.saveTree().isOkOr: + debug "Failed to save tree after membership update", error = error + return + trace "Saved tree after membership update" + elif contentTopic == mix.mixRlnSpamProtection.getProofMetadataContentTopic(): + # Handle proof metadata for network-wide spam detection + mix.mixRlnSpamProtection.handleProofMetadata(message.payload).isOkOr: + warn "Failed to handle proof metadata", error = error + return + trace "Handled proof metadata" + +proc getSpamProtectionContentTopics*(mix: WakuMix): seq[string] = + ## Get the content topics used by spam protection for coordination. + ## Use these to set up relay subscriptions. + if mix.mixRlnSpamProtection.isNil(): + return @[] + return mix.mixRlnSpamProtection.getContentTopics() + +proc saveSpamProtectionTree*(mix: WakuMix): Result[void, string] = + ## Save the spam protection membership tree to disk. + ## This allows preserving the tree state across restarts. + if mix.mixRlnSpamProtection.isNil(): + return err("Spam protection not initialized") + + mix.mixRlnSpamProtection.saveTree() + +proc loadSpamProtectionTree*(mix: WakuMix): Result[void, string] = + ## Load the spam protection membership tree from disk. + ## Call this before init() to restore tree state from previous runs. + ## TODO: This is a temporary solution. Ideally nodes should sync tree state + ## via a store query for historical membership messages or via dedicated + ## tree sync protocol. + if mix.mixRlnSpamProtection.isNil(): + return err("Spam protection not initialized") + + mix.mixRlnSpamProtection.loadTree() + +method start*(mix: WakuMix) {.async.} = + ## Local-only mix protocol initialization. Does NOT touch the network. + ## The network-dependent self-registration broadcast is handled separately + ## by registerDoSProtectionWithNetwork so that this proc can run before + ## peers are connected without blocking on relay startup. + info "starting waku mix protocol" + + if mix.mixRlnSpamProtection.isNil(): + return + + # Initialize spam protection (MixProtocol.init() does NOT call init() on the plugin) + (await mix.mixRlnSpamProtection.init()).isOkOr: + error "Failed to initialize spam protection", error = error + return + + # Load existing tree to sync with other members. + # Should be done after init() (which loads credentials) but before + # registerSelf() (which adds us to the tree). + let loadRes = mix.mixRlnSpamProtection.loadTree() + if loadRes.isErr: + debug "No existing tree found or failed to load, starting fresh", + error = loadRes.error + else: + debug "Loaded existing spam protection membership tree from disk" + + # Restore our credentials to the tree (after tree load, whether it succeeded or not). + # Ensures our member is in the tree if we have an index from keystore. + mix.mixRlnSpamProtection.restoreCredentialsToTree().isOkOr: + error "Failed to restore credentials to tree", error = error + + # Set up publish callback. Must be before the network-side registration so + # the plugin's groupManager.register can broadcast the membership update. + mix.setupSpamProtectionCallbacks() + + (await mix.mixRlnSpamProtection.start()).isOkOr: + error "Failed to start spam protection", error = error + + info "waku mix protocol started" + +proc dosRegistrationRetryLoop(mix: WakuMix) {.async.} = + ## Indefinitely retry the DoS-protection self-registration broadcast until + ## it succeeds (or this task is cancelled by WakuMix.stop()). For nodes that + ## already have a membership index in their keystore, registerSelf early- + ## returns and the loop exits on the first attempt. For fresh nodes, the + ## broadcast needs at least one relay peer subscribed to the membership + ## topic to land — this loop survives transient "no peers yet" failures. + ## + ## TODO: Remove once RLN membership moves on-chain. With on-chain membership + ## peers discover each other via the contract / a watcher rather than via a + ## pubsub broadcast, so the retry loop (and the whole publishCallback path + ## from registerSelf) becomes unnecessary. + ## + ## Retry pacing uses exponential backoff (5s, 10s, 20s, ..., capped at 5min) + ## so persistent misconfiguration — e.g., relay never available — degrades + ## to one log line every 5 minutes after the initial ramp instead of every + ## 5 seconds forever. + const InitialRetryDelay = chronos.seconds(5) + const MaxRetryDelay = chronos.minutes(5) + var delay = InitialRetryDelay + while true: + try: + let registerRes = await mix.mixRlnSpamProtection.registerSelf() + if registerRes.isOk(): + 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() + if saveRes.isErr: + warn "Failed to save spam protection tree", error = saveRes.error + else: + trace "Saved spam protection tree to disk" + return # success — exit the loop + warn "DoS-protection self-registration failed, retrying", + error = registerRes.error, nextDelay = delay + except CancelledError as e: + debug "DoS-protection registration loop cancelled" + raise e + except CatchableError as e: + warn "DoS-protection registration raised, retrying", + error = e.msg, nextDelay = delay + await sleepAsync(delay) + delay = min(delay * 2, MaxRetryDelay) + +proc registerDoSProtectionWithNetwork*(mix: WakuMix) = + ## Kick off an indefinite background task that broadcasts this node's + ## DoS-protection (RLN) membership registration to other mix nodes via + ## relay. Returns immediately so callers don't block on a possibly-slow + ## broadcast. The task is cancelled when WakuMix.stop() is called. + if mix.mixRlnSpamProtection.isNil(): + return + # Guard against kicking off the retry loop when the plugin isn't actually + # usable (e.g., mix.start()'s init/start steps failed). Without this check + # the loop would spin forever logging "Plugin not initialized" warnings. + if not mix.mixRlnSpamProtection.isReady(): + warn "Skipping DoS-protection registration: plugin not ready" + return + # Re-call safety: don't spawn a second loop if one is still in flight. + if not mix.dosRegistrationTask.isNil and not mix.dosRegistrationTask.finished: + debug "DoS-protection registration already in progress, skipping" + return + mix.dosRegistrationTask = mix.dosRegistrationRetryLoop() + +method stop*(mix: WakuMix) {.async.} = + # Cancel the in-flight DoS-protection registration retry loop, if any + if not mix.dosRegistrationTask.isNil and not mix.dosRegistrationTask.finished: + await mix.dosRegistrationTask.cancelAndWait() + # Stop spam protection + if not mix.mixRlnSpamProtection.isNil(): + await mix.mixRlnSpamProtection.stop() + debug "Spam protection stopped" + # Mix Protocol diff --git a/logos_delivery/waku/waku_rendezvous/protocol.nim b/logos_delivery/waku/waku_rendezvous/protocol.nim index 1fe72c803b..43936677b0 100644 --- a/logos_delivery/waku/waku_rendezvous/protocol.nim +++ b/logos_delivery/waku/waku_rendezvous/protocol.nim @@ -10,8 +10,7 @@ import libp2p/protocols/rendezvous/protobuf, libp2p/utils/offsettedseq, libp2p/crypto/curve25519, - libp2p/switch, - libp2p/utility + libp2p/switch import metrics except collect diff --git a/logos_delivery/waku/waku_store_sync/reconciliation.nim b/logos_delivery/waku/waku_store_sync/reconciliation.nim index db3574a5e7..7c49e92505 100644 --- a/logos_delivery/waku/waku_store_sync/reconciliation.nim +++ b/logos_delivery/waku/waku_store_sync/reconciliation.nim @@ -7,7 +7,6 @@ import chronicles, chronos, metrics, - libp2p/utility, libp2p/protocols/protocol, libp2p/stream/connection, libp2p/crypto/crypto, diff --git a/logos_delivery/waku/waku_store_sync/transfer.nim b/logos_delivery/waku/waku_store_sync/transfer.nim index 5d20afb186..cd3b8de0a3 100644 --- a/logos_delivery/waku/waku_store_sync/transfer.nim +++ b/logos_delivery/waku/waku_store_sync/transfer.nim @@ -6,7 +6,6 @@ import chronicles, chronos, metrics, - libp2p/utility, libp2p/protocols/protocol, libp2p/stream/connection, libp2p/crypto/crypto, diff --git a/nimble.lock b/nimble.lock index 03bb7a4340..ac1044d18c 100644 --- a/nimble.lock +++ b/nimble.lock @@ -11,106 +11,130 @@ "sha1": "68bb85cbfb1832ce4db43943911b046c3af3caab" } }, - "unittest2": { - "version": "0.2.5", - "vcsRevision": "26f2ef3ae0ec72a2a75bfe557e02e88f6a31c189", - "url": "https://github.com/status-im/nim-unittest2", + "boringssl": { + "version": "0.0.10", + "vcsRevision": "084f2c8994137a72655b72745936a05949c768cc", + "url": "https://github.com/vacp2p/nim-boringssl", "downloadMethod": "git", "dependencies": [ "nim" ], "checksums": { - "sha1": "02bb3751ba9ddc3c17bfd89f2e41cb6bfb8fc0c9" + "sha1": "4ac575c18b11c0f06d6381279843aa8508f7940e" } }, - "bearssl": { - "version": "0.2.8", - "vcsRevision": "22c6a76ce015bc07e011562bdcfc51d9446c1e82", - "url": "https://github.com/status-im/nim-bearssl", + "npeg": { + "version": "1.3.0", + "vcsRevision": "409f6796d0e880b3f0222c964d1da7de6e450811", + "url": "https://github.com/zevv/npeg", "downloadMethod": "git", "dependencies": [ - "nim", - "unittest2" + "nim" ], "checksums": { - "sha1": "da4dd7ae96d536bdaf42dca9c85d7aed024b6a86" + "sha1": "64f15c85a059c889cb11c5fe72372677c50da621" } }, - "bearssl_pkey_decoder": { - "version": "#21dd3710df9345ed2ad8bf8f882761e07863b8e0", - "vcsRevision": "21dd3710df9345ed2ad8bf8f882761e07863b8e0", - "url": "https://github.com/vacp2p/bearssl_pkey_decoder", + "results": { + "version": "0.5.1", + "vcsRevision": "df8113dda4c2d74d460a8fa98252b0b771bf1f27", + "url": "https://github.com/arnetheduck/nim-results", "downloadMethod": "git", "dependencies": [ - "nim", - "bearssl" + "nim" ], "checksums": { - "sha1": "21b42e2e6ddca6c875d3fc50f36a5115abf51714" + "sha1": "a9c011f74bc9ed5c91103917b9f382b12e82a9e7" } }, - "jwt": { - "version": "#057ec95eb5af0eea9c49bfe9025b3312c95dc5f2", - "vcsRevision": "057ec95eb5af0eea9c49bfe9025b3312c95dc5f2", - "url": "https://github.com/vacp2p/nim-jwt.git", + "nat_traversal": { + "version": "0.0.1", + "vcsRevision": "860e18c37667b5dd005b94c63264560c35d88004", + "url": "https://github.com/status-im/nim-nat-traversal", "downloadMethod": "git", "dependencies": [ "nim", - "bearssl", - "bearssl_pkey_decoder" + "results" ], "checksums": { - "sha1": "3cd368666fd2bc7f99f253452289e827abcac13c" + "sha1": "1a376d3e710590ef2c48748a546369755f0a7c97" } }, - "testutils": { - "version": "0.8.1", - "vcsRevision": "6ce5e5e2301ccbc04b09d27ff78741ff4d352b4d", - "url": "https://github.com/status-im/nim-testutils", + "unicodedb": { + "version": "0.13.2", + "vcsRevision": "66f2458710dc641dd4640368f9483c8a0ec70561", + "url": "https://github.com/nitely/nim-unicodedb", + "downloadMethod": "git", + "dependencies": [ + "nim" + ], + "checksums": { + "sha1": "739102d885d99bb4571b1955f5f12aee423c935b" + } + }, + "regex": { + "version": "0.26.3", + "vcsRevision": "4593305ed1e49731fc75af1dc572dd2559aad19c", + "url": "https://github.com/nitely/nim-regex", "downloadMethod": "git", "dependencies": [ "nim", - "unittest2" + "unicodedb" ], "checksums": { - "sha1": "96a11cf8b84fa9bd12d4a553afa1cc4b7f9df4e3" + "sha1": "4d24e7d7441137cd202e16f2359a5807ddbdc31f" } }, - "db_connector": { - "version": "0.1.0", - "vcsRevision": "29450a2063970712422e1ab857695c12d80112a6", - "url": "https://github.com/nim-lang/db_connector", + "unittest2": { + "version": "0.2.5", + "vcsRevision": "26f2ef3ae0ec72a2a75bfe557e02e88f6a31c189", + "url": "https://github.com/status-im/nim-unittest2", "downloadMethod": "git", "dependencies": [ "nim" ], "checksums": { - "sha1": "4f2e67d0e4b61af9ac5575509305660b473f01a4" + "sha1": "02bb3751ba9ddc3c17bfd89f2e41cb6bfb8fc0c9" } }, - "results": { - "version": "0.5.1", - "vcsRevision": "df8113dda4c2d74d460a8fa98252b0b771bf1f27", - "url": "https://github.com/arnetheduck/nim-results", + "bearssl": { + "version": "0.2.11", + "vcsRevision": "7c307026d4d86dc560880c6fa716f5e626ff7fc2", + "url": "https://github.com/status-im/nim-bearssl", "downloadMethod": "git", "dependencies": [ - "nim" + "nim", + "unittest2" ], "checksums": { - "sha1": "a9c011f74bc9ed5c91103917b9f382b12e82a9e7" + "sha1": "352e6d47bd46300a50b8911d8989b3b429fe3038" } }, - "nat_traversal": { - "version": "0.0.1", - "vcsRevision": "860e18c37667b5dd005b94c63264560c35d88004", - "url": "https://github.com/status-im/nim-nat-traversal", + "bearssl_pkey_decoder": { + "version": "#d34aa46bf9d0a3ffff810fbd3c4d2fa024eb9368", + "vcsRevision": "d34aa46bf9d0a3ffff810fbd3c4d2fa024eb9368", + "url": "https://github.com/vacp2p/bearssl_pkey_decoder", "downloadMethod": "git", "dependencies": [ "nim", - "results" + "bearssl" ], "checksums": { - "sha1": "1a376d3e710590ef2c48748a546369755f0a7c97" + "sha1": "8666edbcb77cb9f97c659114d57c4ba0e7ab74c3" + } + }, + "jwt": { + "version": "#057ec95eb5af0eea9c49bfe9025b3312c95dc5f2", + "vcsRevision": "057ec95eb5af0eea9c49bfe9025b3312c95dc5f2", + "url": "https://github.com/vacp2p/nim-jwt.git", + "downloadMethod": "git", + "dependencies": [ + "nim", + "bearssl", + "bearssl_pkey_decoder" + ], + "checksums": { + "sha1": "3cd368666fd2bc7f99f253452289e827abcac13c" } }, "stew": { @@ -127,9 +151,23 @@ "sha1": "db22942939773ab7d5a0f2b2668c237240c67dd6" } }, + "testutils": { + "version": "0.8.2", + "vcsRevision": "74588b8b55d116ca057431ae3a0ca359e0825fcd", + "url": "https://github.com/status-im/nim-testutils", + "downloadMethod": "git", + "dependencies": [ + "nim", + "stew", + "unittest2" + ], + "checksums": { + "sha1": "c4a67c4ca30e1b0c7198b2912409e064e011d400" + } + }, "zlib": { - "version": "0.1.0", - "vcsRevision": "e680f269fb01af2c34a2ba879ff281795a5258fe", + "version": "0.2.0", + "vcsRevision": "190246aa0bb6569781370964fa2faa474203d6dd", "url": "https://github.com/status-im/nim-zlib", "downloadMethod": "git", "dependencies": [ @@ -138,12 +176,12 @@ "results" ], "checksums": { - "sha1": "bbde4f5a97a84b450fef7d107461e5f35cf2b47f" + "sha1": "a8c0c569d82315f3ffc1249ab42b0404e84fddc3" } }, "httputils": { - "version": "0.4.1", - "vcsRevision": "f142cb2e8bd812dd002a6493b6082827bb248592", + "version": "0.4.3", + "vcsRevision": "a9ca86095e262251d8ebafa1c6504fd476c41e43", "url": "https://github.com/status-im/nim-http-utils", "downloadMethod": "git", "dependencies": [ @@ -153,12 +191,12 @@ "unittest2" ], "checksums": { - "sha1": "016774ab31c3afff9a423f7d80584905ee59c570" + "sha1": "dcdd6633bc35695c2b1ac0f0bd1c5ff4e3623cb5" } }, "chronos": { - "version": "4.2.2", - "vcsRevision": "45f43a9ad8bd8bcf5903b42f365c1c879bd54240", + "version": "4.2.3", + "vcsRevision": "447b3fd8ae0619e847bff46d823d56779e21851b", "url": "https://github.com/status-im/nim-chronos", "downloadMethod": "git", "dependencies": [ @@ -170,12 +208,12 @@ "unittest2" ], "checksums": { - "sha1": "3a4c9477df8cef20a04e4f1b54a2d74fdfc2a3d0" + "sha1": "8b2b063ee6243915ddf47e7b36f56ef7d8062c0f" } }, "metrics": { - "version": "0.2.1", - "vcsRevision": "a1296caf3ebb5f30f51a5feae7749a30df2824c2", + "version": "0.2.2", + "vcsRevision": "9f2e1d4a4164deb37603b16cedd1707408ee5955", "url": "https://github.com/status-im/nim-metrics", "downloadMethod": "git", "dependencies": [ @@ -185,12 +223,12 @@ "stew" ], "checksums": { - "sha1": "84bb09873d7677c06046f391c7b473cd2fcff8a2" + "sha1": "d64e789ba2a3b848eb7c628d728623126182c3f1" } }, "faststreams": { - "version": "0.5.0", - "vcsRevision": "ce27581a3e881f782f482cb66dc5b07a02bd615e", + "version": "0.5.1", + "vcsRevision": "50889cd16ec8771106cdd0eeea460039e8571e06", "url": "https://github.com/status-im/nim-faststreams", "downloadMethod": "git", "dependencies": [ @@ -199,7 +237,7 @@ "unittest2" ], "checksums": { - "sha1": "ee61e507b805ae1df7ec936f03f2d101b0d72383" + "sha1": "969ceb3666e807db8fe5c8df63466749822367a9" } }, "snappy": { @@ -212,15 +250,16 @@ "faststreams", "unittest2", "results", - "stew" + "stew", + "testutils" ], "checksums": { "sha1": "e572d60d6a3178c5b1cde2400c51ad771812cd3d" } }, "serialization": { - "version": "0.5.2", - "vcsRevision": "b0f2fa32960ea532a184394b0f27be37bd80248b", + "version": "0.5.3", + "vcsRevision": "4092500cea76154576539371709ae801afbd2a9d", "url": "https://github.com/status-im/nim-serialization", "downloadMethod": "git", "dependencies": [ @@ -230,7 +269,24 @@ "stew" ], "checksums": { - "sha1": "fa35c1bb76a0a02a2379fe86eaae0957c7527cb8" + "sha1": "c087d26c50da40436599163888532660d6f9e631" + } + }, + "protobuf_serialization": { + "version": "0.5.3", + "vcsRevision": "8406e7287196661614ce6a8e8be20f755376af7f", + "url": "https://github.com/status-im/nim-protobuf-serialization", + "downloadMethod": "git", + "dependencies": [ + "nim", + "stew", + "faststreams", + "serialization", + "npeg", + "unittest2" + ], + "checksums": { + "sha1": "3307412f9755f7ec2079e12cf036fc103aa130b0" } }, "toml_serialization": { @@ -264,18 +320,20 @@ } }, "cbor_serialization": { - "version": "0.3.0", - "vcsRevision": "1664160e04d153573373afddc552b9cbf6fbe4dc", + "version": "0.4.1", + "vcsRevision": "e32d61c54f69b396f47645f9b7373ea2e149013a", "url": "https://github.com/vacp2p/nim-cbor-serialization", "downloadMethod": "git", "dependencies": [ "nim", "serialization", "stew", - "results" + "npeg", + "results", + "unittest2" ], "checksums": { - "sha1": "ab126eae09a6e39c72972a6a0b83cb06a2ffe8f0" + "sha1": "4782b43d06594ac59730b3939110ba93de7d3156" } }, "json_serialization": { @@ -372,16 +430,28 @@ "sha1": "0be03a5da29fdd4409ea74a60fd0ccce882601b4" } }, + "db_connector": { + "version": "0.1.0", + "vcsRevision": "29450a2063970712422e1ab857695c12d80112a6", + "url": "https://github.com/nim-lang/db_connector", + "downloadMethod": "git", + "dependencies": [ + "nim" + ], + "checksums": { + "sha1": "4f2e67d0e4b61af9ac5575509305660b473f01a4" + } + }, "sqlite3_abi": { - "version": "3.53.0.0", - "vcsRevision": "8240e8e2819dfce1b67fa2733135d01b5cc80ae0", + "version": "3.53.3.0", + "vcsRevision": "a4d052efe81472b8d5271ece8e5a3caea90a45eb", "url": "https://github.com/arnetheduck/nim-sqlite3-abi", "downloadMethod": "git", "dependencies": [ "nim" ], "checksums": { - "sha1": "fb7a6e6f36fc4eb4dfa6634dbcbf5cd0dfd0ebf0" + "sha1": "20571756875f29ef292acf10a71c27139e6bf44e" } }, "dnsclient": { @@ -396,41 +466,35 @@ "sha1": "65262c7e533ff49d6aca5539da4bc6c6ce132f40" } }, - "unicodedb": { - "version": "0.13.2", - "vcsRevision": "66f2458710dc641dd4640368f9483c8a0ec70561", - "url": "https://github.com/nitely/nim-unicodedb", + "nimcrypto": { + "version": "0.6.4", + "vcsRevision": "721fb99ee099b632eb86dfad1f0d96ee87583774", + "url": "https://github.com/cheatfate/nimcrypto", "downloadMethod": "git", "dependencies": [ "nim" ], "checksums": { - "sha1": "739102d885d99bb4571b1955f5f12aee423c935b" + "sha1": "f9ab24fa940ed03d0fb09729a7303feb50b7eaec" } }, - "regex": { - "version": "0.26.3", - "vcsRevision": "4593305ed1e49731fc75af1dc572dd2559aad19c", - "url": "https://github.com/nitely/nim-regex", + "lsquic": { + "version": "#v0.5.6", + "vcsRevision": "b778d16d6f00b47249e2674c23f21aa26b711eb7", + "url": "https://github.com/vacp2p/nim-lsquic", "downloadMethod": "git", "dependencies": [ "nim", - "unicodedb" - ], - "checksums": { - "sha1": "4d24e7d7441137cd202e16f2359a5807ddbdc31f" - } - }, - "nimcrypto": { - "version": "0.6.4", - "vcsRevision": "721fb99ee099b632eb86dfad1f0d96ee87583774", - "url": "https://github.com/cheatfate/nimcrypto", - "downloadMethod": "git", - "dependencies": [ - "nim" + "zlib", + "stew", + "chronos", + "nimcrypto", + "unittest2", + "chronicles", + "https://github.com/vacp2p/nim-boringssl" ], "checksums": { - "sha1": "f9ab24fa940ed03d0fb09729a7303feb50b7eaec" + "sha1": "2676f1facb400ad3cfa1b338149d9c20fc5967c4" } }, "websock": { @@ -454,9 +518,9 @@ } }, "json_rpc": { - "version": "0.6.1", + "version": "#v0.6.1", "vcsRevision": "6f1fff8ba685c9192fab153a9d66484ad9066e78", - "url": "https://github.com/status-im/nim-json-rpc.git", + "url": "https://github.com/status-im/nim-json-rpc", "downloadMethod": "git", "dependencies": [ "nim", @@ -475,38 +539,87 @@ "sha1": "596db0aafcb3c83f5dba6d42993f2276e0d00eb5" } }, - "lsquic": { - "version": "0.5.1", - "vcsRevision": "2f01046bf1d513de8b5f8296c3d8bec819ab0cb9", - "url": "https://github.com/vacp2p/nim-lsquic", + "secp256k1": { + "version": "0.6.0.3.2", + "vcsRevision": "d8f1288b7c72f00be5fc2c5ea72bf5cae1eafb15", + "url": "https://github.com/status-im/nim-secp256k1", "downloadMethod": "git", "dependencies": [ "nim", - "zlib", "stew", - "chronos", + "results", + "nimcrypto" + ], + "checksums": { + "sha1": "6618ef9de17121846a8c1d0317026b0ce8584e10" + } + }, + "libp2p": { + "version": "2.1.4", + "vcsRevision": "3b5ae1da95f0614af06221be7a3bb2aeab03f4c7", + "url": "https://github.com/vacp2p/nim-libp2p", + "downloadMethod": "git", + "dependencies": [ + "nim", "nimcrypto", - "unittest2", + "bearssl", + "https://github.com/vacp2p/nim-boringssl", "chronicles", - "boringssl" + "chronos", + "metrics", + "secp256k1", + "stew", + "unittest2", + "results", + "serialization", + "lsquic", + "protobuf_serialization", + "websock", + "nat_traversal" ], "checksums": { - "sha1": "959df1a9ac2a574d6fe30a4faf37c37b443e1cfb" + "sha1": "48da49700427bf68f1bbece13f8f9b620d0abdcb" } }, - "secp256k1": { - "version": "0.6.0.3.2", - "vcsRevision": "d8f1288b7c72f00be5fc2c5ea72bf5cae1eafb15", - "url": "https://github.com/status-im/nim-secp256k1", + "libp2p_mix": { + "version": "#c387ca67cf477dc53ec6228027c45d8eda067917", + "vcsRevision": "c387ca67cf477dc53ec6228027c45d8eda067917", + "url": "https://github.com/logos-co/nim-libp2p-mix.git", "downloadMethod": "git", "dependencies": [ "nim", + "libp2p", + "chronicles", + "chronos", + "metrics", + "nimcrypto", "stew", "results", - "nimcrypto" + "unittest2" ], "checksums": { - "sha1": "6618ef9de17121846a8c1d0317026b0ce8584e10" + "sha1": "cc8808669d35cd51d425e4437c7c295e09485a0a" + } + }, + "mix_rln_spam_protection": { + "version": "#135182b72c16d3bd9c2d06087d84303272e4d1eb", + "vcsRevision": "135182b72c16d3bd9c2d06087d84303272e4d1eb", + "url": "https://github.com/logos-co/mix-rln-spam-protection-plugin.git", + "downloadMethod": "git", + "dependencies": [ + "nim", + "results", + "stew", + "chronicles", + "chronos", + "nimcrypto", + "secp256k1", + "json_serialization", + "libp2p", + "libp2p_mix" + ], + "checksums": { + "sha1": "2d16f9b98130e6e88eb5b7ab1b5108ed15dce354" } }, "eth": { @@ -561,8 +674,8 @@ } }, "dnsdisc": { - "version": "0.1.0", - "vcsRevision": "38f2e0f52c0a8f032ef4530835e519d550706d9e", + "version": "0.1.1", + "vcsRevision": "6cb1b7e3922645275043c68e476cac1501a45e55", "url": "https://github.com/status-im/nim-dnsdisc", "downloadMethod": "git", "dependencies": [ @@ -579,35 +692,7 @@ "results" ], "checksums": { - "sha1": "055b882a0f6b1d1e57a25a7af99d2e5ac6268154" - } - }, - "libp2p": { - "version": "2.0.0", - "vcsRevision": "c43199378f46d0aaf61be1cad1ee1d63e8f665d6", - "url": "https://github.com/vacp2p/nim-libp2p.git", - "downloadMethod": "git", - "dependencies": [ - "nim", - "nimcrypto", - "dnsclient", - "bearssl", - "boringssl", - "chronicles", - "chronos", - "metrics", - "secp256k1", - "stew", - "unittest2", - "results", - "serialization", - "lsquic", - "protobuf_serialization", - "websock", - "jwt" - ], - "checksums": { - "sha1": "327dc7a0cb7e9d0be3d6083841bd496c4cbc48dc" + "sha1": "6451cab35990f334a46927f49f9176579460934d" } }, "taskpools": { @@ -622,28 +707,8 @@ "sha1": "09e1b2fdad55b973724d61227971afc0df0b7a81" } }, - "sds": { - "version": "#b12f5ee07c5b764303b51fb948b32a4ade1de3b5", - "vcsRevision": "b12f5ee07c5b764303b51fb948b32a4ade1de3b5", - "url": "https://github.com/logos-messaging/nim-sds.git", - "downloadMethod": "git", - "dependencies": [ - "nim", - "chronos", - "libp2p", - "chronicles", - "stew", - "stint", - "metrics", - "results", - "ffi" - ], - "checksums": { - "sha1": "175f65038b9877cdf974b07c5f83081f810d5fbe" - } - }, "ffi": { - "version": "0.1.3", + "version": "#v0.1.3", "vcsRevision": "06111de155253b34e47ed2aaed1d61d08d62cc1b", "url": "https://github.com/logos-messaging/nim-ffi", "downloadMethod": "git", @@ -657,67 +722,26 @@ "sha1": "6f9d49375ea1dc71add55c72ac80a808f238e5b0" } }, - "boringssl": { - "version": "0.0.8", - "vcsRevision": "e77caabae78fbc9aa5b78a0a521181b077c82571", - "url": "https://github.com/vacp2p/nim-boringssl", - "downloadMethod": "git", - "dependencies": [ - "nim" - ], - "checksums": { - "sha1": "2f603bb6d70683393bbb091bf6bd325d9c52be9f" - } - }, - "protobuf_serialization": { - "version": "0.4.0", - "vcsRevision": "38d24eb3bd93e605fb88199da71d36b1ec0ad60d", - "url": "https://github.com/status-im/nim-protobuf-serialization", - "downloadMethod": "git", - "dependencies": [ - "nim", - "stew", - "faststreams", - "serialization", - "npeg", - "unittest2" - ], - "checksums": { - "sha1": "5a7a80fb8cca29e41899ce9540b74e49c874f8fd" - } - }, - "npeg": { - "version": "1.3.0", - "vcsRevision": "409f6796d0e880b3f0222c964d1da7de6e450811", - "url": "https://github.com/zevv/npeg", - "downloadMethod": "git", - "dependencies": [ - "nim" - ], - "checksums": { - "sha1": "64f15c85a059c889cb11c5fe72372677c50da621" - } - }, - "libp2p_mix": { - "version": "0.1.0", - "vcsRevision": "380513117d556bf8f70066f5e72a7fd74fe36ba6", - "url": "https://github.com/logos-co/nim-libp2p-mix", + "sds": { + "version": "#b12f5ee07c5b764303b51fb948b32a4ade1de3b5", + "vcsRevision": "", + "url": "https://github.com/logos-messaging/nim-sds.git", "downloadMethod": "git", "dependencies": [ "nim", + "chronos", "libp2p", "chronicles", - "chronos", - "metrics", - "nimcrypto", "stew", + "stint", + "metrics", "results", - "unittest2" + "ffi" ], "checksums": { - "sha1": "ccfb0f0160ac15ac970471964c730d57edacad91" + "sha1": "175f65038b9877cdf974b07c5f83081f810d5fbe" } } }, "tasks": {} -} \ No newline at end of file +} diff --git a/nix/deps.nix b/nix/deps.nix index 63b01f8af9..dd91179cf6 100644 --- a/nix/deps.nix +++ b/nix/deps.nix @@ -312,9 +312,16 @@ }; libp2p_mix = pkgs.fetchgit { - url = "https://github.com/logos-co/nim-libp2p-mix"; - rev = "380513117d556bf8f70066f5e72a7fd74fe36ba6"; - sha256 = "05zjf98nl2hxx62m9blk4yip2f31y44r5x4n98lmm5hghb7wbcpk"; + url = "https://github.com/logos-co/nim-libp2p-mix.git"; + rev = "50c4ab4fa788a33eb12a0a2cecaa708873352b58"; + sha256 = "16prk6cqhalzsvh9kaif5cdn1yadssx3h4572j58fsgm20kdrala"; + fetchSubmodules = true; + }; + + mix_rln_spam_protection = pkgs.fetchgit { + url = "https://github.com/logos-co/mix-rln-spam-protection-plugin.git"; + rev = "61ee3e5aacb6b224b70e164ef7d0a5714fe66b26"; + sha256 = "0j68v3a8vwrrdpcfmabzdlx867nh4lf9flxvfrzq3xs03m2si57h"; fetchSubmodules = true; }; diff --git a/simulations/mixnet/README.md b/simulations/mixnet/README.md index fcc67b6e14..99b0ba50b5 100644 --- a/simulations/mixnet/README.md +++ b/simulations/mixnet/README.md @@ -3,66 +3,128 @@ ## Aim Simulate a local mixnet along with a chat app to publish using mix. -This is helpful to test any changes while development. -It includes scripts that run a `4 node` mixnet along with a lightpush service node(without mix) that can be used to test quickly. +This is helpful to test any changes during development. ## Simulation Details -Note that before running the simulation both `wakunode2` and `chat2mix` have to be built. +The simulation includes: + +1. A 5-node mixnet where `run_mix_node.sh` is the bootstrap node for the other 4 nodes +2. Two chat app instances that publish messages using lightpush protocol over the mixnet + +### Available Scripts + +| Script | Description | +| ------------------ | ------------------------------------------ | +| `run_mix_node.sh` | Bootstrap mix node (must be started first) | +| `run_mix_node1.sh` | Mix node 1 | +| `run_mix_node2.sh` | Mix node 2 | +| `run_mix_node3.sh` | Mix node 3 | +| `run_mix_node4.sh` | Mix node 4 | +| `run_chat_mix.sh` | Chat app instance 1 | +| `run_chat_mix1.sh` | Chat app instance 2 | +| `build_setup.sh` | Build and generate RLN credentials | + +## Prerequisites + +Before running the simulation, build `wakunode2` and `chat2mix`: ```bash cd -make wakunode2 -make chat2mix +source env.sh +make wakunode2 chat2mix +``` + +## RLN Spam Protection Setup + +Generate RLN credentials and the shared Merkle tree for all nodes: + +```bash +cd simulations/mixnet +./build_setup.sh ``` -Simulation includes scripts for: +This script will: + +1. Build and run the `setup_credentials` tool +2. Generate RLN credentials for all nodes (5 mix nodes + 2 chat clients) +3. Create `rln_tree.db` - the shared Merkle tree with all members +4. Create keystore files (`rln_keystore_{peerId}.json`) for each node -1. a 4 waku-node mixnet where `node1` is bootstrap node for the other 3 nodes. -2. scripts to run chat app that publishes using lightpush protocol over the mixnet +**Important:** All scripts must be run from this directory (`simulations/mixnet/`) so they can access their credentials and tree file. + +To regenerate credentials (e.g., after adding new nodes), run `./build_setup.sh` again - it will clean up old files first. ## Usage -Start the service node with below command, which acts as bootstrap node for all other mix nodes. +### Step 1: Start the Mix Nodes + +Start the bootstrap node first (in a separate terminal): -`./run_lp_service_node.sh` +```bash +./run_mix_node.sh +``` -To run the nodes for mixnet run the 4 node scripts in different terminals as below. +Look for the following log lines to ensure the node started successfully: -`./run_mix_node1.sh` +```log +INF mounting mix protocol topics="waku node" +INF Node setup complete topics="wakunode main" +``` -Look for following 2 log lines to ensure node ran successfully and has also mounted mix protocol. +Verify RLN spam protection initialized correctly by checking for these logs: ```log -INF 2025-08-01 14:51:05.445+05:30 mounting mix protocol topics="waku node" tid=39996871 file=waku_node.nim:231 nodeId="(listenAddresses: @[\"/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o\"], enrUri: \"enr:-NC4QKYtas8STkenlqBTJ3a1TTLzJA2DsGGbFlnxem9aSM2IXm-CSVZULdk2467bAyFnepnt8KP_QlfDzdaMXd_zqtwBgmlkgnY0gmlwhH8AAAGHbWl4LWtleaCdCc5iT3bo9gYmXtucyit96bQXcqbXhL3a-S_6j7p9LIptdWx0aWFkZHJzgIJyc4UAAgEAAIlzZWNwMjU2azGhA6RFtVJVBh0SYOoP8xrgnXSlpiFARmQkF9d8Rn4fSeiog3RjcILqYYN1ZHCCIymFd2FrdTIt\")" +INF Initializing MixRlnSpamProtection +INF MixRlnSpamProtection initialized, waiting for sync +DBG Tree loaded from file +INF MixRlnSpamProtection started +``` -INF 2025-08-01 14:49:23.467+05:30 Node setup complete topics="wakunode main" tid=39994244 file=wakunode2.nim:104 +Then start the remaining mix nodes in separate terminals: + +```bash +./run_mix_node1.sh +./run_mix_node2.sh +./run_mix_node3.sh +./run_mix_node4.sh ``` -Once all the 4 nodes are up without any issues, run the script to start the chat application. +### Step 2: Start the Chat Applications + +Once all 5 mix nodes are running, start the first chat app: -`./run_chat_app.sh` +```bash +./run_chat_mix.sh +``` -Enter a nickname to be used. +Enter a nickname when prompted: ```bash pubsub topic is: /waku/2/rs/2/0 Choose a nickname >> ``` -Once you see below log, it means the app is ready for publishing messages over the mixnet. +Once you see the following log, the app is ready to publish messages over the mixnet: ```bash Welcome, test! Listening on - /ip4/192.168.68.64/tcp/60000/p2p/16Uiu2HAkxDGqix1ifY3wF1ZzojQWRAQEdKP75wn1LJMfoHhfHz57 + /ip4//tcp/60000/p2p/16Uiu2HAkxDGqix1ifY3wF1ZzojQWRAQEdKP75wn1LJMfoHhfHz57 ready to publish messages now ``` -Follow similar instructions to run second instance of chat app. -Once both the apps run successfully, send a message and check if it is received by the other app. +Start the second chat app in another terminal: + +```bash +./run_chat_mix1.sh +``` + +### Step 3: Test Messaging + +Once both chat apps are running, send a message from one and verify it is received by the other. -You can exit the chat apps by entering `/exit` as below +To exit the chat apps, enter `/exit`: ```bash >> /exit diff --git a/simulations/mixnet/build_setup.sh b/simulations/mixnet/build_setup.sh new file mode 100755 index 0000000000..81af9d16f2 --- /dev/null +++ b/simulations/mixnet/build_setup.sh @@ -0,0 +1,36 @@ +#!/bin/bash +cd "$(dirname "$0")" +MIXNET_DIR=$(pwd) +cd ../.. +ROOT_DIR=$(pwd) +source "$ROOT_DIR/env.sh" + +# Prefer explicitly provided RLN library path, otherwise use the one built by `make librln`. +LIBRLN_PATH=${LIBRLN_PATH:-"$ROOT_DIR/librln_v2.0.2.a"} + +# Clean up old files first +rm -f "$MIXNET_DIR/rln_tree.db" "$MIXNET_DIR"/rln_keystore_*.json + +echo "Building and running credentials setup..." +# Compile to temp location, then run from mixnet directory +nim c -d:release --mm:refc \ + --passL:"$LIBRLN_PATH" --passL:-lm \ + -o:/tmp/setup_credentials_$$ \ + "$MIXNET_DIR/setup_credentials.nim" 2>&1 | tail -30 + +# Run from mixnet directory so files are created there +cd "$MIXNET_DIR" +/tmp/setup_credentials_$$ + +# Clean up temp binary +rm -f /tmp/setup_credentials_$$ + +# Verify output +if [ -f "rln_tree.db" ]; then + echo "" + echo "Tree file ready at: $(pwd)/rln_tree.db" + ls -la rln_keystore_*.json 2>/dev/null | wc -l | xargs -I {} echo "Generated {} keystore files" +else + echo "Setup failed - rln_tree.db not found" + exit 1 +fi diff --git a/simulations/mixnet/config2.toml b/simulations/mixnet/config2.toml index c40e41103e..3acd2bf8a2 100644 --- a/simulations/mixnet/config2.toml +++ b/simulations/mixnet/config2.toml @@ -13,7 +13,7 @@ discv5-udp-port = 9002 discv5-enr-auto-update = true discv5-bootstrap-node = ["enr:-LG4QBaAbcA921hmu3IrreLqGZ4y3VWCjBCgNN9mpX9vqkkbSrM3HJHZTXnb5iVXgc5pPtDhWLxkB6F3yY25hSwMezkEgmlkgnY0gmlwhH8AAAGKbXVsdGlhZGRyc4oACATAqEQ-BuphgnJzhQACAQAAiXNlY3AyNTZrMaEDpEW1UlUGHRJg6g_zGuCddKWmIUBGZCQX13xGfh9J6KiDdGNwguphg3VkcIIjKYV3YWt1Mg0"] kad-bootstrap-node = ["/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o"] -rest = false +rest = true rest-admin = false ports-shift = 3 num-shards-in-network = 1 diff --git a/simulations/mixnet/config3.toml b/simulations/mixnet/config3.toml index 80c19b34b3..bd8e7c4e98 100644 --- a/simulations/mixnet/config3.toml +++ b/simulations/mixnet/config3.toml @@ -13,7 +13,7 @@ discv5-udp-port = 9003 discv5-enr-auto-update = true discv5-bootstrap-node = ["enr:-LG4QBaAbcA921hmu3IrreLqGZ4y3VWCjBCgNN9mpX9vqkkbSrM3HJHZTXnb5iVXgc5pPtDhWLxkB6F3yY25hSwMezkEgmlkgnY0gmlwhH8AAAGKbXVsdGlhZGRyc4oACATAqEQ-BuphgnJzhQACAQAAiXNlY3AyNTZrMaEDpEW1UlUGHRJg6g_zGuCddKWmIUBGZCQX13xGfh9J6KiDdGNwguphg3VkcIIjKYV3YWt1Mg0"] kad-bootstrap-node = ["/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o"] -rest = false +rest = true rest-admin = false ports-shift = 4 num-shards-in-network = 1 diff --git a/simulations/mixnet/config4.toml b/simulations/mixnet/config4.toml index ed5b2dad08..f174250d54 100644 --- a/simulations/mixnet/config4.toml +++ b/simulations/mixnet/config4.toml @@ -13,7 +13,7 @@ discv5-udp-port = 9004 discv5-enr-auto-update = true discv5-bootstrap-node = ["enr:-LG4QBaAbcA921hmu3IrreLqGZ4y3VWCjBCgNN9mpX9vqkkbSrM3HJHZTXnb5iVXgc5pPtDhWLxkB6F3yY25hSwMezkEgmlkgnY0gmlwhH8AAAGKbXVsdGlhZGRyc4oACATAqEQ-BuphgnJzhQACAQAAiXNlY3AyNTZrMaEDpEW1UlUGHRJg6g_zGuCddKWmIUBGZCQX13xGfh9J6KiDdGNwguphg3VkcIIjKYV3YWt1Mg0"] kad-bootstrap-node = ["/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o"] -rest = false +rest = true rest-admin = false ports-shift = 5 num-shards-in-network = 1 diff --git a/simulations/mixnet/run_chat_mix.sh b/simulations/mixnet/run_chat_mix.sh index f711c055e6..ef05753755 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 --kad-bootstrap-node="/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o" +../../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" #--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 7323bb3a96..5961fce457 100755 --- a/simulations/mixnet/run_chat_mix1.sh +++ b/simulations/mixnet/run_chat_mix1.sh @@ -1,2 +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 -#--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" +../../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" diff --git a/simulations/mixnet/run_mix_node.sh b/simulations/mixnet/run_mix_node.sh index 2b293540cd..5d9ff70d8f 100755 --- a/simulations/mixnet/run_mix_node.sh +++ b/simulations/mixnet/run_mix_node.sh @@ -1 +1,2 @@ ../../build/wakunode2 --config-file="config.toml" 2>&1 | tee mix_node.log + diff --git a/simulations/mixnet/setup_credentials.nim b/simulations/mixnet/setup_credentials.nim new file mode 100644 index 0000000000..77c796354e --- /dev/null +++ b/simulations/mixnet/setup_credentials.nim @@ -0,0 +1,139 @@ +{.push raises: [].} + +## Setup script to generate RLN credentials and shared Merkle tree for mix nodes. +## +## This script: +## 1. Generates credentials for each node (identified by peer ID) +## 2. Registers all credentials in a shared Merkle tree +## 3. Saves the tree to rln_tree.db +## 4. Saves individual keystores named by peer ID +## +## Usage: nim c -r setup_credentials.nim + +import std/[os, strformat, options], chronicles, chronos, results + +import + mix_rln_spam_protection/credentials, + mix_rln_spam_protection/group_manager, + mix_rln_spam_protection/rln_interface, + mix_rln_spam_protection/types + +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 + + # Peer IDs derived from nodekeys in config files + # config.toml: nodekey = "f98e3fba96c32e8d1967d460f1b79457380e1a895f7971cecc8528abe733781a" + # config1.toml: nodekey = "09e9d134331953357bd38bbfce8edb377f4b6308b4f3bfbe85c610497053d684" + # config2.toml: nodekey = "ed54db994682e857d77cd6fb81be697382dc43aa5cd78e16b0ec8098549f860e" + # config3.toml: nodekey = "42f96f29f2d6670938b0864aced65a332dcf5774103b4c44ec4d0ea4ef3c47d6" + # config4.toml: nodekey = "3ce887b3c34b7a92dd2868af33941ed1dbec4893b054572cd5078da09dd923d4" + # chat2mix.sh: nodekey = "cb6fe589db0e5d5b48f7e82d33093e4d9d35456f4aaffc2322c473a173b2ac49" + # chat2mix1.sh: nodekey = "35eace7ccb246f20c487e05015ca77273d8ecaed0ed683de3d39bf4f69336feb" + + # Node info: (peerId, userMessageLimit) + NodeConfigs = [ + ("16Uiu2HAmPiEs2ozjjJF2iN2Pe2FYeMC9w4caRHKYdLdAfjgbWM6o", DefaultUserMessageLimit), + # config.toml (service node) + ("16Uiu2HAmLtKaFaSWDohToWhWUZFLtqzYZGPFuXwKrojFVF6az5UF", DefaultUserMessageLimit), + # config1.toml (mix node 1) + ("16Uiu2HAmTEDHwAziWUSz6ZE23h5vxG2o4Nn7GazhMor4bVuMXTrA", DefaultUserMessageLimit), + # config2.toml (mix node 2) + ("16Uiu2HAmPwRKZajXtfb1Qsv45VVfRZgK3ENdfmnqzSrVm3BczF6f", DefaultUserMessageLimit), + # config3.toml (mix node 3) + ("16Uiu2HAmRhxmCHBYdXt1RibXrjAUNJbduAhzaTHwFCZT4qWnqZAu", DefaultUserMessageLimit), + # config4.toml (mix node 4) + ("16Uiu2HAm1QxSjNvNbsT2xtLjRGAsBLVztsJiTHr9a3EK96717hpj", DefaultUserMessageLimit), + # chat2mix client 1 + ("16Uiu2HAmC9h26U1C83FJ5xpE32ghqya8CaZHX1Y7qpfHNnRABscN", DefaultUserMessageLimit), + # chat2mix client 2 + ] + +proc setupCredentialsAndTree() {.async.} = + ## Generate credentials for all nodes and create a shared tree + + echo "=== RLN Credentials Setup ===" + echo "Generating credentials for ", NodeConfigs.len, " nodes...\n" + + # Generate credentials for all nodes + var allCredentials: + seq[tuple[peerId: string, cred: IdentityCredential, rateLimit: uint64]] + for (peerId, rateLimit) in NodeConfigs: + let cred = generateCredentials().valueOr: + echo "Failed to generate credentials for ", peerId, ": ", error + quit(1) + + allCredentials.add((peerId: peerId, cred: cred, rateLimit: rateLimit)) + echo "Generated credentials for ", peerId + echo " idCommitment: ", cred.idCommitment.toHex()[0 .. 15], "..." + echo " userMessageLimit: ", rateLimit + + echo "" + + # Create a group manager directly to build the tree + let rlnInstance = newRLNInstance().valueOr: + echo "Failed to create RLN instance: ", error + quit(1) + + let groupManager = newOffchainGroupManager(rlnInstance, "/mix/rln/membership/v1") + + # Initialize the group manager + let initRes = await groupManager.init() + if initRes.isErr: + echo "Failed to initialize group manager: ", initRes.error + quit(1) + + # Register all credentials in the tree with their specific rate limits + echo "Registering all credentials in the Merkle tree..." + for i, entry in allCredentials: + let index = ( + await groupManager.registerWithLimit(entry.cred.idCommitment, entry.rateLimit) + ).valueOr: + echo "Failed to register credential for ", entry.peerId, ": ", error + quit(1) + echo " Registered ", + entry.peerId, " at index ", index, " (limit: ", entry.rateLimit, ")" + + echo "" + + # Save the tree to disk + echo "Saving tree to rln_tree.db..." + let saveRes = groupManager.saveTreeToFile("rln_tree.db") + if saveRes.isErr: + echo "Failed to save tree: ", saveRes.error + quit(1) + echo "Tree saved successfully!" + + echo "" + + # Save each credential to a keystore file named by peer ID + echo "Saving keystores..." + for i, entry in allCredentials: + let keystorePath = &"rln_keystore_{entry.peerId}.json" + + # Save with membership index and rate limit + let saveResult = saveKeystore( + entry.cred, + KeystorePassword, + keystorePath, + some(MembershipIndex(i)), + some(entry.rateLimit), + ) + if saveResult.isErr: + echo "Failed to save keystore for ", entry.peerId, ": ", saveResult.error + quit(1) + echo " Saved: ", keystorePath, " (limit: ", entry.rateLimit, ")" + + echo "" + echo "=== Setup Complete ===" + echo " Tree file: rln_tree.db (", NodeConfigs.len, " members)" + 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." + +when isMainModule: + waitFor setupCredentialsAndTree() diff --git a/tests/test_helpers.nim b/tests/test_helpers.nim index a4bb69fbc2..a0442d7320 100644 --- a/tests/test_helpers.nim +++ b/tests/test_helpers.nim @@ -22,7 +22,7 @@ proc setupTestNode*( addAllCapabilities = false, bindUdpPort = address.udpPort, # Assume same as external bindTcpPort = address.tcpPort, # Assume same as external - rng = rng, + rng = rng(), ) nextPort.inc for capability in capabilities: @@ -40,7 +40,10 @@ proc getRng(): crypto.Rng = # purpose of the tests, it's ok as long as we only use a single thread {.gcsafe.}: if rngVar.rng.isNil: - rngVar.rng = crypto.newRng() + # libp2p v2.0.0: crypto.newRng() returns the new `Rng` wrapper type; + # construct an HmacDrbgContext directly so the field type stays as + # `ref HmacDrbgContext` (what bearssl-style consumers expect). + rngVar.rng = HmacDrbgContext.new() rngVar.rng template rng*(): crypto.Rng = diff --git a/tests/test_peer_manager.nim b/tests/test_peer_manager.nim index e2f5a5a261..ed1fa6bce8 100644 --- a/tests/test_peer_manager.nim +++ b/tests/test_peer_manager.nim @@ -1363,7 +1363,8 @@ procSuite "Peer Manager": # Create peer manager let pm = PeerManager.new( - switch = SwitchBuilder.new().withRng(rng()).withMplex().withNoise().build(), + switch = + SwitchBuilder.new().withRng(crypto.newRng()).withMplex().withNoise().build(), storage = nil, ) diff --git a/tests/test_waku_switch.nim b/tests/test_waku_switch.nim index c3f635c178..86faae83d3 100644 --- a/tests/test_waku_switch.nim +++ b/tests/test_waku_switch.nim @@ -4,6 +4,7 @@ import testutils/unittests, chronos, libp2p/builders, + libp2p/crypto/crypto, libp2p/protocols/connectivity/autonat/client, libp2p/protocols/connectivity/relay/relay, libp2p/protocols/connectivity/relay/client, @@ -13,7 +14,7 @@ import logos_delivery/waku/node/waku_switch, ./testlib/common, ./testlib/wakucor proc newCircuitRelayClientSwitch(relayClient: RelayClient): Switch = SwitchBuilder .new() - .withRng(rng()) + .withRng(crypto.newRng()) .withAddresses(@[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()]) .withTcpTransport() .withMplex() @@ -26,7 +27,7 @@ suite "Waku Switch": ## Given let sourceSwitch = newTestSwitch() - wakuSwitch = newWakuSwitch(rng = rng(), circuitRelay = Relay.new()) + wakuSwitch = newWakuSwitch(rng = crypto.newRng(), circuitRelay = Relay.new()) await sourceSwitch.start() await wakuSwitch.start() @@ -46,7 +47,7 @@ suite "Waku Switch": asyncTest "Waku Switch acts as circuit relayer": ## Setup let - wakuSwitch = newWakuSwitch(rng = rng(), circuitRelay = Relay.new()) + wakuSwitch = newWakuSwitch(rng = crypto.newRng(), circuitRelay = Relay.new()) sourceClient = RelayClient.new() destClient = RelayClient.new() sourceSwitch = newCircuitRelayClientSwitch(sourceClient) diff --git a/tests/waku_filter_v2/test_waku_client.nim b/tests/waku_filter_v2/test_waku_client.nim index 8141a139ec..53ae3aa321 100644 --- a/tests/waku_filter_v2/test_waku_client.nim +++ b/tests/waku_filter_v2/test_waku_client.nim @@ -39,8 +39,8 @@ suite "Waku Filter - End to End": pubsubTopic = DefaultPubsubTopic contentTopic = DefaultContentTopic contentTopicSeq = @[contentTopic] - serverSwitch = newStandardSwitch() - clientSwitch = newStandardSwitch() + serverSwitch = newTestSwitch() + clientSwitch = newTestSwitch() wakuFilter = await newTestWakuFilter(serverSwitch) wakuFilterClient = await newTestWakuFilterClient(clientSwitch) @@ -106,7 +106,7 @@ suite "Waku Filter - End to End": suite "Subscribe": asyncTest "Server remote peer info doesn't match an online server": # Given an offline service node - let offlineServerSwitch = newStandardSwitch() + let offlineServerSwitch = newTestSwitch() let offlineServerRemotePeerInfo = offlineServerSwitch.peerInfo.toRemotePeerInfo() @@ -721,7 +721,7 @@ suite "Waku Filter - End to End": # Given a WakuFilterClient list of size MaxFilterPeers var clients: seq[(WakuFilterClient, Switch)] = @[] for i in 0 ..< MaxFilterPeers: - let standardSwitch = newStandardSwitch() + let standardSwitch = newTestSwitch() let wakuFilterClient = await newTestWakuFilterClient(standardSwitch) clients.add((wakuFilterClient, standardSwitch)) @@ -738,7 +738,7 @@ suite "Waku Filter - End to End": wakuFilter.subscriptions.subscribedPeerCount() == MaxFilterPeers # When initialising a new WakuFilterClient and subscribing it to the same service - let standardSwitch = newStandardSwitch() + let standardSwitch = newTestSwitch() let wakuFilterClient = await newTestWakuFilterClient(standardSwitch) await standardSwitch.start() let subscribeResponse = await wakuFilterClient.subscribe( @@ -752,7 +752,7 @@ suite "Waku Filter - End to End": asyncTest "Multiple Subscriptions": # Given a second service node - let serverSwitch2 = newStandardSwitch() + let serverSwitch2 = newTestSwitch() let wakuFilter2 = await newTestWakuFilter(serverSwitch2) await allFutures(serverSwitch2.start()) let serverRemotePeerInfo2 = serverSwitch2.peerInfo.toRemotePeerInfo() diff --git a/tests/waku_filter_v2/test_waku_filter_dos_protection.nim b/tests/waku_filter_v2/test_waku_filter_dos_protection.nim index ab8d202bb4..9ce0258668 100644 --- a/tests/waku_filter_v2/test_waku_filter_dos_protection.nim +++ b/tests/waku_filter_v2/test_waku_filter_dos_protection.nim @@ -24,7 +24,7 @@ type AFilterClient = ref object of RootObj proc init(T: type[AFilterClient]): T = var r = T( - clientSwitch: newStandardSwitch(), + clientSwitch: newTestSwitch(), msgSeq: @[], pushHandlerFuture: newPushHandlerFuture(), ) @@ -93,7 +93,7 @@ suite "Waku Filter - DOS protection": pubsubTopic = DefaultPubsubTopic contentTopic = DefaultContentTopic contentTopicSeq = @[contentTopic] - serverSwitch = newStandardSwitch() + serverSwitch = newTestSwitch() wakuFilter = await newTestWakuFilter( serverSwitch, rateLimitSetting = Opt.some((3, 1000.milliseconds)) ) diff --git a/tests/wakunode_rest/test_rest_store.nim b/tests/wakunode_rest/test_rest_store.nim index 74847bf9b9..1644b99cec 100644 --- a/tests/wakunode_rest/test_rest_store.nim +++ b/tests/wakunode_rest/test_rest_store.nim @@ -108,7 +108,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() peerSwitch.mount(node.wakuStore) @@ -185,7 +185,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() peerSwitch.mount(node.wakuStore) @@ -254,7 +254,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() peerSwitch.mount(node.wakuStore) @@ -349,7 +349,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() peerSwitch.mount(node.wakuStore) @@ -422,7 +422,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() peerSwitch.mount(node.wakuStore) @@ -511,7 +511,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() peerSwitch.mount(node.wakuStore) @@ -561,7 +561,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() let client = newRestHttpClient(initTAddress(restAddress, restPort)) @@ -743,7 +743,7 @@ procSuite "Waku Rest API - Store v3": node.mountStoreClient() let key = generateEcdsaKey() - var peerSwitch = newStandardSwitch(Opt.some(key)) + var peerSwitch = newTestSwitch(some(key)) await peerSwitch.start() peerSwitch.mount(node.wakuStore)