feat: Improve config (v2) (#3925)

* rename NetworkConf -> NetworkPresetConf and related procs/vars
* Rewrite applyNetworkPresetConf to apply user-set fields over preset fields
* New dedicated parser for configJson
* Fix tests to use actual extract JSON nodeconf parser
* Change all confbuilder defaults from literal values to DefaultXXX consts
* Change int/bool WakuNodeConf fields to Option to get user intent w/o sentinels
* Make Option CLI default-value help mention defaults now owned by confbuilder
* Document CLI defaults that differ from confbuilder defaults
* Fix agent-string builder default deviating from CLI default
* Add WakuConfBuilder.enforceSecurityConstraints()
* Fail on RLN user preset overrides instead of drop-and-continue
* Add regression tests for initial set of conf constraints
* fix kademliaDiscoveryConfBuilder.build() enable/disable kad logic
* Misc refactors, fixes
* Add tests

Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
This commit is contained in:
Fabiana Cecin 2026-06-10 09:09:22 -03:00 committed by GitHub
parent 362c35f2fb
commit 41b5c4906f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 709 additions and 445 deletions

View File

@ -95,7 +95,7 @@ when isMainModule:
wakuNodeConf.shards = @[conf.shard]
wakuNodeConf.contentTopics = conf.contentTopics
wakuNodeConf.clusterId = conf.clusterId
wakuNodeConf.clusterId = some(conf.clusterId)
## TODO: Depending on the tester needs we might extend here with shards, clusterId, etc...
wakuNodeConf.metricsServer = true

View File

@ -550,7 +550,7 @@ when isMainModule:
info "cli flags", conf = conf
if conf.clusterId == 1:
let twnNetworkConf = NetworkConf.TheWakuNetworkConf()
let twnNetworkConf = NetworkPresetConf.TheWakuNetworkConf()
conf.bootstrapNodes = twnNetworkConf.discv5BootstrapNodes
conf.rlnRelayDynamic = twnNetworkConf.rlnRelayDynamic

View File

@ -21,7 +21,7 @@ proc setup*(): Waku =
error "failure while loading the configuration", error = $error
quit(QuitFailure)
let twnNetworkConf = NetworkConf.TheWakuNetworkConf()
let twnNetworkConf = NetworkPresetConf.TheWakuNetworkConf()
if len(conf.shards) != 0:
conf.pubsubTopics = conf.shards.mapIt(twnNetworkConf.pubsubTopics[it.uint16])
else:
@ -29,18 +29,18 @@ proc setup*(): Waku =
# Override configuration
conf.maxMessageSize = twnNetworkConf.maxMessageSize
conf.clusterId = twnNetworkConf.clusterId
conf.clusterId = some(twnNetworkConf.clusterId)
conf.rlnRelayEthContractAddress = twnNetworkConf.rlnRelayEthContractAddress
conf.rlnRelayDynamic = twnNetworkConf.rlnRelayDynamic
conf.discv5Discovery = twnNetworkConf.discv5Discovery
conf.rlnRelayDynamic = some(twnNetworkConf.rlnRelayDynamic)
conf.discv5Discovery = some(twnNetworkConf.discv5Discovery)
conf.discv5BootstrapNodes =
conf.discv5BootstrapNodes & twnNetworkConf.discv5BootstrapNodes
conf.rlnEpochSizeSec = twnNetworkConf.rlnEpochSizeSec
conf.rlnRelayUserMessageLimit = twnNetworkConf.rlnRelayUserMessageLimit
conf.rlnEpochSizeSec = some(twnNetworkConf.rlnEpochSizeSec)
conf.rlnRelayUserMessageLimit = some(twnNetworkConf.rlnRelayUserMessageLimit)
# Only set rlnRelay to true if relay is configured
if conf.relay:
conf.rlnRelay = twnNetworkConf.rlnRelay
conf.rlnRelay = some(twnNetworkConf.rlnRelay)
info "Starting node"
var waku = (waitFor Waku.new(conf)).valueOr:

View File

@ -1,11 +1,11 @@
import std/[json, strutils, tables]
import chronos, chronicles, results, confutils, confutils/std/net, ffi
import std/json
import chronos, chronicles, results, ffi
import
logos_delivery/waku/factory/waku,
logos_delivery/waku/node/waku_node,
logos_delivery/waku/api/[api, types],
logos_delivery/waku/events/[message_events, health_events],
tools/confutils/cli_args,
tools/confutils/conf_from_json,
../declare_lib,
../json_event
@ -15,59 +15,11 @@ proc `%`*(id: RequestId): JsonNode =
registerReqFFI(CreateNodeRequest, ctx: ptr FFIContext[Waku]):
proc(configJson: cstring): Future[Result[string, string]] {.async.} =
## Parse the JSON configuration using fieldPairs approach (WakuNodeConf)
var conf = defaultWakuNodeConf().valueOr:
return err("Failed creating default conf: " & error)
let conf = parseNodeConfFromJson($configJson).valueOr:
error "Failed to assemble WakuNodeConf from JSON",
error = error, configJson = $configJson
return err("failed parseNodeConfFromJson " & error)
var jsonNode: JsonNode
try:
jsonNode = parseJson($configJson)
except Exception:
let exceptionMsg = getCurrentExceptionMsg()
error "Failed to parse config JSON",
error = exceptionMsg, configJson = $configJson
return err(
"Failed to parse config JSON: " & exceptionMsg & " configJson string: " &
$configJson
)
var jsonFields: Table[string, (string, JsonNode)]
for key, value in jsonNode:
let lowerKey = key.toLowerAscii()
if jsonFields.hasKey(lowerKey):
error "Duplicate configuration option found when normalized to lowercase",
key = key
return err(
"Duplicate configuration option found when normalized to lowercase: '" & key &
"'"
)
jsonFields[lowerKey] = (key, value)
for confField, confValue in fieldPairs(conf):
let lowerField = confField.toLowerAscii()
if jsonFields.hasKey(lowerField):
let (jsonKey, jsonValue) = jsonFields[lowerField]
let formattedString = ($jsonValue).strip(chars = {'\"'})
try:
confValue = parseCmdArg(typeof(confValue), formattedString)
except Exception:
return err(
"Failed to parse field '" & confField & "' from JSON key '" & jsonKey & "': " &
getCurrentExceptionMsg() & ". Value: " & formattedString
)
jsonFields.del(lowerField)
if jsonFields.len > 0:
var unknownKeys = newSeq[string]()
for _, (jsonKey, _) in pairs(jsonFields):
unknownKeys.add(jsonKey)
error "Unrecognized configuration option(s) found", option = unknownKeys
return err("Unrecognized configuration option(s) found: " & $unknownKeys)
# Create the node
ctx.myLib[] = (await api.createNode(conf)).valueOr:
let errMsg = $error
chronicles.error "CreateNodeRequest failed", err = errMsg
@ -96,7 +48,7 @@ proc logosdelivery_create_node(
): pointer {.dynlib, exportc, cdecl.} =
initializeLibrary()
if isNil(callback):
if callback.isNil():
echo "error: missing callback in logosdelivery_create_node"
return nil

View File

@ -1,4 +1,6 @@
import chronicles, chronos, results
import std/[net, options]
import chronicles, chronos, libp2p/peerid, results
import logos_delivery/waku/factory/waku
import logos_delivery/messaging/messaging_client

View File

@ -4,7 +4,13 @@ import ../waku_conf
logScope:
topics = "waku conf builder discv5"
const DefaultDiscv5UdpPort*: Port = Port(9000)
const
DefaultDiscv5Enabled*: bool = false
DefaultDiscv5BitsPerHop: int = 1
DefaultDiscv5BucketIpLimit: uint = 2
DefaultDiscv5EnrAutoUpdate: bool = true
DefaultDiscv5TableIpLimit: uint = 10
DefaultDiscv5UdpPort: Port = Port(9000)
###########################
## Discv5 Config Builder ##
@ -48,17 +54,17 @@ proc withBootstrapNodes*(b: var Discv5ConfBuilder, bootstrapNodes: seq[string])
b.bootstrapNodes = concat(b.bootstrapNodes, bootstrapNodes)
proc build*(b: Discv5ConfBuilder): Result[Option[Discv5Conf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultDiscv5Enabled):
return ok(none(Discv5Conf))
return ok(
some(
Discv5Conf(
bootstrapNodes: b.bootstrapNodes,
bitsPerHop: b.bitsPerHop.get(1),
bucketIpLimit: b.bucketIpLimit.get(2),
enrAutoUpdate: b.enrAutoUpdate.get(true),
tableIpLimit: b.tableIpLimit.get(10),
bitsPerHop: b.bitsPerHop.get(DefaultDiscv5BitsPerHop),
bucketIpLimit: b.bucketIpLimit.get(DefaultDiscv5BucketIpLimit),
enrAutoUpdate: b.enrAutoUpdate.get(DefaultDiscv5EnrAutoUpdate),
tableIpLimit: b.tableIpLimit.get(DefaultDiscv5TableIpLimit),
udpPort: b.udpPort.get(DefaultDiscv5UdpPort),
)
)

View File

@ -4,6 +4,12 @@ import ../waku_conf
logScope:
topics = "waku conf builder filter service"
const
DefaultFilterEnabled: bool = false
DefaultFilterMaxPeersToServe: uint32 = 500
DefaultFilterSubscriptionTimeout: uint16 = 300
DefaultFilterMaxCriteria: uint32 = 1000
###################################
## Filter Service Config Builder ##
###################################
@ -37,15 +43,15 @@ proc withMaxCriteria*(b: var FilterServiceConfBuilder, maxCriteria: uint32) =
b.maxCriteria = some(maxCriteria)
proc build*(b: FilterServiceConfBuilder): Result[Option[FilterServiceConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultFilterEnabled):
return ok(none(FilterServiceConf))
return ok(
some(
FilterServiceConf(
maxPeersToServe: b.maxPeersToServe.get(500),
subscriptionTimeout: b.subscriptionTimeout.get(300),
maxCriteria: b.maxCriteria.get(1000),
maxPeersToServe: b.maxPeersToServe.get(DefaultFilterMaxPeersToServe),
subscriptionTimeout: b.subscriptionTimeout.get(DefaultFilterSubscriptionTimeout),
maxCriteria: b.maxCriteria.get(DefaultFilterMaxCriteria),
)
)
)

View File

@ -5,18 +5,20 @@ import logos_delivery/waku/factory/waku_conf
logScope:
topics = "waku conf builder kademlia discovery"
const DefaultKadEnabled*: bool = false
#######################################
## Kademlia Discovery Config Builder ##
#######################################
type KademliaDiscoveryConfBuilder* = object
enabled*: bool
enabled*: Option[bool]
bootstrapNodes*: seq[string]
proc init*(T: type KademliaDiscoveryConfBuilder): KademliaDiscoveryConfBuilder =
KademliaDiscoveryConfBuilder()
proc withEnabled*(b: var KademliaDiscoveryConfBuilder, enabled: bool) =
b.enabled = enabled
b.enabled = some(enabled)
proc withBootstrapNodes*(
b: var KademliaDiscoveryConfBuilder, bootstrapNodes: seq[string]
@ -26,11 +28,12 @@ proc withBootstrapNodes*(
proc build*(
b: KademliaDiscoveryConfBuilder
): Result[Option[KademliaDiscoveryConf], string] =
# Kademlia is enabled if explicitly enabled OR if bootstrap nodes are provided
let enabled = b.enabled or b.bootstrapNodes.len > 0
if not enabled:
# Explicit disable wins: enabled=false disables regardless of bootstrap nodes.
if b.enabled == some(false):
return ok(none(KademliaDiscoveryConf))
# Otherwise enabled if config-enabled or any bootstrap nodes are provided.
if not b.enabled.get(DefaultKadEnabled) and b.bootstrapNodes.len == 0:
return ok(none(KademliaDiscoveryConf))
var parsedNodes: seq[(PeerId, seq[MultiAddress])]
for nodeStr in b.bootstrapNodes:
let (peerId, ma) = parseFullAddress(nodeStr).valueOr:

View File

@ -4,7 +4,11 @@ import ../waku_conf
logScope:
topics = "waku conf builder metrics server"
const DefaultMetricsHttpPort*: Port = Port(8008)
const
DefaultMetricsEnabled: bool = false
DefaultMetricsHttpAddress: IpAddress = static parseIpAddress("127.0.0.1")
DefaultMetricsHttpPort: Port = Port(8008)
DefaultMetricsLogging: bool = false
###################################
## Metrics Server Config Builder ##
@ -35,15 +39,15 @@ proc withLogging*(b: var MetricsServerConfBuilder, logging: bool) =
b.logging = some(logging)
proc build*(b: MetricsServerConfBuilder): Result[Option[MetricsServerConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultMetricsEnabled):
return ok(none(MetricsServerConf))
return ok(
some(
MetricsServerConf(
httpAddress: b.httpAddress.get(static parseIpAddress("127.0.0.1")),
httpAddress: b.httpAddress.get(DefaultMetricsHttpAddress),
httpPort: b.httpPort.get(DefaultMetricsHttpPort),
logging: b.logging.get(false),
logging: b.logging.get(DefaultMetricsLogging),
)
)
)

View File

@ -5,6 +5,8 @@ import ../waku_conf, logos_delivery/waku/waku_mix
logScope:
topics = "waku conf builder mix"
const DefaultMixEnabled: bool = false
##################################
## Mix Config Builder ##
##################################
@ -26,7 +28,7 @@ proc withMixNodes*(b: var MixConfBuilder, mixNodes: seq[MixNodePubInfo]) =
b.mixNodes = mixNodes
proc build*(b: MixConfBuilder): Result[Option[MixConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultMixEnabled):
return ok(none[MixConf]())
else:
if b.mixKey.isSome():

View File

@ -4,7 +4,10 @@ import ../waku_conf
logScope:
topics = "waku conf builder rest server"
const DefaultRestPort*: Port = Port(8645)
const
DefaultRestEnabled: bool = false
DefaultRestPort: Port = Port(8645)
DefaultRestAdmin: bool = false
################################
## REST Server Config Builder ##
@ -43,7 +46,7 @@ proc withRelayCacheCapacity*(b: var RestServerConfBuilder, relayCacheCapacity: u
b.relayCacheCapacity = some(relayCacheCapacity)
proc build*(b: RestServerConfBuilder): Result[Option[RestServerConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultRestEnabled):
return ok(none(RestServerConf))
if b.listenAddress.isNone():
@ -57,7 +60,7 @@ proc build*(b: RestServerConfBuilder): Result[Option[RestServerConf], string] =
allowOrigin: b.allowOrigin,
listenAddress: b.listenAddress.get(),
port: b.port.get(DefaultRestPort),
admin: b.admin.get(false),
admin: b.admin.get(DefaultRestAdmin),
relayCacheCapacity: b.relayCacheCapacity.get(),
)
)

View File

@ -4,6 +4,11 @@ import ../waku_conf
logScope:
topics = "waku conf builder rln relay"
const
DefaultRlnRelayEnabled*: bool = false
DefaultRlnRelayEpochSizeSec*: uint64 = 1
DefaultRlnRelayUserMessageLimit*: uint64 = 1
##############################
## RLN Relay Config Builder ##
##############################
@ -56,7 +61,7 @@ proc withUserMessageLimit*(b: var RlnRelayConfBuilder, userMessageLimit: uint64)
b.userMessageLimit = some(userMessageLimit)
proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultRlnRelayEnabled):
return ok(none(RlnRelayConf))
if b.chainId.isNone():
@ -78,11 +83,6 @@ proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] =
return err("rlnRelay.ethClientUrls is not specified")
if b.ethContractAddress.get("") == "":
return err("rlnRelay.ethContractAddress is not specified")
if b.epochSizeSec.isNone():
return err("rlnRelay.epochSizeSec is not specified")
if b.userMessageLimit.isNone():
return err("rlnRelay.userMessageLimit is not specified")
return ok(
some(
RlnRelayConf(
@ -92,8 +92,8 @@ proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] =
dynamic: b.dynamic.get(),
ethClientUrls: b.ethClientUrls.get(),
ethContractAddress: b.ethContractAddress.get(),
epochSizeSec: b.epochSizeSec.get(),
userMessageLimit: b.userMessageLimit.get(),
epochSizeSec: b.epochSizeSec.get(DefaultRlnRelayEpochSizeSec),
userMessageLimit: b.userMessageLimit.get(DefaultRlnRelayUserMessageLimit),
)
)
)

View File

@ -5,6 +5,14 @@ import ../waku_conf, ./store_sync_conf_builder
logScope:
topics = "waku conf builder store service"
const
DefaultStoreEnabled: bool = false
DefaultStoreDbMigration: bool = true
DefaultStoreDbVacuum: bool = false
DefaultStoreMaxNumDbConnections: int = 50
DefaultStoreResume: bool = false
DefaultStoreRetentionPolicy: string = "time:" & $2.days.seconds
##################################
## Store Service Config Builder ##
##################################
@ -77,7 +85,7 @@ proc validateRetentionPolicies(policies: seq[string]): Result[void, string] =
return ok()
proc build*(b: StoreServiceConfBuilder): Result[Option[StoreServiceConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultStoreEnabled):
return ok(none(StoreServiceConf))
if b.dbUrl.get("") == "":
@ -88,7 +96,7 @@ proc build*(b: StoreServiceConfBuilder): Result[Option[StoreServiceConf], string
let retentionPolicies =
if b.retentionPolicies.len == 0:
@["time:" & $2.days.seconds]
@[DefaultStoreRetentionPolicy]
else:
validateRetentionPolicies(b.retentionPolicies).isOkOr:
return err("invalid retention policies: " & error)
@ -97,12 +105,12 @@ proc build*(b: StoreServiceConfBuilder): Result[Option[StoreServiceConf], string
return ok(
some(
StoreServiceConf(
dbMigration: b.dbMigration.get(true),
dbMigration: b.dbMigration.get(DefaultStoreDbMigration),
dbURl: b.dbUrl.get(),
dbVacuum: b.dbVacuum.get(false),
maxNumDbConnections: b.maxNumDbConnections.get(50),
dbVacuum: b.dbVacuum.get(DefaultStoreDbVacuum),
maxNumDbConnections: b.maxNumDbConnections.get(DefaultStoreMaxNumDbConnections),
retentionPolicies: retentionPolicies,
resume: b.resume.get(false),
resume: b.resume.get(DefaultStoreResume),
storeSyncConf: storeSyncConf,
)
)

View File

@ -4,6 +4,8 @@ import ../waku_conf
logScope:
topics = "waku conf builder store sync"
const DefaultStoreSyncEnabled: bool = false
##################################
## Store Sync Config Builder ##
##################################
@ -30,7 +32,7 @@ proc withRelayJitterSec*(b: var StoreSyncConfBuilder, relayJitterSec: uint32) =
b.relayJitterSec = some(relayJitterSec)
proc build*(b: StoreSyncConfBuilder): Result[Option[StoreSyncConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultStoreSyncEnabled):
return ok(none(StoreSyncConf))
if b.rangeSec.isNone():

View File

@ -13,6 +13,9 @@ import
factory/networks_config,
common/logging,
common/utils/parse_size_units,
node/peer_manager,
waku_core/message/default_values,
waku_core/topics/pubsub_topic,
waku_enr/capabilities,
persistency/persistency,
],
@ -35,9 +38,38 @@ import
logScope:
topics = "waku conf builder"
# Picks up the same -d:git_version=... build flag that cli_args.nim defines.
const git_version {.strdefine.} = "(unknown)"
const
DefaultMaxConnections* = 150
DefaultP2pTcpPort*: Port = Port(60000)
DefaultMaxConnections = 150
DefaultRelay: bool = false
# historical confbuilder default; wakunode2 CLI deviates (true)
DefaultLightPush: bool = false
DefaultPeerExchange: bool = false
# historical confbuilder default; wakunode2 CLI deviates (true)
DefaultStoreSyncMount: bool = false
DefaultRendezvous: bool = false
# historical confbuilder default; wakunode2 CLI deviates (true)
DefaultMix*: bool = false
DefaultRelayPeerExchange: bool = false
DefaultLogLevel: logging.LogLevel = logging.LogLevel.INFO
DefaultLogFormat: logging.LogFormat = logging.LogFormat.TEXT
DefaultNatStrategy: string = "none"
DefaultP2pTcpPort: Port = Port(60000)
DefaultP2pListenAddress: IpAddress = static parseIpAddress("0.0.0.0")
DefaultPortsShift: uint16 = 0
DefaultExtMultiAddrsOnly: bool = false
DefaultDnsAddrsNameServers: seq[IpAddress] =
@[static parseIpAddress("1.1.1.1"), static parseIpAddress("1.0.0.1")]
DefaultPeerPersistence: bool = false
DefaultAgentString*: string = "logos-delivery-" & git_version
DefaultRelayShardedPeerManagement: bool = false
DefaultRelayServiceRatio: string = "50:50"
DefaultCircuitRelayClient: bool = false
DefaultP2pReliability*: bool = true
DefaultNumShardsInCluster: uint16 = 1
DefaultShardingConfKind: ShardingConfKind = AutoSharding
type MaxMessageSizeKind* = enum
mmskNone
@ -99,7 +131,7 @@ type WakuConfBuilder* = object
# TODO: move within a relayConf
rendezvous: Option[bool]
networkConf: Option[NetworkConf]
networkPresetConf: Option[NetworkPresetConf]
staticNodes: seq[string]
@ -153,8 +185,10 @@ proc init*(T: type WakuConfBuilder): WakuConfBuilder =
kademliaDiscoveryConf: KademliaDiscoveryConfBuilder.init(),
)
proc withNetworkConf*(b: var WakuConfBuilder, networkConf: NetworkConf) =
b.networkConf = some(networkConf)
proc withNetworkPresetConf*(
b: var WakuConfBuilder, networkPresetConf: NetworkPresetConf
) =
b.networkPresetConf = some(networkPresetConf)
proc withNodeKey*(b: var WakuConfBuilder, nodeKey: crypto.PrivateKey) =
b.nodeKey = some(nodeKey)
@ -309,121 +343,129 @@ proc buildShardingConf(
bNumShardsInCluster: Option[uint16],
bSubscribeShards: Option[seq[uint16]],
): (ShardingConf, seq[uint16]) =
case bShardingConfKind.get(AutoSharding)
case bShardingConfKind.get(DefaultShardingConfKind)
of StaticSharding:
(ShardingConf(kind: StaticSharding), bSubscribeShards.get(@[]))
of AutoSharding:
let numShardsInCluster = bNumShardsInCluster.get(1)
let numShardsInCluster = bNumShardsInCluster.get(DefaultNumShardsInCluster)
let shardingConf =
ShardingConf(kind: AutoSharding, numShardsInCluster: numShardsInCluster)
let upperShard = uint16(numShardsInCluster - 1)
(shardingConf, bSubscribeShards.get(toSeq(0.uint16 .. upperShard)))
proc applyNetworkConf(builder: var WakuConfBuilder) =
# Apply network conf, overrides most values passed individually
# If you want to tweak values, don't use networkConf
# TODO: networkconf should be one field of the conf builder so that this function becomes unnecessary
if builder.networkConf.isNone():
return
let networkConf = builder.networkConf.get()
template checkSetPresetValueToField[T](
field: var Option[T], presetVal: T, msg: static string
) =
## Set the field to the preset's value, unless the field is already set
## (explicit wins). Warn iff the field's existing value differs from the
## preset's. No-op if they agree.
if builder.clusterId.isSome():
warn "Cluster id was provided alongside a network conf",
used = networkConf.clusterId, discarded = builder.clusterId.get()
builder.clusterId = some(networkConf.clusterId)
if field.isSome():
if field.get() != presetVal:
warn msg, used = field.get(), discarded = presetVal
else:
field = some(presetVal)
proc checkAddPresetValueToField[T](field: var seq[T], presetVals: seq[T]) =
## Append the preset's list values to the field's existing list. Lists
## concat rather than override; both the user's and the preset's entries
## end up in the final list.
field = field & presetVals
proc applyNetworkPresetConf(builder: var WakuConfBuilder) =
## NetworkPresetConf = network presets.
## Cascade the chosen preset's values onto builder fields the user hasn't set.
## User-set fields stay; preset fills the gaps and warns on conflict (explicit wins).
## List fields concat (preset's nodes appended to user's).
if builder.networkPresetConf.isNone():
return # If there is no preset given, then nothing to do.
let networkPresetConf = builder.networkPresetConf.get()
checkSetPresetValueToField(
builder.clusterId, networkPresetConf.clusterId,
"Cluster id was provided alongside a network conf",
)
# Apply relay parameters
if builder.relay.get(false) and networkConf.rlnRelay:
if builder.rlnRelayConf.enabled.isSome():
warn "RLN Relay was provided alongside a network conf",
used = networkConf.rlnRelay, discarded = builder.rlnRelayConf.enabled
builder.rlnRelayConf.withEnabled(true)
if builder.rlnRelayConf.ethContractAddress.get("") != "":
warn "RLN Relay ETH Contract Address was provided alongside a network conf",
used = networkConf.rlnRelayEthContractAddress.string,
discarded = builder.rlnRelayConf.ethContractAddress.get().string
builder.rlnRelayConf.withEthContractAddress(networkConf.rlnRelayEthContractAddress)
if builder.rlnRelayConf.chainId.isSome():
warn "RLN Relay Chain Id was provided alongside a network conf",
used = networkConf.rlnRelayChainId, discarded = builder.rlnRelayConf.chainId
builder.rlnRelayConf.withChainId(networkConf.rlnRelayChainId)
if builder.rlnRelayConf.dynamic.isSome():
warn "RLN Relay Dynamic was provided alongside a network conf",
used = networkConf.rlnRelayDynamic, discarded = builder.rlnRelayConf.dynamic
builder.rlnRelayConf.withDynamic(networkConf.rlnRelayDynamic)
if builder.rlnRelayConf.epochSizeSec.isSome():
warn "RLN Epoch Size in Seconds was provided alongside a network conf",
used = networkConf.rlnEpochSizeSec,
discarded = builder.rlnRelayConf.epochSizeSec
builder.rlnRelayConf.withEpochSizeSec(networkConf.rlnEpochSizeSec)
if builder.rlnRelayConf.userMessageLimit.isSome():
warn "RLN Relay User Message Limit was provided alongside a network conf",
used = networkConf.rlnRelayUserMessageLimit,
discarded = builder.rlnRelayConf.userMessageLimit
if builder.rlnRelayConf.userMessageLimit.get(0) == 0:
## only override with the "preset" value if there was not explicit set value
builder.rlnRelayConf.withUserMessageLimit(networkConf.rlnRelayUserMessageLimit)
if builder.relay.get(DefaultRelay) and networkPresetConf.rlnRelay:
checkSetPresetValueToField(
builder.rlnRelayConf.enabled,
networkPresetConf.rlnRelay, # true
"RLN Relay was provided alongside a network conf",
)
checkSetPresetValueToField(
builder.rlnRelayConf.ethContractAddress,
networkPresetConf.rlnRelayEthContractAddress,
"RLN Relay ETH Contract Address was provided alongside a network conf",
)
checkSetPresetValueToField(
builder.rlnRelayConf.chainId, networkPresetConf.rlnRelayChainId,
"RLN Relay Chain Id was provided alongside a network conf",
)
checkSetPresetValueToField(
builder.rlnRelayConf.dynamic, networkPresetConf.rlnRelayDynamic,
"RLN Relay Dynamic was provided alongside a network conf",
)
checkSetPresetValueToField(
builder.rlnRelayConf.epochSizeSec, networkPresetConf.rlnEpochSizeSec,
"RLN Epoch Size in Seconds was provided alongside a network conf",
)
checkSetPresetValueToField(
builder.rlnRelayConf.userMessageLimit, networkPresetConf.rlnRelayUserMessageLimit,
"RLN Relay User Message Limit was provided alongside a network conf",
)
# End Apply relay parameters
case builder.maxMessageSize.kind
of mmskNone:
discard
builder.withMaxMessageSize(parseCorrectMsgSize(networkPresetConf.maxMessageSize))
of mmskStr, mmskInt:
warn "Max Message Size was provided alongside a network conf",
used = networkConf.maxMessageSize, discarded = $builder.maxMessageSize
builder.withMaxMessageSize(parseCorrectMsgSize(networkConf.maxMessageSize))
used = $builder.maxMessageSize, discarded = networkPresetConf.maxMessageSize
if builder.shardingConf.isSome():
warn "Sharding Conf was provided alongside a network conf",
used = networkConf.shardingConf.kind, discarded = builder.shardingConf
case networkConf.shardingConf.kind
of StaticSharding:
builder.shardingConf = some(StaticSharding)
checkSetPresetValueToField(
builder.shardingConf, networkPresetConf.shardingConf.kind,
"Sharding Conf was provided alongside a network conf",
)
case networkPresetConf.shardingConf.kind
of AutoSharding:
builder.shardingConf = some(AutoSharding)
if builder.numShardsInCluster.isSome():
warn "Num Shards In Cluster overrides network conf preset",
used = builder.numShardsInCluster.get(),
ignored = networkConf.shardingConf.numShardsInCluster
else:
builder.numShardsInCluster = some(networkConf.shardingConf.numShardsInCluster)
checkSetPresetValueToField(
builder.numShardsInCluster, networkPresetConf.shardingConf.numShardsInCluster,
"Num Shards In Cluster overrides network conf preset",
)
of StaticSharding:
discard
if networkConf.discv5Discovery:
if builder.discv5Conf.enabled.isNone:
builder.discv5Conf.withEnabled(networkConf.discv5Discovery)
checkSetPresetValueToField(
builder.discv5Conf.enabled, networkPresetConf.discv5Discovery,
"Discv5 Discovery was provided alongside a network conf",
)
checkAddPresetValueToField(
builder.discv5Conf.bootstrapNodes, networkPresetConf.discv5BootstrapNodes
)
if builder.discv5Conf.bootstrapNodes.len == 0 and
networkConf.discv5BootstrapNodes.len > 0:
warn "Discv5 Bootstrap nodes were provided alongside a network conf",
used = networkConf.discv5BootstrapNodes,
discarded = builder.discv5Conf.bootstrapNodes
builder.discv5Conf.withBootstrapNodes(networkConf.discv5BootstrapNodes)
checkSetPresetValueToField(
builder.kademliaDiscoveryConf.enabled, networkPresetConf.enableKadDiscovery,
"Kademlia Discovery was provided alongside a network conf",
)
checkAddPresetValueToField(
builder.kademliaDiscoveryConf.bootstrapNodes, networkPresetConf.kadBootstrapNodes
)
if networkConf.enableKadDiscovery:
if not builder.kademliaDiscoveryConf.enabled:
builder.kademliaDiscoveryConf.withEnabled(networkConf.enableKadDiscovery)
if builder.kademliaDiscoveryConf.bootstrapNodes.len == 0 and
networkConf.kadBootstrapNodes.len > 0:
builder.kademliaDiscoveryConf.withBootstrapNodes(networkConf.kadBootstrapNodes)
if networkConf.mix:
if builder.mix.isNone:
builder.mix = some(networkConf.mix)
if builder.p2pReliability.isNone:
builder.withP2pReliability(networkConf.p2pReliability)
checkSetPresetValueToField(
builder.mix, networkPresetConf.mix, "Mix was provided alongside a network conf"
)
checkSetPresetValueToField(
builder.p2pReliability, networkPresetConf.p2pReliability,
"P2P Reliability was provided alongside a network conf",
)
# Process entry nodes from network config - classify and distribute
if networkConf.entryNodes.len > 0:
let processed = processEntryNodes(networkConf.entryNodes)
if networkPresetConf.entryNodes.len > 0:
let processed = processEntryNodes(networkPresetConf.entryNodes)
if processed.isOk():
let (enrTreeUrls, bootstrapEnrs, staticNodesFromEntry) = processed.get()
@ -442,6 +484,47 @@ proc applyNetworkConf(builder: var WakuConfBuilder) =
else:
warn "Failed to process entry nodes from network conf", error = processed.error()
proc rejectOverride[T](
field: Option[T], presetValue: T, msg: string
): Result[void, string] =
## Errors with `msg` if `field` is set to anything other than the preset's value.
if field.isSome() and field.get() != presetValue:
return err(msg)
ok()
proc enforceSecurityConstraints(builder: WakuConfBuilder): Result[void, string] =
## Errors if the resolved config violates a security constraint.
if builder.networkPresetConf.isSome():
let preset = builder.networkPresetConf.get()
let relayEnabled = builder.relay.get(DefaultRelay)
let rlnRelayConf = builder.rlnRelayConf
let rlnRelayEnabled = rlnRelayConf.enabled.get(DefaultRlnRelayEnabled)
if relayEnabled and preset.rlnRelay:
if not rlnRelayEnabled:
return
err("network preset mandates RLN relay: cannot relay with rln-relay disabled")
?rejectOverride(
rlnRelayConf.ethContractAddress, preset.rlnRelayEthContractAddress,
"network preset mandates its RLN contract: cannot relay with a different rln-relay-eth-contract-address",
)
?rejectOverride(
rlnRelayConf.chainId, preset.rlnRelayChainId,
"network preset mandates its RLN chain id: cannot relay with a different rln-relay-chain-id",
)
?rejectOverride(
rlnRelayConf.dynamic, preset.rlnRelayDynamic,
"network preset mandates its RLN membership mode: cannot relay with a different rln-relay-dynamic",
)
?rejectOverride(
rlnRelayConf.epochSizeSec, preset.rlnEpochSizeSec,
"network preset mandates its RLN epoch size: cannot relay with a different rln-relay-epoch-sec",
)
ok()
proc build*(
builder: var WakuConfBuilder, rng: ref HmacDrbgContext = crypto.newRng()
): Result[WakuConf, string] =
@ -450,51 +533,59 @@ proc build*(
## of libwaku. It aims to be agnostic so it does not apply a
## default when it is opinionated.
applyNetworkConf(builder)
applyNetworkPresetConf(builder)
# We should not ignore any user-supplied config parameter: the user is
# allowed to override any preset parameter with any explicit config
# parameter. However, we do gate config building with an error if any
# one of these preset overrides is considered a security concern.
# This eliminates ambiguous behavior such as warning of an override and
# then ignoring it: either fail-fast or accept the override.
?enforceSecurityConstraints(builder)
let relay =
if builder.relay.isSome():
builder.relay.get()
else:
warn "whether to mount relay is not specified, defaulting to not mounting"
false
DefaultRelay
let lightPush =
if builder.lightPush.isSome():
builder.lightPush.get()
else:
warn "whether to mount lightPush is not specified, defaulting to not mounting"
false
DefaultLightPush
let peerExchange =
if builder.peerExchange.isSome():
builder.peerExchange.get()
else:
warn "whether to mount peerExchange is not specified, defaulting to not mounting"
false
DefaultPeerExchange
let storeSync =
if builder.storeSync.isSome():
builder.storeSync.get()
else:
warn "whether to mount storeSync is not specified, defaulting to not mounting"
false
DefaultStoreSyncMount
let rendezvous =
if builder.rendezvous.isSome():
builder.rendezvous.get()
else:
warn "whether to mount rendezvous is not specified, defaulting to not mounting"
false
DefaultRendezvous
let mix =
if builder.mix.isSome():
builder.mix.get()
else:
warn "whether to mount mix is not specified, defaulting to not mounting"
false
DefaultMix
let relayPeerExchange = builder.relayPeerExchange.get(false)
let relayPeerExchange = builder.relayPeerExchange.get(DefaultRelayPeerExchange)
let nodeKey = ?nodeKey(builder, rng)
@ -503,7 +594,7 @@ proc build*(
# TODO: ClusterId should never be defaulted, instead, presets
# should be defined and used
warn("Cluster Id was not specified, defaulting to 0")
0.uint16
DefaultClusterId
else:
builder.clusterId.get().uint16
@ -522,8 +613,9 @@ proc build*(
of mmskStr:
?parseMsgSize(builder.maxMessageSize.str)
else:
warn "Max Message Size not specified, defaulting to 150KiB"
parseCorrectMsgSize("150KiB")
warn "Max Message Size not specified, defaulting to DefaultMaxWakuMessageSize",
default = DefaultMaxWakuMessageSizeStr
DefaultMaxWakuMessageSize
let contentTopics = builder.contentTopics.get(@[])
@ -568,21 +660,21 @@ proc build*(
builder.logLevel.get()
else:
warn "Log Level not specified, defaulting to INFO"
logging.LogLevel.INFO
DefaultLogLevel
let logFormat =
if builder.logFormat.isSome():
builder.logFormat.get()
else:
warn "Log Format not specified, defaulting to TEXT"
logging.LogFormat.TEXT
DefaultLogFormat
let natStrategy =
if builder.natStrategy.isSome():
builder.natStrategy.get()
else:
warn "Nat Strategy is not specified, defaulting to none"
"none"
DefaultNatStrategy
let p2pTcpPort = builder.p2pTcpPort.get(DefaultP2pTcpPort)
@ -591,14 +683,14 @@ proc build*(
builder.p2pListenAddress.get()
else:
warn "P2P listening address not specified, listening on 0.0.0.0"
(static parseIpAddress("0.0.0.0"))
DefaultP2pListenAddress
let portsShift =
if builder.portsShift.isSome():
builder.portsShift.get()
else:
warn "Ports Shift is not specified, defaulting to 0"
0.uint16
DefaultPortsShift
let dns4DomainName =
if builder.dns4DomainName.isSome():
@ -621,21 +713,21 @@ proc build*(
builder.extMultiAddrsOnly.get()
else:
warn "Whether to only announce external multiaddresses is not specified, defaulting to false"
false
DefaultExtMultiAddrsOnly
let dnsAddrsNameServers =
if builder.dnsAddrsNameServers.len != 0:
builder.dnsAddrsNameServers
else:
warn "DNS name servers IPs not provided, defaulting to Cloudflare's."
@[static parseIpAddress("1.1.1.1"), static parseIpAddress("1.0.0.1")]
DefaultDnsAddrsNameServers
let peerPersistence =
if builder.peerPersistence.isSome():
builder.peerPersistence.get()
else:
warn "Peer persistence not specified, defaulting to false"
false
DefaultPeerPersistence
let maxConnections =
if builder.maxConnections.isSome():
@ -649,15 +741,13 @@ proc build*(
warn "max-connections less than DefaultMaxConnections; we suggest using DefaultMaxConnections or more for better connectivity",
provided = maxConnections, recommended = DefaultMaxConnections
# TODO: Do the git version thing here
let agentString = builder.agentString.get("logos-delivery")
let agentString = builder.agentString.get(DefaultAgentString)
# TODO: use `DefaultColocationLimit`. the user of this value should
# probably be defining a config object
let colocationLimit = builder.colocationLimit.get(5)
let colocationLimit = builder.colocationLimit.get(DefaultColocationLimit)
# TODO: is there a strategy for experimental features? delete vs promote
let relayShardedPeerManagement = builder.relayShardedPeerManagement.get(false)
let relayShardedPeerManagement =
builder.relayShardedPeerManagement.get(DefaultRelayShardedPeerManagement)
let wakuFlags = CapabilitiesBitfield.init(
lightpush = lightPush and relay,
@ -718,12 +808,12 @@ proc build*(
agentString: agentString,
colocationLimit: colocationLimit,
maxRelayPeers: builder.maxRelayPeers,
relayServiceRatio: builder.relayServiceRatio.get("50:50"),
relayServiceRatio: builder.relayServiceRatio.get(DefaultRelayServiceRatio),
rateLimit: rateLimit,
circuitRelayClient: builder.circuitRelayClient.get(false),
circuitRelayClient: builder.circuitRelayClient.get(DefaultCircuitRelayClient),
staticNodes: builder.staticNodes,
relayShardedPeerManagement: relayShardedPeerManagement,
p2pReliability: builder.p2pReliability.get(false),
p2pReliability: builder.p2pReliability.get(DefaultP2pReliability),
wakuFlags: wakuFlags,
localStoragePath: builder.localStoragePath.get(DefaultStoragePath),
)

View File

@ -4,7 +4,10 @@ import logos_delivery/waku/factory/waku_conf
logScope:
topics = "waku conf builder websocket"
const DefaultWebSocketPort*: Port = Port(8000)
const
DefaultWebSocketEnabled: bool = false
DefaultWebSocketSecureEnabled: bool = false
DefaultWebSocketPort: Port = Port(8000)
##############################
## WebSocket Config Builder ##
@ -40,10 +43,10 @@ proc withCertPath*(b: var WebSocketConfBuilder, certPath: string) =
b.certPath = some(certPath)
proc build*(b: WebSocketConfBuilder): Result[Option[WebSocketConf], string] =
if not b.enabled.get(false):
if not b.enabled.get(DefaultWebSocketEnabled):
return ok(none(WebSocketConf))
if not b.secureEnabled.get(false):
if not b.secureEnabled.get(DefaultWebSocketSecureEnabled):
return ok(
some(
WebSocketConf(

View File

@ -1,6 +1,7 @@
{.push raises: [].}
import chronicles, results, stint
import logos_delivery/waku/waku_core/message/default_values
logScope:
topics = "waku networks conf"
@ -17,7 +18,8 @@ type
of StaticSharding:
discard
type NetworkConf* = object
type NetworkPresetConf* = object
## A network "preset" (--preset=twn, --preset=logos.dev).
maxMessageSize*: string # TODO: static convert to a uint64
clusterId*: uint16
rlnRelay*: bool
@ -38,10 +40,10 @@ type NetworkConf* = object
# cluster-id=1 (aka The Waku Network)
# Cluster configuration corresponding to The Waku Network. Note that it
# overrides existing cli configuration
proc TheWakuNetworkConf*(T: type NetworkConf): NetworkConf =
proc TheWakuNetworkConf*(T: type NetworkPresetConf): NetworkPresetConf =
const RelayChainId = 59141'u256
return NetworkConf(
maxMessageSize: "150KiB",
return NetworkPresetConf(
maxMessageSize: DefaultMaxWakuMessageSizeStr,
clusterId: 1,
rlnRelay: true,
rlnRelayEthContractAddress: "0xB9cd878C90E49F797B4431fBF4fb333108CB90e6",
@ -65,10 +67,10 @@ proc TheWakuNetworkConf*(T: type NetworkConf): NetworkConf =
# cluster-id=2 (Logos Dev Network)
# Cluster configuration for the Logos Dev Network.
proc LogosDevConf*(T: type NetworkConf): NetworkConf =
proc LogosDevConf*(T: type NetworkPresetConf): NetworkPresetConf =
const ZeroChainId = 0'u256
return NetworkConf(
maxMessageSize: "150KiB",
return NetworkPresetConf(
maxMessageSize: DefaultMaxWakuMessageSizeStr,
clusterId: 2,
rlnRelay: false,
rlnRelayEthContractAddress: "",
@ -94,9 +96,9 @@ proc LogosDevConf*(T: type NetworkConf): NetworkConf =
# cluster-id=2 (Logos Test Network)
# Cluster configuration for the Logos Test Network.
proc LogosTestConf*(T: type NetworkConf): NetworkConf =
proc LogosTestConf*(T: type NetworkPresetConf): NetworkPresetConf =
const ZeroChainId = 0'u256
return NetworkConf(
return NetworkPresetConf(
maxMessageSize: "150KiB",
clusterId: 2,
rlnRelay: false,

View File

@ -98,7 +98,7 @@ suite "LM API health checking":
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.clusterId = some(3'u16)
conf.numShardsInNetwork = 1
conf.rest = false
@ -277,7 +277,7 @@ suite "LM API health checking":
edgeConf.listenAddress = parseIpAddress("0.0.0.0")
edgeConf.tcpPort = Port(0)
edgeConf.discv5UdpPort = Port(0)
edgeConf.clusterId = 3'u16
edgeConf.clusterId = some(3'u16)
edgeConf.maxMessageSize = "150 KiB"
edgeConf.rest = false

View File

@ -67,9 +67,9 @@ proc createApiNodeConf(numShards: uint16 = 1): WakuNodeConf =
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.clusterId = some(3'u16)
conf.numShardsInNetwork = numShards
conf.reliabilityEnabled = true
conf.reliabilityEnabled = some(true)
conf.rest = false
result = conf

View File

@ -126,9 +126,9 @@ proc createApiNodeConf(mode: cli_args.WakuMode = cli_args.WakuMode.Core): WakuNo
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.clusterId = some(3'u16)
conf.numShardsInNetwork = 1
conf.reliabilityEnabled = true
conf.reliabilityEnabled = some(true)
conf.rest = false
result = conf

View File

@ -77,9 +77,9 @@ proc createApiNodeConf(
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.clusterId = some(3'u16)
conf.numShardsInNetwork = numShards
conf.reliabilityEnabled = true
conf.reliabilityEnabled = some(true)
conf.rest = false
result = conf

View File

@ -1,43 +1,23 @@
{.used.}
import std/[options, json, strutils], results, stint, testutils/unittests
import std/[options, strutils], results, stint, testutils/unittests
import json_serialization, confutils, confutils/std/net
import
tools/confutils/cli_args,
tools/confutils/conf_from_json,
logos_delivery/waku/api/api_conf,
logos_delivery/waku/factory/waku_conf,
logos_delivery/waku/factory/networks_config,
logos_delivery/waku/factory/conf_builder/conf_builder,
logos_delivery/waku/common/logging
# Helper: parse JSON into WakuNodeConf using fieldPairs (same as liblogosdelivery)
proc parseWakuNodeConfFromJson(jsonStr: string): Result[WakuNodeConf, string] =
var conf = defaultWakuNodeConf().valueOr:
return err(error)
var jsonNode: JsonNode
try:
jsonNode = parseJson(jsonStr)
except Exception:
return err("JSON parse error: " & getCurrentExceptionMsg())
for confField, confValue in fieldPairs(conf):
if jsonNode.contains(confField):
let formattedString = ($jsonNode[confField]).strip(chars = {'\"'})
try:
confValue = parseCmdArg(typeof(confValue), formattedString)
except Exception:
return err(
"Field '" & confField & "' parse error: " & getCurrentExceptionMsg() &
". Value: " & formattedString
)
return ok(conf)
suite "WakuNodeConf - mode-driven toWakuConf":
test "Core mode enables service protocols":
## Given
var conf = defaultWakuNodeConf().valueOr:
raiseAssert error
conf.mode = Core
conf.clusterId = 1
conf.clusterId = some(1'u16)
## When
let wakuConfRes = conf.toWakuConf()
@ -58,7 +38,7 @@ suite "WakuNodeConf - mode-driven toWakuConf":
var conf = defaultWakuNodeConf().valueOr:
raiseAssert error
conf.mode = Edge
conf.clusterId = 1
conf.clusterId = some(1'u16)
## When
let wakuConfRes = conf.toWakuConf()
@ -81,7 +61,7 @@ suite "WakuNodeConf - mode-driven toWakuConf":
conf.mode = cli_args.WakuMode.noMode
conf.relay = true
conf.lightpush = false
conf.clusterId = 5
conf.clusterId = some(5'u16)
## When
let wakuConfRes = conf.toWakuConf()
@ -115,30 +95,30 @@ suite "WakuNodeConf - mode-driven toWakuConf":
suite "WakuNodeConf - JSON parsing with fieldPairs":
test "Empty JSON produces valid default conf":
## Given / When
let confRes = parseWakuNodeConfFromJson("{}")
let confRes = parseNodeConfFromJson("{}")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.mode == cli_args.WakuMode.noMode
conf.clusterId == 0
conf.clusterId.isNone()
conf.logLevel == logging.LogLevel.INFO
test "JSON with mode and clusterId":
## Given / When
let confRes = parseWakuNodeConfFromJson("""{"mode": "Core", "clusterId": 42}""")
let confRes = parseNodeConfFromJson("""{"mode": "Core", "clusterId": 42}""")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.mode == Core
conf.clusterId == 42
conf.clusterId == some(42'u16)
test "JSON with Edge mode":
## Given / When
let confRes = parseWakuNodeConfFromJson("""{"mode": "Edge"}""")
let confRes = parseNodeConfFromJson("""{"mode": "Edge"}""")
## Then
require confRes.isOk()
@ -148,7 +128,7 @@ suite "WakuNodeConf - JSON parsing with fieldPairs":
test "JSON with logLevel":
## Given / When
let confRes = parseWakuNodeConfFromJson("""{"logLevel": "DEBUG"}""")
let confRes = parseNodeConfFromJson("""{"logLevel": "DEBUG"}""")
## Then
require confRes.isOk()
@ -159,29 +139,25 @@ suite "WakuNodeConf - JSON parsing with fieldPairs":
test "JSON with sharding config":
## Given / When
let confRes =
parseWakuNodeConfFromJson("""{"clusterId": 99, "numShardsInNetwork": 16}""")
parseNodeConfFromJson("""{"clusterId": 99, "numShardsInNetwork": 16}""")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.clusterId == 99
conf.clusterId == some(99'u16)
conf.numShardsInNetwork == 16
test "JSON with unknown fields is silently ignored":
test "JSON with unknown fields is rejected":
## Given / When
let confRes =
parseWakuNodeConfFromJson("""{"unknownField": true, "clusterId": 5}""")
let confRes = parseNodeConfFromJson("""{"unknownField": true, "clusterId": 5}""")
## Then - unknown fields are just ignored (not in fieldPairs)
require confRes.isOk()
let conf = confRes.get()
check:
conf.clusterId == 5
## Then - the parser rejects unrecognized config keys
check confRes.isErr()
test "Invalid JSON syntax returns error":
## Given / When
let confRes = parseWakuNodeConfFromJson("{ not valid json }")
let confRes = parseNodeConfFromJson("{ not valid json }")
## Then
check confRes.isErr()
@ -250,7 +226,7 @@ suite "WakuNodeConf - preset integration":
suite "WakuNodeConf JSON -> WakuConf integration":
test "Core mode JSON config produces valid WakuConf":
## Given
let confRes = parseWakuNodeConfFromJson(
let confRes = parseNodeConfFromJson(
"""{"mode": "Core", "clusterId": 55, "numShardsInNetwork": 6}"""
)
require confRes.isOk()
@ -272,7 +248,7 @@ suite "WakuNodeConf JSON -> WakuConf integration":
test "Edge mode JSON config produces valid WakuConf":
## Given
let confRes = parseWakuNodeConfFromJson("""{"mode": "Edge", "clusterId": 1}""")
let confRes = parseNodeConfFromJson("""{"mode": "Edge", "clusterId": 1}""")
require confRes.isOk()
let conf = confRes.get()
@ -290,8 +266,7 @@ suite "WakuNodeConf JSON -> WakuConf integration":
test "JSON with preset produces valid WakuConf":
## Given
let confRes =
parseWakuNodeConfFromJson("""{"mode": "Core", "preset": "logosdev"}""")
let confRes = parseNodeConfFromJson("""{"mode": "Core", "preset": "logosdev"}""")
require confRes.isOk()
let conf = confRes.get()
@ -308,7 +283,7 @@ suite "WakuNodeConf JSON -> WakuConf integration":
test "JSON with static nodes":
## Given
let confRes = parseWakuNodeConfFromJson(
let confRes = parseNodeConfFromJson(
"""{"mode": "Core", "clusterId": 42, "staticnodes": ["/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc"]}"""
)
require confRes.isOk()
@ -327,7 +302,7 @@ suite "WakuNodeConf JSON -> WakuConf integration":
test "JSON with max message size":
## Given
let confRes =
parseWakuNodeConfFromJson("""{"clusterId": 42, "maxMessageSize": "100KiB"}""")
parseNodeConfFromJson("""{"clusterId": 42, "maxMessageSize": "100KiB"}""")
require confRes.isOk()
let conf = confRes.get()

View File

@ -1,6 +1,6 @@
{.used.}
import std/[net]
import std/[net, options]
import chronos, testutils/unittests, stew/byteutils
import brokers/broker_context
@ -24,9 +24,9 @@ proc createApiNodeConf(): WakuNodeConf =
conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0)
conf.clusterId = 3'u16
conf.clusterId = some(3'u16)
conf.numShardsInNetwork = 1
conf.reliabilityEnabled = true
conf.reliabilityEnabled = some(true)
conf.rest = false
return conf

View File

@ -16,7 +16,7 @@ import
suite "Waku Conf - build with cluster conf":
test "Cluster Conf is passed and relay is enabled":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.discv5Conf.withUdpPort(9000)
builder.withRelayServiceRatio("50:50")
@ -26,7 +26,7 @@ suite "Waku Conf - build with cluster conf":
## Given
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
builder.withNetworkConf(networkConf)
builder.withNetworkPresetConf(networkPresetConf)
builder.withRelay(true)
builder.rlnRelayConf.withUserMessageLimit(userMessageLimit)
@ -38,29 +38,29 @@ suite "Waku Conf - build with cluster conf":
## Then
let resValidate = conf.validate()
assert resValidate.isOk(), $resValidate.error
check conf.clusterId == networkConf.clusterId
check conf.shardingConf.kind == networkConf.shardingConf.kind
check conf.clusterId == networkPresetConf.clusterId
check conf.shardingConf.kind == networkPresetConf.shardingConf.kind
check conf.shardingConf.numShardsInCluster ==
networkConf.shardingConf.numShardsInCluster
networkPresetConf.shardingConf.numShardsInCluster
check conf.subscribeShards == expectedShards
check conf.maxMessageSizeBytes ==
uint64(parseCorrectMsgSize(networkConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkConf.discv5BootstrapNodes
uint64(parseCorrectMsgSize(networkPresetConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkPresetConf.discv5BootstrapNodes
if networkConf.rlnRelay:
if networkPresetConf.rlnRelay:
assert conf.rlnRelayConf.isSome(), "RLN Relay conf is disabled"
let rlnRelayConf = conf.rlnRelayConf.get()
check rlnRelayConf.ethContractAddress.string ==
networkConf.rlnRelayEthContractAddress
check rlnRelayConf.dynamic == networkConf.rlnRelayDynamic
check rlnRelayConf.chainId == networkConf.rlnRelayChainId
check rlnRelayConf.epochSizeSec == networkConf.rlnEpochSizeSec
networkPresetConf.rlnRelayEthContractAddress
check rlnRelayConf.dynamic == networkPresetConf.rlnRelayDynamic
check rlnRelayConf.chainId == networkPresetConf.rlnRelayChainId
check rlnRelayConf.epochSizeSec == networkPresetConf.rlnEpochSizeSec
check rlnRelayConf.userMessageLimit == userMessageLimit.uint
test "Cluster Conf is passed, but relay is disabled":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.withRelayServiceRatio("50:50")
builder.discv5Conf.withUdpPort(9000)
@ -69,7 +69,7 @@ suite "Waku Conf - build with cluster conf":
## Given
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
builder.withNetworkConf(networkConf)
builder.withNetworkPresetConf(networkPresetConf)
builder.withRelay(false)
## When
@ -80,20 +80,20 @@ suite "Waku Conf - build with cluster conf":
## Then
let resValidate = conf.validate()
assert resValidate.isOk(), $resValidate.error
check conf.clusterId == networkConf.clusterId
check conf.shardingConf.kind == networkConf.shardingConf.kind
check conf.clusterId == networkPresetConf.clusterId
check conf.shardingConf.kind == networkPresetConf.shardingConf.kind
check conf.shardingConf.numShardsInCluster ==
networkConf.shardingConf.numShardsInCluster
networkPresetConf.shardingConf.numShardsInCluster
check conf.subscribeShards == expectedShards
check conf.maxMessageSizeBytes ==
uint64(parseCorrectMsgSize(networkConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkConf.discv5BootstrapNodes
uint64(parseCorrectMsgSize(networkPresetConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkPresetConf.discv5BootstrapNodes
assert conf.rlnRelayConf.isNone
test "Cluster Conf is passed, but rln relay is disabled":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
let # Mount all shards in network
@ -101,7 +101,7 @@ suite "Waku Conf - build with cluster conf":
## Given
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
builder.withNetworkConf(networkConf)
builder.withNetworkPresetConf(networkPresetConf)
builder.rlnRelayConf.withEnabled(false)
## When
@ -112,25 +112,25 @@ suite "Waku Conf - build with cluster conf":
## Then
let resValidate = conf.validate()
assert resValidate.isOk(), $resValidate.error
check conf.clusterId == networkConf.clusterId
check conf.shardingConf.kind == networkConf.shardingConf.kind
check conf.clusterId == networkPresetConf.clusterId
check conf.shardingConf.kind == networkPresetConf.shardingConf.kind
check conf.shardingConf.numShardsInCluster ==
networkConf.shardingConf.numShardsInCluster
networkPresetConf.shardingConf.numShardsInCluster
check conf.subscribeShards == expectedShards
check conf.maxMessageSizeBytes ==
uint64(parseCorrectMsgSize(networkConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkConf.discv5BootstrapNodes
uint64(parseCorrectMsgSize(networkPresetConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkPresetConf.discv5BootstrapNodes
assert conf.rlnRelayConf.isNone
test "Cluster Conf is passed and valid shards are specified":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
let shards = @[2.uint16, 3.uint16]
## Given
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
builder.withNetworkConf(networkConf)
builder.withNetworkPresetConf(networkPresetConf)
builder.withSubscribeShards(shards)
## When
@ -141,24 +141,24 @@ suite "Waku Conf - build with cluster conf":
## Then
let resValidate = conf.validate()
assert resValidate.isOk(), $resValidate.error
check conf.clusterId == networkConf.clusterId
check conf.shardingConf.kind == networkConf.shardingConf.kind
check conf.clusterId == networkPresetConf.clusterId
check conf.shardingConf.kind == networkPresetConf.shardingConf.kind
check conf.shardingConf.numShardsInCluster ==
networkConf.shardingConf.numShardsInCluster
networkPresetConf.shardingConf.numShardsInCluster
check conf.subscribeShards == shards
check conf.maxMessageSizeBytes ==
uint64(parseCorrectMsgSize(networkConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkConf.discv5BootstrapNodes
uint64(parseCorrectMsgSize(networkPresetConf.maxMessageSize))
check conf.discv5Conf.get().bootstrapNodes == networkPresetConf.discv5BootstrapNodes
test "Cluster Conf is passed and invalid shards are specified":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
let shards = @[2.uint16, 10.uint16]
## Given
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
builder.withNetworkConf(networkConf)
builder.withNetworkPresetConf(networkPresetConf)
builder.withSubscribeShards(shards)
## When
@ -167,63 +167,112 @@ suite "Waku Conf - build with cluster conf":
## Then
assert resConf.isErr(), "Invalid shard was accepted"
test "Cluster Conf is passed and RLN contract is **not** overridden":
test "Cluster Conf mandating RLN fails conf build if user disables rln relay":
## Setup
let networkConf = NetworkConf.TheWakuNetworkConf()
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
# Mount all shards in network
let expectedShards = toSeq[0.uint16 .. 7.uint16]
let contractAddress = "0x0123456789ABCDEF"
let userMessageLimit = rand(1 .. 1000).uint64
## Given
builder.rlnRelayConf.withEthContractAddress(contractAddress)
builder.withNetworkConf(networkConf)
builder.withNetworkPresetConf(networkPresetConf)
builder.withRelay(true)
builder.rlnRelayConf.withUserMessageLimit(userMessageLimit)
builder.rlnRelayConf.withEnabled(false)
## When
let resConf = builder.build()
assert resConf.isOk(), $resConf.error
let conf = resConf.get()
## Then
let resValidate = conf.validate()
assert resValidate.isOk(), $resValidate.error
check conf.clusterId == networkConf.clusterId
check conf.shardingConf.kind == networkConf.shardingConf.kind
check conf.shardingConf.numShardsInCluster ==
networkConf.shardingConf.numShardsInCluster
check conf.subscribeShards == expectedShards
check conf.maxMessageSizeBytes ==
uint64(parseCorrectMsgSize(networkConf.maxMessageSize))
check conf.discv5Conf.isSome == networkConf.discv5Discovery
check conf.discv5Conf.get().bootstrapNodes == networkConf.discv5BootstrapNodes
assert networkPresetConf.rlnRelay, "precondition: preset must mandate RLN"
assert resConf.isErr(), "relay with rln relay disabled was accepted"
if networkConf.rlnRelay:
assert conf.rlnRelayConf.isSome
test "Cluster Conf mandating RLN fails conf build if user overrides the rln contract":
## Setup
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
# otherwise-valid RLN, so only the security gate can fail the build
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
let rlnRelayConf = conf.rlnRelayConf.get()
check rlnRelayConf.ethContractAddress.string ==
networkConf.rlnRelayEthContractAddress
check rlnRelayConf.dynamic == networkConf.rlnRelayDynamic
check rlnRelayConf.chainId == networkConf.rlnRelayChainId
check rlnRelayConf.epochSizeSec == networkConf.rlnEpochSizeSec
check rlnRelayConf.userMessageLimit == userMessageLimit.uint
## Given
builder.withNetworkPresetConf(networkPresetConf)
builder.withRelay(true)
builder.rlnRelayConf.withEthContractAddress(
networkPresetConf.rlnRelayEthContractAddress & "0"
)
## When
let resConf = builder.build()
## Then
assert networkPresetConf.rlnRelay, "precondition: preset must mandate RLN"
assert resConf.isErr(), "relay with an overridden rln contract was accepted"
test "Cluster Conf mandating RLN fails conf build if user overrides the rln chain id":
## Setup
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
# otherwise-valid RLN, so only the security gate can fail the build
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
## Given
builder.withNetworkPresetConf(networkPresetConf)
builder.withRelay(true)
builder.rlnRelayConf.withChainId(1'u) # chain id 1 differs from the preset's
## When
let resConf = builder.build()
## Then
assert networkPresetConf.rlnRelay, "precondition: preset must mandate RLN"
assert resConf.isErr(), "relay with an overridden rln chain id was accepted"
test "Cluster Conf mandating RLN fails conf build if user overrides rln dynamic mode":
## Setup
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
# otherwise-valid RLN, so only the security gate can fail the build
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
## Given
builder.withNetworkPresetConf(networkPresetConf)
builder.withRelay(true)
builder.rlnRelayConf.withDynamic(not networkPresetConf.rlnRelayDynamic)
## When
let resConf = builder.build()
## Then
assert networkPresetConf.rlnRelay, "precondition: preset must mandate RLN"
assert resConf.isErr(), "relay with an overridden rln dynamic mode was accepted"
test "Cluster Conf mandating RLN fails conf build if user overrides the rln epoch size":
## Setup
let networkPresetConf = NetworkPresetConf.TheWakuNetworkConf()
var builder = WakuConfBuilder.init()
# otherwise-valid RLN, so only the security gate can fail the build
builder.rlnRelayConf.withEthClientUrls(@["https://my_eth_rpc_url/"])
## Given
builder.withNetworkPresetConf(networkPresetConf)
builder.withRelay(true)
builder.rlnRelayConf.withEpochSizeSec(networkPresetConf.rlnEpochSizeSec + 1'u64)
## When
let resConf = builder.build()
## Then
assert networkPresetConf.rlnRelay, "precondition: preset must mandate RLN"
assert resConf.isErr(), "relay with an overridden rln epoch size was accepted"
test "num-shards-in-network > 0 overrides preset":
## Setup
let networkConf = NetworkConf.LogosDevConf()
let networkPresetConf = NetworkPresetConf.LogosDevConf()
var builder = WakuConfBuilder.init()
# Sanity check
check networkConf.shardingConf.kind == AutoSharding
check networkConf.shardingConf.numShardsInCluster > 1
check networkPresetConf.shardingConf.kind == AutoSharding
check networkPresetConf.shardingConf.numShardsInCluster > 1
## Given: preset says >1 shards but user explicitly sets 1
builder.withNetworkConf(networkConf)
builder.withNetworkPresetConf(networkPresetConf)
builder.withNumShardsInCluster(1)
builder.withShardingConf(AutoSharding)
@ -244,22 +293,18 @@ suite "Waku Conf - build with cluster conf":
## StaticSharding shouldn't make any sense (that is, no use case).
## Given: emulate --preset=logos.dev --num-shards-in-network=0
let networkConf = NetworkConf.LogosDevConf()
let networkPresetConf = NetworkPresetConf.LogosDevConf()
var builder = WakuConfBuilder.init()
builder.withNetworkConf(networkConf)
# Note: builder.withNumShardsInCluster() is not called when the
# value that comes from the CLI path is 0 (which means it was
# either set to 0 or was left unset).
builder.withShardingConf(StaticSharding)
builder.withNetworkPresetConf(networkPresetConf)
## When
let conf = builder.build().expect("build should succeed")
## Then: preset wins and StaticSharding user intent is lost
conf.validate().expect("conf should validate")
check conf.shardingConf.kind == networkConf.shardingConf.kind
check conf.shardingConf.kind == networkPresetConf.shardingConf.kind
check conf.shardingConf.numShardsInCluster ==
networkConf.shardingConf.numShardsInCluster
networkPresetConf.shardingConf.numShardsInCluster
suite "Waku Conf - node key":
test "Node key is generated":

View File

@ -1,24 +1,28 @@
{.used.}
import chronos, testutils/unittests, std/options
import std/[net, options]
import chronos, testutils/unittests
import logos_delivery
import tools/confutils/cli_args
import logos_delivery/waku/factory/networks_config
import logos_delivery/waku/factory/conf_builder/conf_builder
suite "Waku API - Create node":
asyncTest "Create node with minimal configuration":
## Given
var nodeConf = defaultWakuNodeConf().valueOr:
raiseAssert error
raiseAssert "defaultWakuNodeConf failed: " & error
nodeConf.mode = Core
nodeConf.clusterId = 3'u16
nodeConf.clusterId = some(3'u16)
nodeConf.rest = false
# This is the actual minimal config but as the node auto-start, it is not suitable for tests
## When
let node = (await createNode(nodeConf)).valueOr:
raiseAssert error
raiseAssert "createNode (minimal config) failed: " & error
## Then
check:
@ -29,9 +33,9 @@ suite "Waku API - Create node":
asyncTest "Create node with full configuration":
## Given
var nodeConf = defaultWakuNodeConf().valueOr:
raiseAssert error
raiseAssert "defaultWakuNodeConf failed: " & error
nodeConf.mode = Core
nodeConf.clusterId = 99'u16
nodeConf.clusterId = some(99'u16)
nodeConf.rest = false
nodeConf.numShardsInNetwork = 16
nodeConf.maxMessageSize = "1024 KiB"
@ -44,7 +48,7 @@ suite "Waku API - Create node":
## When
let node = (await createNode(nodeConf)).valueOr:
raiseAssert error
raiseAssert "createNode (full config) failed: " & error
## Then
check:
@ -61,9 +65,9 @@ suite "Waku API - Create node":
asyncTest "Create node with mixed entry nodes (enrtree, multiaddr)":
## Given
var nodeConf = defaultWakuNodeConf().valueOr:
raiseAssert error
raiseAssert "defaultWakuNodeConf failed: " & error
nodeConf.mode = Core
nodeConf.clusterId = 42'u16
nodeConf.clusterId = some(42'u16)
nodeConf.rest = false
nodeConf.entryNodes = @[
"enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im",
@ -72,7 +76,7 @@ suite "Waku API - Create node":
## When
let node = (await createNode(nodeConf)).valueOr:
raiseAssert error
raiseAssert "createNode (mixed entry nodes) failed: " & error
## Then
check:

View File

@ -57,7 +57,7 @@ suite "Waku external config - default values":
suite "Waku external config - apply preset":
test "Preset is TWN":
## Setup
let expectedConf = NetworkConf.TheWakuNetworkConf()
let expectedConf = NetworkPresetConf.TheWakuNetworkConf()
## Given
let preConfig = WakuNodeConf(
@ -94,7 +94,7 @@ suite "Waku external config - apply preset":
test "Subscribes to all valid shards in twn":
## Setup
let expectedConf = NetworkConf.TheWakuNetworkConf()
let expectedConf = NetworkPresetConf.TheWakuNetworkConf()
## Given
let shards: seq[uint16] = @[0, 1, 2, 3, 4, 5, 6, 7]
@ -110,7 +110,7 @@ suite "Waku external config - apply preset":
test "Subscribes to some valid shards in twn":
## Setup
let expectedConf = NetworkConf.TheWakuNetworkConf()
let expectedConf = NetworkPresetConf.TheWakuNetworkConf()
## Given
let shards: seq[uint16] = @[0, 4, 7]

View File

@ -37,7 +37,7 @@ import ./envvar as confEnvvarDefs, ./envvar_net as confEnvvarNet
export
confTomlDefs, confTomlNet, confEnvvarDefs, confEnvvarNet, ProtectedShard,
DefaultMaxWakuMessageSizeStr
DefaultMaxWakuMessageSizeStr, DefaultAgentString
logScope:
topics = "waku cli args"
@ -45,6 +45,13 @@ logScope:
# Git version in git describe format (defined at compile time)
const git_version* {.strdefine.} = "n/a"
# CLI defaults that differ from confbuilder defaults
const
DefaultCLIRelay* = true
DefaultCLIPeerExchange* = true
DefaultCLIRendezvous* = true
DefaultCLINat* = "any"
type ConfResult*[T] = Result[T, string]
type EthRpcUrl* = distinct string
@ -117,20 +124,23 @@ type WakuNodeConf* = object
name: "rln-relay-eth-private-key"
.}: string
# TODO: Remove "Default is" when it's already visible on the CLI
# Option-typed; desc states the default since the CLI can't auto-show it for none().
rlnRelayUserMessageLimit* {.
desc:
"Set a user message limit for the rln membership registration. Must be a positive integer. Default is 1.",
defaultValue: 1,
"Set a user message limit for the rln membership registration. Must be a positive integer. Default is " &
$DefaultRlnRelayUserMessageLimit & ".",
defaultValue: none(uint64),
name: "rln-relay-user-message-limit"
.}: uint64
.}: Option[uint64]
# Option-typed; desc states the default since the CLI can't auto-show it for none().
rlnEpochSizeSec* {.
desc:
"Epoch size in seconds used to rate limit RLN memberships. Default is 1 second.",
defaultValue: 1,
"Epoch size in seconds used to rate limit RLN memberships. Default is " &
$DefaultRlnRelayEpochSizeSec & " second.",
defaultValue: none(uint64),
name: "rln-relay-epoch-sec"
.}: uint64
.}: Option[uint64]
maxMessageSize* {.
desc:
@ -170,15 +180,18 @@ type WakuNodeConf* = object
name: "preset"
.}: string
# Option-typed; desc states the default since the CLI can't auto-show it for none().
clusterId* {.
desc:
"Cluster id that the node is running in. Node in a different cluster id is disconnected.",
defaultValue: 0,
desc: static(
"Cluster id that the node is running in. Node in a different cluster id is disconnected. Default is " &
$DefaultClusterId & "."
),
defaultValue: none(uint16),
name: "cluster-id"
.}: uint16
.}: Option[uint16]
agentString* {.
defaultValue: "logos-delivery-" & cli_args.git_version,
defaultValue: DefaultAgentString,
desc: "Node agent string which is used as identifier in network",
name: "agent-string"
.}: string
@ -203,7 +216,7 @@ type WakuNodeConf* = object
desc:
"Specify method to use for determining public address. " &
"Must be one of: any, none, upnp, pmp, extip:<IP>.",
defaultValue: "any"
defaultValue: DefaultCLINat
.}: string
extMultiAddrs* {.
@ -275,7 +288,9 @@ hence would have reachability issues.""",
## Relay config
relay* {.
desc: "Enable relay protocol: true|false", defaultValue: true, name: "relay"
desc: "Enable relay protocol: true|false",
defaultValue: DefaultCLIRelay,
name: "relay"
.}: bool
relayPeerExchange* {.
@ -291,11 +306,14 @@ hence would have reachability issues.""",
name: "relay-shard-manager"
.}: bool
# Option-typed; desc states the default since the CLI can't auto-show it for none().
rlnRelay* {.
desc: "Enable spam protection through rln-relay: true|false.",
defaultValue: false,
desc:
"Enable spam protection through rln-relay: true|false. Default is " &
$DefaultRlnRelayEnabled & ".",
defaultValue: none(bool),
name: "rln-relay"
.}: bool
.}: Option[bool]
rlnRelayCredIndex* {.
desc: "the index of the onchain commitment to use",
@ -304,9 +322,9 @@ hence would have reachability issues.""",
rlnRelayDynamic* {.
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false.",
defaultValue: false,
defaultValue: none(bool),
name: "rln-relay-dynamic"
.}: bool
.}: Option[bool]
entryNodes* {.
desc:
@ -466,13 +484,14 @@ hence would have reachability issues.""",
.}: string
## Reliability config
# Option-typed; desc states the default since the CLI can't auto-show it for none().
reliabilityEnabled* {.
desc:
"""Adds an extra effort in the delivery/reception of messages by leveraging store-v3 requests.
with the drawback of consuming some more bandwidth.""",
defaultValue: true,
"""Adds an extra effort in the delivery/reception of messages by leveraging store-v3 requests, with the drawback of consuming some more bandwidth. Default is """ &
$DefaultP2pReliability & ".",
defaultValue: none(bool),
name: "reliability"
.}: bool
.}: Option[bool]
## REST HTTP config
rest* {.
@ -557,8 +576,11 @@ with the drawback of consuming some more bandwidth.""",
.}: string
## Discovery v5 config
# Option-typed; desc states the default since the CLI can't auto-show it for none().
discv5Discovery* {.
desc: "Enable discovering nodes via Node Discovery v5.",
desc:
"Enable discovering nodes via Node Discovery v5. Default is " &
$DefaultDiscv5Enabled & ".",
defaultValue: none(bool),
name: "discv5-discovery"
.}: Option[bool]
@ -608,7 +630,7 @@ with the drawback of consuming some more bandwidth.""",
## waku peer exchange config
peerExchange* {.
desc: "Enable waku peer exchange protocol (responder side): true|false",
defaultValue: true,
defaultValue: DefaultCLIPeerExchange,
name: "peer-exchange"
.}: bool
@ -622,13 +644,17 @@ with the drawback of consuming some more bandwidth.""",
## Rendez vous
rendezvous* {.
desc: "Enable waku rendezvous discovery server",
defaultValue: true,
defaultValue: DefaultCLIRendezvous,
name: "rendezvous"
.}: bool
#Mix config
mix* {.desc: "Enable mix protocol: true|false", defaultValue: false, name: "mix".}:
bool
# Option-typed; desc states the default since the CLI can't auto-show it for none().
mix* {.
desc: "Enable mix protocol: true|false. Default is " & $DefaultMix & ".",
defaultValue: none(bool),
name: "mix"
.}: Option[bool]
mixkey* {.
desc:
@ -643,12 +669,14 @@ with the drawback of consuming some more bandwidth.""",
.}: seq[MixNodePubInfo]
# Kademlia Discovery config
# Option-typed; desc states the default since the CLI can't auto-show it for none().
enableKadDiscovery* {.
desc:
"Enable extended kademlia discovery. Can be enabled without bootstrap nodes for the first node in the network.",
defaultValue: false,
"Enable extended kademlia discovery. Can be enabled without bootstrap nodes for the first node in the network. Default is " &
$DefaultKadEnabled & ".",
defaultValue: none(bool),
name: "enable-kad-discovery"
.}: bool
.}: Option[bool]
kadBootstrapNodes* {.
desc:
@ -919,15 +947,15 @@ proc toKeystoreGeneratorConf*(n: WakuNodeConf): RlnKeystoreGeneratorConf =
chainId: UInt256.fromBytesBE(n.rlnRelayChainId.toBytesBE()),
ethClientUrls: n.ethClientUrls.mapIt(string(it)),
ethContractAddress: n.rlnRelayEthContractAddress,
userMessageLimit: n.rlnRelayUserMessageLimit,
userMessageLimit: n.rlnRelayUserMessageLimit.get(DefaultRlnRelayUserMessageLimit),
ethPrivateKey: n.rlnRelayEthPrivateKey,
credPath: n.rlnRelayCredPath,
credPassword: n.rlnRelayCredPassword,
)
proc toNetworkConf(
proc toNetworkPresetConf(
preset: string, clusterId: Option[uint16]
): ConfResult[Option[NetworkConf]] =
): ConfResult[Option[NetworkPresetConf]] =
var lcPreset = toLowerAscii(preset)
if clusterId.isSome() and clusterId.get() == 1:
warn(
@ -942,29 +970,30 @@ proc toNetworkConf(
case lcPreset
of "":
ok(none(NetworkConf))
ok(none(NetworkPresetConf))
of "twn":
ok(some(NetworkConf.TheWakuNetworkConf()))
ok(some(NetworkPresetConf.TheWakuNetworkConf()))
of "logos.dev", "logosdev":
ok(some(NetworkConf.LogosDevConf()))
ok(some(NetworkPresetConf.LogosDevConf()))
of "logos.test", "logostest":
ok(some(NetworkConf.LogosTestConf()))
ok(some(NetworkPresetConf.LogosTestConf()))
else:
err("Invalid --preset value passed: " & lcPreset)
proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
var b = WakuConfBuilder.init()
let networkConf = toNetworkConf(n.preset, some(n.clusterId)).valueOr:
let networkPresetConf = toNetworkPresetConf(n.preset, n.clusterId).valueOr:
return err("Error determining cluster from preset: " & $error)
if networkConf.isSome():
b.withNetworkConf(networkConf.get())
if networkPresetConf.isSome():
b.withNetworkPresetConf(networkPresetConf.get())
b.withLogLevel(n.logLevel)
b.withLogFormat(n.logFormat)
b.rlnRelayConf.withEnabled(n.rlnRelay)
if n.rlnRelay.isSome():
b.rlnRelayConf.withEnabled(n.rlnRelay.get())
if n.rlnRelayCredPath != "":
b.rlnRelayConf.withCredPath(n.rlnRelayCredPath)
if n.rlnRelayCredPassword != "":
@ -976,18 +1005,22 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
if n.rlnRelayChainId != 0:
b.rlnRelayConf.withChainId(n.rlnRelayChainId)
b.rlnRelayConf.withUserMessageLimit(n.rlnRelayUserMessageLimit)
b.rlnRelayConf.withEpochSizeSec(n.rlnEpochSizeSec)
if n.rlnRelayUserMessageLimit.isSome():
b.rlnRelayConf.withUserMessageLimit(n.rlnRelayUserMessageLimit.get())
if n.rlnEpochSizeSec.isSome():
b.rlnRelayConf.withEpochSizeSec(n.rlnEpochSizeSec.get())
if n.rlnRelayCredIndex.isSome():
b.rlnRelayConf.withCredIndex(n.rlnRelayCredIndex.get())
b.rlnRelayConf.withDynamic(n.rlnRelayDynamic)
if n.rlnRelayDynamic.isSome():
b.rlnRelayConf.withDynamic(n.rlnRelayDynamic.get())
if n.maxMessageSize != "":
b.withMaxMessageSize(n.maxMessageSize)
b.withProtectedShards(n.protectedShards)
b.withClusterId(n.clusterId)
if n.clusterId.isSome():
b.withClusterId(n.clusterId.get())
b.withAgentString(n.agentString)
@ -1041,7 +1074,7 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
if n.numShardsInNetwork != 0:
b.withNumShardsInCluster(n.numShardsInNetwork)
b.withShardingConf(AutoSharding)
else:
elif networkPresetConf.isNone():
b.withShardingConf(StaticSharding)
# It is not possible to pass an empty sequence on the CLI
@ -1074,9 +1107,10 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
b.storeServiceConf.storeSyncConf.withRangeSec(n.storeSyncRange)
b.storeServiceConf.storeSyncConf.withRelayJitterSec(n.storeSyncRelayJitter)
b.mixConf.withEnabled(n.mix)
if n.mix.isSome():
b.mixConf.withEnabled(n.mix.get())
b.withMix(n.mix.get())
b.mixConf.withMixNodes(n.mixnodes)
b.withMix(n.mix)
if n.mixkey.isSome():
b.mixConf.withMixKey(n.mixkey.get())
@ -1086,7 +1120,8 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
b.filterServiceConf.withMaxCriteria(n.filterMaxCriteria)
b.withLightPush(n.lightpush)
b.withP2pReliability(n.reliabilityEnabled)
if n.reliabilityEnabled.isSome():
b.withP2pReliability(n.reliabilityEnabled.get())
b.restServerConf.withEnabled(n.rest)
b.restServerConf.withListenAddress(n.restAddress)
@ -1129,7 +1164,8 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
b.withLocalStoragePath(n.localStoragePath)
b.kademliaDiscoveryConf.withEnabled(n.enableKadDiscovery)
if n.enableKadDiscovery.isSome():
b.kademliaDiscoveryConf.withEnabled(n.enableKadDiscovery.get())
b.kademliaDiscoveryConf.withBootstrapNodes(n.kadBootstrapNodes)
# Mode-driven configuration overrides

View File

@ -0,0 +1,121 @@
import std/[json, strutils, tables]
import confutils, confutils/std/net, results
import ./cli_args
proc collectJsonFields*(
jsonNode: JsonNode
): Result[Table[string, (string, JsonNode)], string] =
## Walk the top-level JSON object and key it by lowercased names.
if jsonNode.kind != JObject:
return err("config JSON must be a JSON object, got " & $jsonNode.kind)
var jsonFields: Table[string, (string, JsonNode)]
for key, value in jsonNode:
let lowerKey = key.toLowerAscii()
if jsonFields.hasKey(lowerKey):
let firstKey = jsonFields[lowerKey][0]
return err(
"Duplicate configuration option (case-insensitive): '" & firstKey & "' and '" &
key & "'"
)
jsonFields[lowerKey] = (key, value)
return ok(jsonFields)
proc unknownKeysError(
jsonFields: Table[string, (string, JsonNode)], prefix: string
): string =
## Format leftover JSON keys as an error message.
var keys = newSeq[string]()
for _, (jsonKey, _) in pairs(jsonFields):
keys.add(jsonKey)
return prefix & ": " & $keys
proc jsonScalarToString(node: JsonNode): Result[string, string] =
## Convert a scalar JSON value to its string form.
case node.kind
of JString:
return ok(node.getStr())
of JInt:
return ok($node.getInt())
of JFloat:
return ok($node.getFloat())
of JBool:
return ok($node.getBool())
of JNull:
return ok("")
else:
return err("expected scalar JSON value, got " & $node.kind)
proc applyJsonFieldsToConf(
conf: var WakuNodeConf,
jsonFields: var Table[string, (string, JsonNode)],
parseErrPrefix: string,
unknownErrPrefix: string,
): Result[void, string] =
## Walk `conf`'s fields and write each one matched (case-insensitive) by
## `jsonFields`. seq fields take a JArray (full replace); scalar fields
## take any scalar JSON kind. Errors on leftover unknown keys.
for confField, confValue in fieldPairs(conf):
let lowerField = confField.toLowerAscii()
if jsonFields.hasKey(lowerField):
let (jsonKey, jsonValue) = jsonFields[lowerField]
when confValue is seq:
if jsonValue.kind != JArray:
return err(
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey &
"' must be a JSON array"
)
var newSeq: typeof(confValue) = @[]
for item in jsonValue:
let formattedItem = jsonScalarToString(item).valueOr:
return err(
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " &
error
)
try:
type ElemType = typeof(confValue[0])
newSeq.add(parseCmdArg(ElemType, formattedItem))
except CatchableError as e:
return err(
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " &
e.msg & ". Value: " & formattedItem
)
confValue = newSeq
else:
let formattedString = jsonScalarToString(jsonValue).valueOr:
return err(
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " &
error
)
try:
confValue = parseCmdArg(typeof(confValue), formattedString)
except CatchableError as e:
return err(
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " &
e.msg & ". Value: " & formattedString
)
jsonFields.del(lowerField)
if jsonFields.len > 0:
return err(unknownKeysError(jsonFields, unknownErrPrefix))
return ok()
proc assembleFullConf*(
jsonFields: Table[string, (string, JsonNode)]
): Result[WakuNodeConf, string] =
## Build a WakuNodeConf from a flat JSON object whose keys are WakuNodeConf field names.
var conf = ?defaultWakuNodeConf()
var fields = jsonFields
?applyJsonFieldsToConf(
conf, fields, "Failed to parse field", "Unrecognized configuration option(s) found"
)
return ok(conf)
proc parseNodeConfFromJson*(jsonStr: string): Result[WakuNodeConf, string] =
## Parse a flat JSON config whose keys are WakuNodeConf field names.
var jsonNode: JsonNode
try:
jsonNode = parseJson(jsonStr)
except CatchableError as e:
return err("Failed to parse config JSON: " & e.msg)
let jsonFields = ?collectJsonFields(jsonNode)
return assembleFullConf(jsonFields)