feat: service ports default to 0 (auto-assign)

* tcp/rest/websocket/metrics/discv5 default port changed to 0
* all CLI port types are now uint16 (instead of Port)
* any port set to 0 on conf results in a random port bound
* write back bound values to both WakuConf and WakuNode.ports
* REST GET /info reports actually bound ports for enabled services
* conf builders now err if any port config is unset
* setupDiscoveryV5 returns Result and errors out on port 0
* rename setupAndStartDiscv5WithAutoPort to setupAndStartDiscv5
* updateWaku ENR rebuild now runs after discv5 startup
* remove Port(0) conf from tests (0 is default)
* add port = 0 to conf builder tests (conf builder has no default)
This commit is contained in:
Fabiana Cecin 2026-04-23 00:13:54 -03:00
parent 260def68ad
commit 5603f766d1
No known key found for this signature in database
GPG Key ID: BCAB8A55CB51B6C7
19 changed files with 498 additions and 95 deletions

View File

@ -94,8 +94,6 @@ suite "LM API health checking":
raiseAssert error
conf.mode = Core
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.numShardsInNetwork = 1
conf.rest = false
@ -271,8 +269,6 @@ suite "LM API health checking":
raiseAssert error
edgeConf.mode = Edge
edgeConf.listenAddress = parseIpAddress("0.0.0.0")
edgeConf.tcpPort = Port(0)
edgeConf.discv5UdpPort = Port(0)
edgeConf.clusterId = 3'u16
edgeConf.maxMessageSize = "150 KiB"
edgeConf.rest = false

View File

@ -65,8 +65,6 @@ proc createApiNodeConf(numShards: uint16 = 1): WakuNodeConf =
raiseAssert error
conf.mode = cli_args.WakuMode.Core
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.numShardsInNetwork = numShards
conf.reliabilityEnabled = true

View File

@ -122,8 +122,6 @@ proc createApiNodeConf(mode: cli_args.WakuMode = cli_args.WakuMode.Core): WakuNo
raiseAssert error
conf.mode = mode
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.numShardsInNetwork = 1
conf.reliabilityEnabled = true

View File

