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
This commit is contained in:
Fabiana Cecin 2026-07-09 17:23:34 -03:00 committed by GitHub
parent d83900aa9b
commit 28956856bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 167 additions and 9 deletions

View File

@ -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()

View File

@ -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()

View File

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

View File

@ -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()

View File

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