From ad4bdcc1a788412703572d56d97faf10a2ef6bd6 Mon Sep 17 00:00:00 2001 From: Prem Chaitanya Prathi Date: Fri, 26 Jun 2026 06:54:20 +0530 Subject: [PATCH] feat(mix): kad mix-node discovery for fleet (kadBootstrapNodes) --- library/api/client_api.nim | 7 +++++ src/chat/delivery/waku_client.nim | 51 +++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/library/api/client_api.nim b/library/api/client_api.nim index f9637b2..a82b96c 100644 --- a/library/api/client_api.nim +++ b/library/api/client_api.nim @@ -57,6 +57,13 @@ proc createChatClient( wakuCfg.mixNodes = @[] for node in config["mixNodes"]: wakuCfg.mixNodes.add(node.getStr()) + # Fleet mode: kad bootstrap nodes for mix-node service discovery (instead of a + # static mixNodes list with pubkeys). The chat discovers the fleet mix nodes + + # their curve25519 pubkeys via these. + if config.hasKey("kadBootstrapNodes"): + wakuCfg.kadBootstrapNodes = @[] + for node in config["kadBootstrapNodes"]: + wakuCfg.kadBootstrapNodes.add(node.getStr()) if config.hasKey("minMixPoolSize"): wakuCfg.minMixPoolSize = config["minMixPoolSize"].getInt(4) # Adopt a fixed identity (e.g. a provisioned mix-sim chat credential) so the diff --git a/src/chat/delivery/waku_client.nim b/src/chat/delivery/waku_client.nim index 680e8e0..25a52f3 100644 --- a/src/chat/delivery/waku_client.nim +++ b/src/chat/delivery/waku_client.nim @@ -11,12 +11,16 @@ import libp2p/crypto/curve25519, libp2p/crypto/rng as libp2p_rng, libp2p/peerid, + libp2p/multiaddress, + libp2p/nameresolving/dnsresolver, + libp2p/protocols/kademlia/types, + libp2p/protocols/service_discovery/types as sd_types, libp2p_mix, libp2p_mix/curve25519 as mix_curve25519, libp2p_mix/entry_connection, libp2p_mix/mix_protocol as mix_proto, nimcrypto/utils as ncrutils, - std/[random, strutils], + std/[random, sets, strutils], stew/byteutils, strformat, logos_delivery/waku/[ @@ -25,12 +29,14 @@ import node/peer_manager, waku_core, waku_core/codecs, + waku_core/peers, waku_node, waku_enr, waku_mix/protocol as waku_mix_protocol, waku_lightpush/client as lightpush_client, discovery/waku_discv5, discovery/waku_dnsdisc, + discovery/waku_kademlia, factory/builder, waku_filter_v2/client, ], @@ -43,7 +49,7 @@ logScope: type ChatPayload* = object pubsubTopic*: PubsubTopic contentTopic*: string - timestamp*: Timestamp + timestamp*: waku_core.Timestamp bytes*: seq[byte] proc toChatPayload*(msg: WakuMessage, pubsubTopic: PubsubTopic): ChatPayload = @@ -101,6 +107,7 @@ type WakuConfig* = object staticPeers*: seq[string] mixEnabled*: bool mixNodes*: seq[string] + kadBootstrapNodes*: seq[string] minMixPoolSize*: int type @@ -219,6 +226,13 @@ proc buildWakuNode(cfg: WakuConfig): WakuNode = builder.withNodeKey(cfg.nodeKey) builder.withRecord(record) builder.withNetworkConfigurationDetails(ip, Port(cfg.port)).tryGet() + # DNS resolver so /dns4/ fleet bootstrap addrs (kad discovery) resolve and dial; + # without it libp2p logs "Can't resolve DNSADDR without NameResolver". + builder.withSwitchConfiguration( + nameResolver = DnsResolver.new( + @[initTAddress("1.1.1.1", Port(53)), initTAddress("8.8.8.8", Port(53))] + ) + ) let node = builder.build().tryGet() node.mountMetadata(cfg.clusterId, cfg.shardId).expect("failed to mount waku metadata protocol") @@ -306,6 +320,39 @@ proc start*(client: WakuClient) {.async.} = disableSpamProtection = false)).isOkOr: error "Failed to mount mix protocol", error = $error quit(QuitFailure) + + # Fleet mode: discover the mix nodes (and their curve25519 pubkeys) via libp2p + # Kademlia service discovery instead of a static mixNodes list. The discovery + # logic lives in nwaku (waku_kademlia) — we only mount it with the bootstrap + # nodes, because the chat builds its WakuNode by hand and bypasses the + # conf-driven factory that would otherwise auto-mount it (mirrors apps/chat2mix). + # Discovered mix peers land in the peerManager and fill the pool waitForMixPool + # waits on. + if client.cfg.kadBootstrapNodes.len > 0: + var kadBootstrapPeers: seq[(PeerId, seq[MultiAddress])] + for nodeStr in client.cfg.kadBootstrapNodes: + let pInfo = parsePeerInfo(nodeStr).valueOr: + warn "Failed to parse kad bootstrap node", node = nodeStr, err = error + continue + kadBootstrapPeers.add((pInfo.peerId, pInfo.addrs)) + if kadBootstrapPeers.len > 0: + client.node.mountKademlia( + KademliaDiscoveryConf( + bootstrapNodes: kadBootstrapPeers, + servicesToDiscover: toHashSet(@[mix_proto.MixProtocolID]), + randomLookupInterval: chronos.seconds(60), + serviceLookupInterval: chronos.seconds(60), + kadDhtConfig: KadDHTConfig.new(), + discoConfig: sd_types.ServiceDiscoveryConfig.new(), + clientMode: false, + xprPublishing: true, + ) + ).isOkOr: + error "Failed to mount kademlia mix discovery", error = error + quit(QuitFailure) + info "Kademlia mix discovery mounted", + bootstrapPeers = kadBootstrapPeers.len + asyncSpawn client.waitForMixPool() await client.node.start()