@ -73,8 +73,6 @@ proc createApiNodeConf(
raiseAssert error
conf.mode = mode
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.numShardsInNetwork = numShards
conf.reliabilityEnabled = true

View File

@ -376,6 +376,7 @@ suite "WakuConfBuilder - store retention policies":
test "Multiple retention policies":
## Given
var b = WakuConfBuilder.init()
b.withP2pTcpPort(0'u16)
b.storeServiceConf.withEnabled(true)
b.storeServiceConf.withDbUrl("sqlite://test.db")
b.storeServiceConf.withRetentionPolicies(
@ -420,6 +421,7 @@ suite "WakuConfBuilder - store retention policies":
test "Store disabled - no retention policy applied":
## Given
var b = WakuConfBuilder.init()
b.withP2pTcpPort(0'u16)
# storeServiceConf not enabled
## When

View File

@ -1,13 +1,18 @@
{.used.}
import testutils/unittests, chronos, libp2p/protocols/connectivity/relay/relay
import
std/[net, options, strutils],
testutils/unittests,
chronos,
chronos/transports/[stream, datagram, common],
metrics/chronos_httpserver,
libp2p/[crypto/crypto, multiaddress, protocols/connectivity/relay/relay]
import
../testlib/wakunode,
waku/waku_node,
waku/factory/node_factory,
waku/factory/conf_builder/conf_builder,
waku/factory/conf_builder/web_socket_conf_builder
tests/testlib/[wakunode, wakucore],
waku/[waku_node, common/auto_port, discovery/waku_discv5, node/waku_metrics],
waku/factory/
[node_factory, conf_builder/conf_builder, conf_builder/web_socket_conf_builder]
suite "Node Factory":
asynctest "Set up a node based on default configurations":
@ -68,5 +73,143 @@ asynctest "Start a node based on default test configuration":
check:
node.started == true
# Default conf has p2pTcpPort=0, so the OS must have assigned a real port.
var hasNonZeroTcp = false
for a in node.switch.peerInfo.listenAddrs:
let s = $a
if ("/tcp/" in s) and not ("/tcp/0" in s):
hasNonZeroTcp = true
check hasNonZeroTcp
## Cleanup
await node.stop()
suite "Auto-port retry":
asynctest "metrics binds on free TCP port, fails on taken":
let takenPort = Port(55100)
let freePort = Port(55101)
let taken = createStreamServer(initTAddress("127.0.0.1", takenPort))
proc buildMetricsConf(port: Port): MetricsServerConf =
var b = MetricsServerConfBuilder.init()
b.withEnabled(true)
b.withHttpPort(port)
b.build().value.get()
try:
let failRes = await startMetricsServerAndLogging(buildMetricsConf(takenPort), 0'u16)
check failRes.isErr
let okRes = await startMetricsServerAndLogging(buildMetricsConf(freePort), 0'u16)
check okRes.isOk
if okRes.isOk:
await okRes.get().server.close()
finally:
taken.stop()
await taken.closeWait()
asynctest "discv5 binds on free UDP port, fails on taken":
let takenPort = Port(55200)
let freePort = Port(55201)
proc dummyCb(
transp: DatagramTransport, raddr: TransportAddress
): Future[void] {.async: (raises: []).} =
discard
let takenUdp =
newDatagramTransport(dummyCb, local = initTAddress("0.0.0.0", takenPort))
let nodeKey = generateSecp256k1Key()
let node = newTestWakuNode(nodeKey, parseIpAddress("0.0.0.0"), Port(0))
await node.start()
proc buildDiscv5Conf(port: Port): Discv5Conf =
var b = Discv5ConfBuilder.init()
b.withEnabled(true)
b.withUdpPort(port)
b.build().value.get()
try:
let failRes = await setupAndStartDiscv5(
node.enr,
node.peerManager,
node.topicSubscriptionQueue,
buildDiscv5Conf(takenPort),
@[],
node.rng,
nodeKey,
parseIpAddress("0.0.0.0"),
0'u16,
)
check failRes.isErr
let okRes = await setupAndStartDiscv5(
node.enr,
node.peerManager,
node.topicSubscriptionQueue,
buildDiscv5Conf(freePort),
@[],
node.rng,
nodeKey,
parseIpAddress("0.0.0.0"),
0'u16,
)
check okRes.isOk
if okRes.isOk:
await okRes.get().stop()
finally:
await takenUdp.closeWait()
await node.stop()
asynctest "exhausted retries err for metrics and discv5":
let origMin = autoPortMin
let origMax = autoPortMax
let pinned = 58888'u16
autoPortMin = pinned
autoPortMax = pinned
let takenTcp = createStreamServer(initTAddress("127.0.0.1", Port(pinned)))
proc dummyCb(
transp: DatagramTransport, raddr: TransportAddress
): Future[void] {.async: (raises: []).} =
discard
let takenUdp =
newDatagramTransport(dummyCb, local = initTAddress("0.0.0.0", Port(pinned)))
let nodeKey = generateSecp256k1Key()
let node = newTestWakuNode(nodeKey, parseIpAddress("0.0.0.0"), Port(0))
await node.start()
try:
var mb = MetricsServerConfBuilder.init()
mb.withEnabled(true)
mb.withHttpPort(0'u16)
let metricsRes =
await startMetricsServerAndLogging(mb.build().value.get(), 0'u16)
check metricsRes.isErr
var db = Discv5ConfBuilder.init()
db.withEnabled(true)
db.withUdpPort(0'u16)
let discv5Res = await setupAndStartDiscv5(
node.enr,
node.peerManager,
node.topicSubscriptionQueue,
db.build().value.get(),
@[],
node.rng,
nodeKey,
parseIpAddress("0.0.0.0"),
0'u16,
)
check discv5Res.isErr
finally:
takenTcp.stop()
await takenTcp.closeWait()
await takenUdp.closeWait()
await node.stop()
autoPortMin = origMin
autoPortMax = origMax

View File

@ -4,7 +4,7 @@ import
libp2p/crypto/[crypto, secp],
libp2p/multiaddress,
nimcrypto/utils,
std/[options, random, sequtils],
std/[net, options, random, sequtils],
results,
testutils/unittests
import
@ -18,6 +18,7 @@ suite "Waku Conf - build with cluster conf":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.discv5Conf.withUdpPort(9000)
builder.withRelayServiceRatio("50:50")
# Mount all shards in network
@ -62,6 +63,7 @@ suite "Waku Conf - build with cluster conf":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.withRelayServiceRatio("50:50")
builder.discv5Conf.withUdpPort(9000)
# Mount all shards in network
@ -95,6 +97,8 @@ suite "Waku Conf - build with cluster conf":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.discv5Conf.withUdpPort(0'u16)
let # Mount all shards in network
expectedShards = toSeq[0.uint16 .. 7.uint16]
@ -126,6 +130,8 @@ suite "Waku Conf - build with cluster conf":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.discv5Conf.withUdpPort(0'u16)
let shards = @[2.uint16, 3.uint16]
## Given
@ -171,6 +177,8 @@ suite "Waku Conf - build with cluster conf":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.discv5Conf.withUdpPort(0'u16)
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
# Mount all shards in network
@ -217,6 +225,8 @@ suite "Waku Conf - build with cluster conf":
## Setup
let networkConf = NetworkConf.LogosDevConf()
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.discv5Conf.withUdpPort(0'u16)
# Sanity check
check networkConf.shardingConf.kind == AutoSharding
@ -246,6 +256,8 @@ suite "Waku Conf - build with cluster conf":
## Given: emulate --preset=logos.dev --num-shards-in-network=0
let networkConf = NetworkConf.LogosDevConf()
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.discv5Conf.withUdpPort(0'u16)
builder.withNetworkConf(networkConf)
# Note: builder.withNumShardsInCluster() is not called when the
# value that comes from the CLI path is 0 (which means it was
@ -265,6 +277,7 @@ suite "Waku Conf - node key":
test "Node key is generated":
## Setup
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.withClusterId(1)
## Given
@ -288,6 +301,7 @@ suite "Waku Conf - node key":
let key = SkPrivateKey.init(utils.fromHex(nodeKeyStr)).tryGet()
crypto.PrivateKey(scheme: Secp256k1, skkey: key)
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.withClusterId(1)
## Given
@ -309,6 +323,7 @@ suite "Waku Conf - extMultiaddrs":
test "Valid multiaddresses are passed and accepted":
## Setup
var builder = WakuConfBuilder.init()
builder.withP2pTcpPort(0'u16)
builder.withClusterId(1)
## Given
@ -346,3 +361,66 @@ suite "Waku Conf Builder - rate limits":
## Then
assert res.isOk(), $res.error
suite "Waku Conf - port required":
test "p2pTcpPort not specified returns err":
## Setup: minimal builder with no withP2pTcpPort call
var builder = WakuConfBuilder.init()
## When
let res = builder.build()
## Then
check res.isErr()
check res.error == "p2pTcpPort is not specified"
test "discv5 enabled without udpPort returns err":
## Setup
var builder = WakuConfBuilder.init()
builder.discv5Conf.withEnabled(true)
## When
let res = builder.build()
## Then
check res.isErr()
check res.error == "Discv5 Conf building failed: discv5.udpPort is not specified"
test "metricsServer enabled without httpPort returns err":
## Setup
var builder = WakuConfBuilder.init()
builder.metricsServerConf.withEnabled(true)
## When
let res = builder.build()
## Then
check res.isErr()
check res.error ==
"Metrics Server Conf building failed: metricsServer.httpPort is not specified"
test "restServer enabled without port returns err":
## Setup: listenAddress must be set (checked before port)
var builder = WakuConfBuilder.init()
builder.restServerConf.withEnabled(true)
builder.restServerConf.withListenAddress(parseIpAddress("127.0.0.1"))
## When
let res = builder.build()
## Then
check res.isErr()
check res.error ==
"REST Server Conf building failed: restServer.port is not specified"
test "webSocket enabled without port returns err":
## Setup
var builder = WakuConfBuilder.init()
builder.webSocketConf.withEnabled(true)
## When
let res = builder.build()
## Then
check res.isErr()
check res.error == "WebSocket Conf building failed: websocket.port is not specified"

View File

@ -506,7 +506,8 @@ suite "Waku Discovery v5":
waku.conf.nodeKey,
waku.conf.endpointConf.p2pListenAddress,
waku.conf.portsShift,
)
).valueOr:
raiseAssert error
check:
waku.node.peerManager.switch.peerStore.peers().anyIt(
@ -537,7 +538,8 @@ suite "Waku Discovery v5":
waku.conf.nodeKey,
waku.conf.endpointConf.p2pListenAddress,
waku.conf.portsShift,
)
).valueOr:
raiseAssert error
check:
not waku.node.peerManager.switch.peerStore.peers().anyIt(

View File

@ -1,6 +1,7 @@
{.used.}
import
std/options,
testutils/unittests,
presto,
presto/client as presto_client,
@ -62,6 +63,37 @@ suite "Waku v2 REST API - Debug":
await restServer.closeWait()
await node.stop()
asyncTest "GET /info exposes node.ports":
let node = testWakuNode()
node.ports = BoundPorts(
tcp: some(1001'u16),
webSocket: some(1002'u16),
rest: some(1003'u16),
discv5Udp: some(1004'u16),
metrics: some(1005'u16),
)
let restAddress = parseIpAddress("0.0.0.0")
let restServer = WakuRestServerRef.init(restAddress, Port(0)).tryGet()
installDebugApiHandlers(restServer.router, node)
restServer.start()
let client = newRestHttpClient(
initTAddress(restAddress, restServer.httpServer.address.port)
)
let response = await client.debugInfoV1()
check:
response.status == 200
response.data.ports.tcp == some(1001'u16)
response.data.ports.webSocket == some(1002'u16)
response.data.ports.rest == some(1003'u16)
response.data.ports.discv5Udp == some(1004'u16)
response.data.ports.metrics == some(1005'u16)
await restServer.stop()
await restServer.closeWait()
asyncTest "Get node version - GET /version":
# Given
let node = testWakuNode()

View File

@ -192,8 +192,7 @@ type WakuNodeConf* = object
name: "listen-address"
.}: IpAddress
tcpPort* {.desc: "TCP listening port.", defaultValue: 60000, name: "tcp-port".}:
Port
tcpPort* {.desc: "TCP listening port.", defaultValue: 0, name: "tcp-port".}: uint16
portsShift* {.
desc: "Add a shift to all port numbers.", defaultValue: 0, name: "ports-shift"
@ -490,7 +489,7 @@ with the drawback of consuming some more bandwidth.""",
restPort* {.
desc: "Listening port of the REST HTTP server.",
defaultValue: 8645,
defaultValue: 0,
name: "rest-port"
.}: uint16
@ -531,7 +530,7 @@ with the drawback of consuming some more bandwidth.""",
metricsServerPort* {.
desc: "Listening HTTP port of the metrics server.",
defaultValue: 8008,
defaultValue: 0,
name: "metrics-server-port"
.}: uint16
@ -565,9 +564,9 @@ with the drawback of consuming some more bandwidth.""",
discv5UdpPort* {.
desc: "Listening UDP port for Node Discovery v5.",
defaultValue: 9000,
defaultValue: 0,
name: "discv5-udp-port"
.}: Port
.}: uint16
discv5BootstrapNodes* {.
desc:
@ -664,8 +663,8 @@ with the drawback of consuming some more bandwidth.""",
.}: bool
websocketPort* {.
desc: "WebSocket listening port.", defaultValue: 8000, name: "websocket-port"
.}: Port
desc: "WebSocket listening port.", defaultValue: 0, name: "websocket-port"
.}: uint16
websocketSecureSupport* {.
desc: "Enable secure websocket: true|false",

13
waku/common/auto_port.nim Normal file
View File

@ -0,0 +1,13 @@
{.push raises: [].}
import std/random
const AutoPortRetryCount* = 20
var
autoPortMin* = 50000'u16
autoPortMax* = 59000'u16
rng = initRand()
proc getAutoPort*(): uint16 =
uint16(rng.rand(autoPortMin.int .. autoPortMax.int))

View File

@ -10,7 +10,7 @@ import
eth/keys as eth_keys,
eth/p2p/discoveryv5/node,
eth/p2p/discoveryv5/protocol
import ../node/peer_manager/peer_manager, ../waku_core, ../waku_enr
import waku/[common/auto_port, node/peer_manager/peer_manager, waku_core, waku_enr]
export protocol, waku_enr
@ -409,7 +409,13 @@ proc setupDiscoveryV5*(
key: crypto.PrivateKey,
p2pListenAddress: IpAddress,
portsShift: uint16,
): WakuDiscoveryV5 =
): Result[WakuDiscoveryV5, string] =
if conf.udpPort == Port(0):
return err(
"setupDiscoveryV5: udpPort must be non-zero; " &
"use setupAndStartDiscv5 for port=0 auto-port retry"
)
let dynamicBootstrapEnrs =
dynamicBootstrapNodes.filterIt(it.hasUdpPort()).mapIt(it.enr.get())
@ -441,10 +447,52 @@ proc setupDiscoveryV5*(
autoupdateRecord: conf.enrAutoUpdate,
)
WakuDiscoveryV5.new(
rng, discv5Conf, some(myENR), some(nodePeerManager), nodeTopicSubscriptionQueue
ok(
WakuDiscoveryV5.new(
rng, discv5Conf, some(myENR), some(nodePeerManager), nodeTopicSubscriptionQueue
)
)
proc setupAndStartDiscv5*(
myENR: enr.Record,
nodePeerManager: PeerManager,
nodeTopicSubscriptionQueue: AsyncEventQueue[SubscriptionEvent],
conf: Discv5Conf,
dynamicBootstrapNodes: seq[RemotePeerInfo],
rng: ref HmacDrbgContext,
key: crypto.PrivateKey,
p2pListenAddress: IpAddress,
portsShift: uint16,
): Future[Result[WakuDiscoveryV5, string]] {.async: (raises: []).} =
## Construct and start a `WakuDiscoveryV5` instance, handling auto-port
## retry when the caller asks for `udpPort == 0`.
var c = conf
let autoMode = c.udpPort == Port(0)
let attempts = if autoMode: AutoPortRetryCount else: 1
var lastErr = ""
for attempt in 1 .. attempts:
if autoMode:
c.udpPort = Port(getAutoPort())
let wd = setupDiscoveryV5(
myENR, nodePeerManager, nodeTopicSubscriptionQueue, c, dynamicBootstrapNodes, rng,
key, p2pListenAddress, portsShift,
).valueOr:
return err(error)
let startRes = await wd.start()
if startRes.isOk():
return ok(wd)
lastErr = startRes.error
if autoMode:
return err("discv5: auto-port bind exhausted; last error: " & lastErr)
return err(lastErr)
proc udpPort*(wd: WakuDiscoveryV5): Port =
wd.conf.port
proc updateBootstrapRecords*(
self: var WakuDiscoveryV5, newRecordsString: string
): Result[void, string] =

View File

@ -38,8 +38,8 @@ proc withTableIpLimit*(b: var Discv5ConfBuilder, tableIpLimit: uint) =
proc withUdpPort*(b: var Discv5ConfBuilder, udpPort: Port) =
b.udpPort = some(udpPort)
proc withUdpPort*(b: var Discv5ConfBuilder, udpPort: uint) =
b.udpPort = some(Port(udpPort.uint16))
proc withUdpPort*(b: var Discv5ConfBuilder, udpPort: uint16) =
b.udpPort = some(Port(udpPort))
proc withBootstrapNodes*(b: var Discv5ConfBuilder, bootstrapNodes: seq[string]) =
# TODO: validate ENRs?
@ -49,6 +49,9 @@ proc build*(b: Discv5ConfBuilder): Result[Option[Discv5Conf], string] =
if not b.enabled.get(false):
return ok(none(Discv5Conf))
if b.udpPort.isNone():
return err("discv5.udpPort is not specified")
return ok(
some(
Discv5Conf(
@ -57,7 +60,7 @@ proc build*(b: Discv5ConfBuilder): Result[Option[Discv5Conf], string] =
bucketIpLimit: b.bucketIpLimit.get(2),
enrAutoUpdate: b.enrAutoUpdate.get(true),
tableIpLimit: b.tableIpLimit.get(10),
udpPort: b.udpPort.get(9000.Port),
udpPort: b.udpPort.get(),
)
)
)

View File

@ -36,11 +36,14 @@ proc build*(b: MetricsServerConfBuilder): Result[Option[MetricsServerConf], stri
if not b.enabled.get(false):
return ok(none(MetricsServerConf))
if b.httpPort.isNone():
return err("metricsServer.httpPort is not specified")
return ok(
some(
MetricsServerConf(
httpAddress: b.httpAddress.get(static parseIpAddress("127.0.0.1")),
httpPort: b.httpPort.get(8008.Port),
httpPort: b.httpPort.get(),
logging: b.logging.get(false),
)
)

View File

@ -8,11 +8,13 @@ import
results
import
../waku_conf,
../networks_config,
../../common/logging,
../../common/utils/parse_size_units,
../../waku_enr/capabilities,
waku/[
factory/waku_conf,
factory/networks_config,
common/logging,
common/utils/parse_size_units,
waku_enr/capabilities,
],
tools/confutils/entry_nodes
import
@ -574,12 +576,9 @@ proc build*(
warn "Nat Strategy is not specified, defaulting to none"
"none"
let p2pTcpPort =
if builder.p2pTcpPort.isSome():
builder.p2pTcpPort.get()
else:
warn "P2P Listening TCP Port is not specified, listening on 60000"
60000.Port
if builder.p2pTcpPort.isNone():
return err("p2pTcpPort is not specified")
let p2pTcpPort = builder.p2pTcpPort.get()
let p2pListenAddress =
if builder.p2pListenAddress.isSome():

View File

@ -202,6 +202,11 @@ proc new*(
else:
nil
if not restServer.isNil():
let boundRestPort = restServer.httpServer.address.port
node.ports.rest = some(boundRestPort.uint16)
wakuConf.restServerConf.get().port = boundRestPort
# Set the extMultiAddrsOnly flag so the node knows not to replace explicit addresses
node.extMultiAddrsOnly = wakuConf.endpointConf.extMultiAddrsOnly
@ -249,7 +254,7 @@ proc getPorts(
return ok((tcpPort: tcpPort, websocketPort: websocketPort))
proc getRunningNetConfig(waku: ptr Waku): Future[Result[NetConfig, string]] {.async.} =
var conf = waku[].conf
let conf = waku[].conf
let (tcpPort, websocketPort) = getPorts(waku[].node.switch.peerInfo.listenAddrs).valueOr:
return err("Could not retrieve ports: " & error)
@ -281,6 +286,10 @@ proc updateEnr(waku: ptr Waku): Future[Result[void, string]] {.async.} =
waku[].node.enr = record
# If TCP/WS was configured with port 0, node.announcedAddresses was built
# pre-bind with a port value of 0. In any case, the resync is harmless.
waku[].node.announcedAddresses = netConf.announcedAddresses
return ok()
proc updateAddressInENR(waku: ptr Waku): Result[void, string] =
@ -312,11 +321,8 @@ proc updateAddressInENR(waku: ptr Waku): Result[void, string] =
return ok()
proc updateWaku(waku: ptr Waku): Future[Result[void, string]] {.async.} =
let conf = waku[].conf
if conf.endpointConf.p2pTcpPort == Port(0) or
(conf.websocketConf.isSome() and conf.websocketConf.get.port == Port(0)):
(await updateEnr(waku)).isOkOr:
return err("error calling updateEnr: " & $error)
(await updateEnr(waku)).isOkOr:
return err("error calling updateEnr: " & $error)
?updateAnnouncedAddrWithPrimaryIpAddr(waku[].node)
@ -390,29 +396,39 @@ proc startWaku*(waku: ptr Waku): Future[Result[void, string]] {.async: (raises:
(await startNode(waku.node, waku.conf, waku.dynamicBootstrapNodes)).isOkOr:
return err("error while calling startNode: " & $error)
## Update waku data that is set dynamically on node start
try:
(await updateWaku(waku)).isOkOr:
return err("Error in updateApp: " & $error)
except CatchableError:
return err("Caught exception in updateApp: " & getCurrentExceptionMsg())
let bound = getPorts(waku.node.switch.peerInfo.listenAddrs).valueOr:
return err("failed to read bound ports from switch: " & $error)
if bound.tcpPort.isSome():
waku[].node.ports.tcp = some(bound.tcpPort.get().uint16)
if bound.websocketPort.isSome():
waku[].node.ports.webSocket = some(bound.websocketPort.get().uint16)
## Discv5
if conf.discv5Conf.isSome():
waku[].wakuDiscV5 = waku_discv5.setupDiscoveryV5(
waku.node.enr,
waku.node.peerManager,
waku.node.topicSubscriptionQueue,
conf.discv5Conf.get(),
waku.dynamicBootstrapNodes,
waku.rng,
conf.nodeKey,
conf.endpointConf.p2pListenAddress,
conf.portsShift,
)
waku[].wakuDiscV5 = (
await waku_discv5.setupAndStartDiscv5(
waku.node.enr,
waku.node.peerManager,
waku.node.topicSubscriptionQueue,
conf.discv5Conf.get(),
waku.dynamicBootstrapNodes,
waku.rng,
conf.nodeKey,
conf.endpointConf.p2pListenAddress,
conf.portsShift,
)
).valueOr:
return err("failed to start waku discovery v5: " & error)
(await waku.wakuDiscV5.start()).isOkOr:
return err("failed to start waku discovery v5: " & $error)
waku[].node.ports.discv5Udp = some(waku[].wakuDiscV5.udpPort.uint16)
waku[].conf.discv5Conf.get().udpPort = waku[].wakuDiscV5.udpPort
## Update waku data that is set dynamically on node start
try:
(await updateWaku(waku)).isOkOr:
return err("Error in updateWaku: " & $error)
except CatchableError:
return err("Caught exception in updateWaku: " & getCurrentExceptionMsg())
## Reliability
if not waku[].deliveryService.isNil():
@ -482,14 +498,15 @@ proc startWaku*(waku: ptr Waku): Future[Result[void, string]] {.async: (raises:
if conf.metricsServerConf.isSome():
try:
waku[].metricsServer = (
await (
waku_metrics.startMetricsServerAndLogging(
conf.metricsServerConf.get(), conf.portsShift
)
let (server, port) = (
await waku_metrics.startMetricsServerAndLogging(
conf.metricsServerConf.get(), conf.portsShift
)
).valueOr:
return err("Starting monitoring and external interfaces failed: " & error)
waku[].metricsServer = server
waku[].node.ports.metrics = some(port.uint16)
waku[].conf.metricsServerConf.get().httpPort = port
except CatchableError:
return err(
"Caught exception starting monitoring and external interfaces failed: " &

View File

@ -2,8 +2,11 @@
import chronicles, chronos, metrics, metrics/chronos_httpserver
import
../waku_rln_relay/protocol_metrics as rln_metrics,
../utils/collector,
waku/[
common/auto_port,
waku_rln_relay/protocol_metrics as rln_metrics,
utils/collector,
],
./peer_manager,
./waku_node
@ -57,27 +60,44 @@ proc startMetricsLog*() =
discard setTimer(Moment.fromNow(LogInterval), logMetrics)
type StartedMetricsServer* = tuple[server: MetricsHttpServerRef, port: Port]
proc startMetricsServer(
serverIp: IpAddress, serverPort: Port
): Future[Result[MetricsHttpServerRef, string]] {.async.} =
info "Starting metrics HTTP server", serverIp = $serverIp, serverPort = $serverPort
): Future[Result[StartedMetricsServer, string]] {.async.} =
let autoMode = serverPort == Port(0)
let attempts = if autoMode: AutoPortRetryCount else: 1
var lastErr = ""
let server = MetricsHttpServerRef.new($serverIp, serverPort).valueOr:
return err("metrics HTTP server start failed: " & $error)
for attempt in 1 .. attempts:
let port =
if autoMode:
Port(getAutoPort())
else:
serverPort
info "Starting metrics HTTP server", serverIp = $serverIp, serverPort = $port
try:
await server.start()
except CatchableError:
return err("metrics HTTP server start failed: " & getCurrentExceptionMsg())
let server = MetricsHttpServerRef.new($serverIp, port).valueOr:
lastErr = $error
continue
info "Metrics HTTP server started", serverIp = $serverIp, serverPort = $serverPort
return ok(server)
try:
await server.start()
except CatchableError:
lastErr = getCurrentExceptionMsg()
continue
info "Metrics HTTP server started", serverIp = $serverIp, serverPort = $port
return ok((server: server, port: port))
if autoMode:
return err("metrics HTTP server: auto-port bind exhausted; last error: " & lastErr)
return err("metrics HTTP server start failed: " & lastErr)
proc startMetricsServerAndLogging*(
conf: MetricsServerConf, portsShift: uint16
): Future[Result[MetricsHttpServerRef, string]] {.async.} =
var metricsServer: MetricsHttpServerRef
metricsServer = (
): Future[Result[StartedMetricsServer, string]] {.async.} =
let started = (
await (
startMetricsServer(conf.httpAddress, Port(conf.httpPort.uint16 + portsShift))
)
@ -87,4 +107,4 @@ proc startMetricsServerAndLogging*(
if conf.logging:
startMetricsLog()
return ok(metricsServer)
return ok(started)

View File

@ -96,11 +96,19 @@ const WakuNodeVersionString* = "version / git commit hash: " & git_version
# key and crypto modules different
type
BoundPorts* = object ## Set by the factory once each service has bound to a port.
tcp*: Option[uint16]
webSocket*: Option[uint16]
rest*: Option[uint16]
discv5Udp*: Option[uint16]
metrics*: Option[uint16]
# TODO: Move to application instance (e.g., `WakuNode2`)
WakuInfo* = object # NOTE One for simplicity, can extend later as needed
listenAddresses*: seq[string]
enrUri*: string #multiaddrStrings*: seq[string]
mixPubKey*: Option[string]
ports*: BoundPorts
# NOTE based on Eth2Node in NBC eth2_network.nim
WakuNode* = ref object
@ -140,6 +148,7 @@ type
wakuMix*: WakuMix
kademliaDiscoveryLoop*: Future[void]
wakuKademlia*: WakuKademlia
ports*: BoundPorts
proc deduceRelayShard(
node: WakuNode,
@ -244,11 +253,13 @@ proc info*(node: WakuNode): WakuInfo =
let peerInfo = node.switch.peerInfo
var listenStr: seq[string]
for address in node.announcedAddresses:
# Post-bind: when a transport was given port=0, this reflects the real
# OS-assigned port rather than the pre-bind configured 0.
for address in peerInfo.listenAddrs:
var fulladdr = $address & "/p2p/" & $peerInfo.peerId
listenStr &= fulladdr
let enrUri = node.enr.toUri()
var wakuInfo = WakuInfo(listenAddresses: listenStr, enrUri: enrUri)
var wakuInfo = WakuInfo(listenAddresses: listenStr, enrUri: enrUri, ports: node.ports)
if not node.wakuMix.isNil():
let keyStr = node.wakuMix.pubKey.to0xHex()
wakuInfo.mixPubKey = some(keyStr)

View File

@ -10,6 +10,7 @@ type DebugWakuInfo* = object
listenAddresses*: seq[string]
enrUri*: Option[string]
mixPubKey*: Option[string]
ports*: BoundPorts
#### Type conversion
@ -18,10 +19,45 @@ proc toDebugWakuInfo*(nodeInfo: WakuInfo): DebugWakuInfo =
listenAddresses: nodeInfo.listenAddresses,
enrUri: some(nodeInfo.enrUri),
mixPubKey: nodeInfo.mixPubKey,
ports: nodeInfo.ports,
)
#### Serialization and deserialization
proc writeValue*(
writer: var JsonWriter[RestJson], value: BoundPorts
) {.raises: [IOError].} =
writer.beginRecord()
if value.tcp.isSome():
writer.writeField("tcp", value.tcp.get())
if value.webSocket.isSome():
writer.writeField("webSocket", value.webSocket.get())
if value.rest.isSome():
writer.writeField("rest", value.rest.get())
if value.discv5Udp.isSome():
writer.writeField("discv5Udp", value.discv5Udp.get())
if value.metrics.isSome():
writer.writeField("metrics", value.metrics.get())
writer.endRecord()
proc readValue*(
reader: var JsonReader[RestJson], value: var BoundPorts
) {.raises: [SerializationError, IOError].} =
for fieldName in readObjectFields(reader):
case fieldName
of "tcp":
value.tcp = some(reader.readValue(uint16))
of "webSocket":
value.webSocket = some(reader.readValue(uint16))
of "rest":
value.rest = some(reader.readValue(uint16))
of "discv5Udp":
value.discv5Udp = some(reader.readValue(uint16))
of "metrics":
value.metrics = some(reader.readValue(uint16))
else:
unrecognizedFieldWarning(value)
proc writeValue*(
writer: var JsonWriter[RestJson], value: DebugWakuInfo
) {.raises: [IOError].} =
@ -31,6 +67,7 @@ proc writeValue*(
writer.writeField("enrUri", value.enrUri.get())
if value.mixPubKey.isSome():
writer.writeField("mixPubKey", value.mixPubKey.get())
writer.writeField("ports", value.ports)
writer.endRecord()
proc readValue*(
@ -39,6 +76,7 @@ proc readValue*(
var
listenAddresses: Option[seq[string]]
enrUri: Option[string]
ports: BoundPorts
for fieldName in readObjectFields(reader):
case fieldName
@ -58,6 +96,8 @@ proc readValue*(
"Multiple `mixPubKey` fields found", "DebugWakuInfo"
)
value.mixPubKey = some(reader.readValue(string))
of "ports":
ports = reader.readValue(BoundPorts)
else:
unrecognizedFieldWarning(value)
@ -65,5 +105,8 @@ proc readValue*(
reader.raiseUnexpectedValue("Field `listenAddresses` is missing")
value = DebugWakuInfo(
listenAddresses: listenAddresses.get, enrUri: enrUri, mixPubKey: value.mixPubKey
listenAddresses: listenAddresses.get,
enrUri: enrUri,
mixPubKey: value.mixPubKey,
ports: ports,
)