From 28956856bb9eafa23aef14bd5108ff54df82cbc2 Mon Sep 17 00:00:00 2001 From: Fabiana Cecin Date: Thu, 9 Jul 2026 17:23:34 -0300 Subject: [PATCH] Fix interop tests (#4020) * logosdelivery_create_node takes a flat WakuNodeConf JSON conf (auto-detect) * Fix tools/confutils/conf_from_json.nim not accepting Port(0) * Fix: don't mount lightpush without relay * Add tests --- .../api/logos_delivery_conf_json.nim | 63 +++++++++++++ logos_delivery/api/messaging_conf.nim | 16 ++-- .../conf_builder/waku_conf_builder.nim | 2 +- tests/api/test_conf.nim | 88 ++++++++++++++++++- tools/confutils/conf_from_json.nim | 7 ++ 5 files changed, 167 insertions(+), 9 deletions(-) diff --git a/logos_delivery/api/logos_delivery_conf_json.nim b/logos_delivery/api/logos_delivery_conf_json.nim index 18e9ad485..260f4f2ae 100644 --- a/logos_delivery/api/logos_delivery_conf_json.nim +++ b/logos_delivery/api/logos_delivery_conf_json.nim @@ -13,6 +13,10 @@ const KeyKernelConf = "kernelconf" KeyMessagingOverrides = "messagingoverrides" KeyChannelsOverrides = "channelsoverrides" + # [Legacy flat JSON config] Keys that left the kernel and so must be lifted out of + # a flat blob before the WakuNodeConf walker sees them (it would reject them). + KeyReliabilityEnabled = "reliabilityenabled" + KeyReliability = "reliability" proc parseMode(s: string): Result[LogosDeliveryMode, string] = case s.strip().toLowerAscii() @@ -39,6 +43,50 @@ proc parseOverrides[T](defaults: T, node: JsonNode, label: string): Result[T, st ) return ok(conf) +proc parseFlatConf( + mode: LogosDeliveryMode, topJsonNode: var Table[string, (string, JsonNode)] +): ConfResult[LogosDeliveryConf] = + ## [Legacy flat JSON config] Flat shape: a blob of `WakuNodeConf` fields. `mode` + ## expands to protocol flags over raw kernel defaults, `reliabilityEnabled` routes + ## to the messaging conf (it left the kernel), and the rest parses as a + ## `WakuNodeConf`. Full stack. Delete this proc and its call site to drop support. + var messaging = MessagingClientConf() + var reliabilityFields: Table[string, (string, JsonNode)] + for key in [KeyReliabilityEnabled, KeyReliability]: + if topJsonNode.hasKey(key): + reliabilityFields[key] = topJsonNode.getOrDefault(key) + topJsonNode.del(key) + if reliabilityFields.len > 0: + ?applyJsonFieldsToConf( + messaging, reliabilityFields, "Failed to parse reliability field", + "Unrecognized reliability option(s) found", + ) + + # [Legacy flat JSON config] The blob is a raw WakuNodeConf, exactly as the + # pre-refactor flat create_node parsed it: start from the kernel defaults and apply + # the mode's protocol flags (the kernel no longer owns `mode`, so we expand it here, + # like the old kernel builder did), then let explicit fields override. + var kernel = ?defaultWakuNodeConf() + ?applyMode(kernel, mode) + ?applyJsonFieldsToConf( + kernel, topJsonNode, "Failed to parse config field", + "Unrecognized configuration option(s) found", + ) + + # [Legacy flat JSON config] Reliability is resolved from the preset by the messaging + # layer (the kernel no longer carries it), so a flat blob's `preset` must lift it + # here to stay faithful to master. An explicit reliability in the blob still wins. + if kernel.preset.len > 0: + messaging = merge(?resolvePreset(kernel.preset), messaging) + + return ok( + LogosDeliveryConf( + kernelConf: KernelConf(kernel), + messagingConf: some(messaging), + channelsConf: some(ReliableChannelManagerConf()), + ) + ) + proc parseLogosDeliveryConf*(jsonStr: string): ConfResult[LogosDeliveryConf] = var node: JsonNode try: @@ -70,6 +118,21 @@ proc parseLogosDeliveryConf*(jsonStr: string): ConfResult[LogosDeliveryConf] = err(unknownKeysError(top, "fleet mode takes only 'kernelConf'; unexpected")) return ok(LogosDeliveryConf.init(KernelConf(kernel))) + # [Legacy flat JSON config] A wrapper key marks our structured shape. Otherwise any + # leftover top-level key besides `preset` (mode is already consumed) is a bare + # kernel field -> flat blob. Delete this block to drop flat-shape support. + let hasWrapper = + top.hasKey(KeyMessagingOverrides) or top.hasKey(KeyChannelsOverrides) or + top.hasKey(KeyKernelConf) + if not hasWrapper: + var bareField = false + for k in top.keys: + if k != KeyPreset: + bareField = true + break + if bareField: + return parseFlatConf(mode, top) + var preset = "" var messagingOverrides = MessagingClientConf() var channelsOverrides = ReliableChannelManagerConf() diff --git a/logos_delivery/api/messaging_conf.nim b/logos_delivery/api/messaging_conf.nim index 1907e3540..da0d7c641 100644 --- a/logos_delivery/api/messaging_conf.nim +++ b/logos_delivery/api/messaging_conf.nim @@ -13,12 +13,8 @@ type LogosDeliveryMode* {.pure.} = enum Core # full service node Fleet # kernel-only node from a raw kernel config -proc toWakuNodeConf*( - self: MessagingClientConf, mode: LogosDeliveryMode -): ConfResult[WakuNodeConf] = - ## Mode sets the protocol flags; set fields map to their kernel counterpart. - var conf = ?defaultWakuNodeConf() - +proc applyMode*(conf: var WakuNodeConf, mode: LogosDeliveryMode): ConfResult[void] = + ## Sets the protocol flags implied by the mode. case mode of LogosDeliveryMode.Core: conf.relay = true @@ -36,6 +32,14 @@ proc toWakuNodeConf*( of LogosDeliveryMode.Fleet: return err("fleet mode takes a raw kernel config; use LogosDelivery.new(kernelConf)") + return ok() + +proc toWakuNodeConf*( + self: MessagingClientConf, mode: LogosDeliveryMode +): ConfResult[WakuNodeConf] = + ## Mode sets the protocol flags; set fields map to their kernel counterpart. + var conf = ?defaultWakuNodeConf() + ?applyMode(conf, mode) if self.store.isSome(): conf.store = self.store.get() diff --git a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim index d312f620e..0eaec4934 100644 --- a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim @@ -797,7 +797,7 @@ proc build*( subscribeShards: subscribeShards, protectedShards: protectedShards, relay: relay, - lightPush: lightPush, + lightPush: lightPush and relay, # can't mount lightpush without relay peerExchangeService: peerExchange, rendezvous: rendezvous, peerExchangeDiscovery: true, diff --git a/tests/api/test_conf.nim b/tests/api/test_conf.nim index 4017d006b..280068516 100644 --- a/tests/api/test_conf.nim +++ b/tests/api/test_conf.nim @@ -172,8 +172,10 @@ suite "parseLogosDeliveryConf - JSON parsing": test "invalid JSON is rejected": check parseLogosDeliveryConf("{ not json }").isErr() - test "unknown top-level keys are rejected": - check parseLogosDeliveryConf("""{"logLevel": "INFO", "mode": "Core"}""").isErr() + test "a truly unknown top-level key is rejected (flat walker rejects the field)": + # NB: a *known* WakuNodeConf field at top level (e.g. logLevel) is now accepted + # as a flat blob; only a field that is not a WakuNodeConf field is rejected. + check parseLogosDeliveryConf("""{"totallyBogusField": "x", "mode": "Core"}""").isErr() test "override keys accept CLI switch names": let lc = parseLogosDeliveryConf( @@ -270,6 +272,8 @@ suite "parseLogosDeliveryConf - JSON parsing": .isErr() test "kernelConf is rejected outside fleet mode": + # kernelConf is a fleet-only wrapper. Under Core/Edge it is neither consumed by the + # structured path nor a flat kernel field, so it must surface as an unknown key. check parseLogosDeliveryConf("""{"mode": "core", "kernelConf": {}}""").isErr() suite "LogosDelivery.new - construction (the app-dev entry)": @@ -336,3 +340,83 @@ suite "LogosDelivery.new - raw kernel construction": # start()/stop() must skip the nil messaging + channel layers, not deref them (await node.start()).expect("start") (await node.stop()).expect("stop") + +# [Legacy flat JSON config] These suites cover the legacy flat shape; delete them +# together with parseFlatConf when flat-shape support is dropped. +suite "parseLogosDeliveryConf - flat WakuNodeConf shape (interop compatibility)": + test "a flat blob of kernel fields is detected and parsed as a full stack": + let lc = parseLogosDeliveryConf( + """{"relay": true, "clusterId": 7, "store": true, "filter": false}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(lc.kernelConf).relay == true + WakuNodeConf(lc.kernelConf).clusterId == some(7'u16) + lc.messagingConf.isSome() # full stack + lc.channelsConf.isSome() + + test "flat blob carrying mode: mode expands to flags, explicit flags override": + let lc = parseLogosDeliveryConf( + """{"mode": "Edge", "relay": true, "clusterId": 7}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(lc.kernelConf).relay == true + # explicit flat field overrides the Edge default + WakuNodeConf(lc.kernelConf).filter == false # Edge default, not set explicitly + WakuNodeConf(lc.kernelConf).clusterId == some(7'u16) + + test "flat blob's reliabilityEnabled routes to the messaging conf, not the kernel": + let lc = parseLogosDeliveryConf("""{"relay": true, "reliabilityEnabled": true}""").valueOr: + raiseAssert error + check: + lc.messagingConf.get().reliabilityEnabled == some(true) + + test "an unknown key in a flat blob is rejected": + check parseLogosDeliveryConf("""{"relay": true, "bogusKey": 1}""").isErr() + + test "a flat blob's preset lifts reliability into the messaging record": + # twn defines a p2pReliability; the flat path must resolve it into the messaging + # conf (the kernel no longer carries reliability), matching master. + let lc = parseLogosDeliveryConf("""{"preset": "twn", "relay": true}""").valueOr: + raiseAssert error + let fromPreset = resolvePreset("twn").valueOr: + raiseAssert error + check lc.messagingConf.get().reliabilityEnabled == fromPreset.reliabilityEnabled + + test "port 0 (auto-allocate) is accepted in a flat blob": + let lc = parseLogosDeliveryConf( + """{"relay": true, "tcpPort": 0, "discv5UdpPort": 0}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(lc.kernelConf).tcpPort == Port(0) + WakuNodeConf(lc.kernelConf).discv5UdpPort == Port(0) + + test "port 0 is accepted in structured messagingOverrides": + let lc = parseLogosDeliveryConf("""{"messagingOverrides": {"tcp-port": 0}}""").valueOr: + raiseAssert error + check lc.messagingConf.get().p2pTcpPort == some(Port(0)) + + test "mode/preset with no bare field stays structured; a bare field flips it to flat": + # No bare kernel field -> structured: mode owns relay, no per-flag override. + let structured = parseLogosDeliveryConf( + """{"mode": "Edge", "preset": "logostest"}""" + ).valueOr: + raiseAssert error + check WakuNodeConf(structured.kernelConf).relay == false # Edge, structured + # Adding a bare kernel field (relay) flips to flat, where the explicit flag wins; + # relay == true is only reachable via the flat path, so it proves the routing. + let flat = parseLogosDeliveryConf( + """{"mode": "Edge", "preset": "logostest", "relay": true}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(flat.kernelConf).relay == true + WakuNodeConf(flat.kernelConf).preset == "logostest" + + test "a wrapper key alongside a bare kernel field is rejected, not split": + # A wrapper key (messagingOverrides) marks the structured shape, so the blob does + # not flip to flat; the leftover bare field (relay) then has no structured home and + # is rejected. Guards against the discriminator silently splitting a mixed object. + check parseLogosDeliveryConf("""{"messagingOverrides": {}, "relay": true}""").isErr() diff --git a/tools/confutils/conf_from_json.nim b/tools/confutils/conf_from_json.nim index 68a833a45..7e78fd300 100644 --- a/tools/confutils/conf_from_json.nim +++ b/tools/confutils/conf_from_json.nim @@ -53,6 +53,13 @@ proc parseScalarInto[U]( let s = jsonScalarToString(jsonValue).valueOr: return err(prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & error) + when U is Port: + # `Port(0)` means "auto-allocate an ephemeral port" and is a valid value (see + # PR #3828). confutils' `parseCmdArg(Port)` enforces the CLI range 1-65535 and + # rejects "0", but the JSON config API must accept it, so handle 0 explicitly and + # delegate every other value to confutils. + if s == "0": + return ok(Port(0)) try: ok(parseCmdArg(U, s)) except CatchableError as e: