diff --git a/apps/liteprotocoltester/liteprotocoltester.nim b/apps/liteprotocoltester/liteprotocoltester.nim index bcfe2cdec..dd3097e49 100644 --- a/apps/liteprotocoltester/liteprotocoltester.nim +++ b/apps/liteprotocoltester/liteprotocoltester.nim @@ -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 diff --git a/apps/networkmonitor/networkmonitor.nim b/apps/networkmonitor/networkmonitor.nim index 61bb8d07a..4fed6da01 100644 --- a/apps/networkmonitor/networkmonitor.nim +++ b/apps/networkmonitor/networkmonitor.nim @@ -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 diff --git a/examples/wakustealthcommitments/node_spec.nim b/examples/wakustealthcommitments/node_spec.nim index 8eb11595c..3f4deeb08 100644 --- a/examples/wakustealthcommitments/node_spec.nim +++ b/examples/wakustealthcommitments/node_spec.nim @@ -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: diff --git a/liblogosdelivery/logos_delivery_api/node_api.nim b/liblogosdelivery/logos_delivery_api/node_api.nim index de6f69c19..58785e80d 100644 --- a/liblogosdelivery/logos_delivery_api/node_api.nim +++ b/liblogosdelivery/logos_delivery_api/node_api.nim @@ -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 diff --git a/logos_delivery/waku/api/api.nim b/logos_delivery/waku/api/api.nim index 1cbe47256..74cf5888b 100644 --- a/logos_delivery/waku/api/api.nim +++ b/logos_delivery/waku/api/api.nim @@ -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 diff --git a/logos_delivery/waku/factory/conf_builder/discv5_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/discv5_conf_builder.nim index 5dd269d23..e28cb10fb 100644 --- a/logos_delivery/waku/factory/conf_builder/discv5_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/discv5_conf_builder.nim @@ -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), ) ) diff --git a/logos_delivery/waku/factory/conf_builder/filter_service_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/filter_service_conf_builder.nim index 0a6617430..dafd6b5ab 100644 --- a/logos_delivery/waku/factory/conf_builder/filter_service_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/filter_service_conf_builder.nim @@ -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), ) ) ) diff --git a/logos_delivery/waku/factory/conf_builder/kademlia_discovery_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/kademlia_discovery_conf_builder.nim index 576b6b7d9..62fe42e27 100644 --- a/logos_delivery/waku/factory/conf_builder/kademlia_discovery_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/kademlia_discovery_conf_builder.nim @@ -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: diff --git a/logos_delivery/waku/factory/conf_builder/metrics_server_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/metrics_server_conf_builder.nim index 8b2ea4eb8..ec3245f97 100644 --- a/logos_delivery/waku/factory/conf_builder/metrics_server_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/metrics_server_conf_builder.nim @@ -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), ) ) ) diff --git a/logos_delivery/waku/factory/conf_builder/mix_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/mix_conf_builder.nim index 971e63588..68aca5f68 100644 --- a/logos_delivery/waku/factory/conf_builder/mix_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/mix_conf_builder.nim @@ -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(): diff --git a/logos_delivery/waku/factory/conf_builder/rest_server_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/rest_server_conf_builder.nim index dcafbb56a..1bf7d17ff 100644 --- a/logos_delivery/waku/factory/conf_builder/rest_server_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/rest_server_conf_builder.nim @@ -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(), ) ) diff --git a/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim index 4cdcf8324..f4e70767e 100644 --- a/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim @@ -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), ) ) ) diff --git a/logos_delivery/waku/factory/conf_builder/store_service_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/store_service_conf_builder.nim index f1b0b1402..1df3da61f 100644 --- a/logos_delivery/waku/factory/conf_builder/store_service_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/store_service_conf_builder.nim @@ -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, ) ) diff --git a/logos_delivery/waku/factory/conf_builder/store_sync_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/store_sync_conf_builder.nim index 4c7177b71..74cbcece0 100644 --- a/logos_delivery/waku/factory/conf_builder/store_sync_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/store_sync_conf_builder.nim @@ -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(): diff --git a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim index 7d7189fac..d034e0bd0 100644 --- a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim @@ -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), ) diff --git a/logos_delivery/waku/factory/conf_builder/web_socket_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/web_socket_conf_builder.nim index 285e678e8..d71ffbc1f 100644 --- a/logos_delivery/waku/factory/conf_builder/web_socket_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/web_socket_conf_builder.nim @@ -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( diff --git a/logos_delivery/waku/factory/networks_config.nim b/logos_delivery/waku/factory/networks_config.nim index 488f58464..3d1e296fe 100644 --- a/logos_delivery/waku/factory/networks_config.nim +++ b/logos_delivery/waku/factory/networks_config.nim @@ -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, diff --git a/tests/api/test_api_health.nim b/tests/api/test_api_health.nim index e38439cbd..d8a7eabf2 100644 --- a/tests/api/test_api_health.nim +++ b/tests/api/test_api_health.nim @@ -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 diff --git a/tests/api/test_api_receive.nim b/tests/api/test_api_receive.nim index 3280f0609..d7b6e3d7b 100644 --- a/tests/api/test_api_receive.nim +++ b/tests/api/test_api_receive.nim @@ -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 diff --git a/tests/api/test_api_send.nim b/tests/api/test_api_send.nim index 3e9a4b717..e51473b17 100644 --- a/tests/api/test_api_send.nim +++ b/tests/api/test_api_send.nim @@ -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 diff --git a/tests/api/test_api_subscription.nim b/tests/api/test_api_subscription.nim index e20250791..2a6ce36ee 100644 --- a/tests/api/test_api_subscription.nim +++ b/tests/api/test_api_subscription.nim @@ -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 diff --git a/tests/api/test_node_conf.nim b/tests/api/test_node_conf.nim index 2da1362af..ce0e2b28e 100644 --- a/tests/api/test_node_conf.nim +++ b/tests/api/test_node_conf.nim @@ -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() diff --git a/tests/channels/test_reliable_channel_send_receive.nim b/tests/channels/test_reliable_channel_send_receive.nim index 16577899e..5a80d104c 100644 --- a/tests/channels/test_reliable_channel_send_receive.nim +++ b/tests/channels/test_reliable_channel_send_receive.nim @@ -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 diff --git a/tests/factory/test_waku_conf.nim b/tests/factory/test_waku_conf.nim index 8a033c008..117f23250 100644 --- a/tests/factory/test_waku_conf.nim +++ b/tests/factory/test_waku_conf.nim @@ -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": diff --git a/tests/test_waku.nim b/tests/test_waku.nim index 310356a44..ff009c3ba 100644 --- a/tests/test_waku.nim +++ b/tests/test_waku.nim @@ -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: diff --git a/tests/wakunode2/test_cli_args.nim b/tests/wakunode2/test_cli_args.nim index 36bb947f2..5b21bd327 100644 --- a/tests/wakunode2/test_cli_args.nim +++ b/tests/wakunode2/test_cli_args.nim @@ -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] diff --git a/tools/confutils/cli_args.nim b/tools/confutils/cli_args.nim index cbd707a2d..00a9b0794 100644 --- a/tools/confutils/cli_args.nim +++ b/tools/confutils/cli_args.nim @@ -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:.", - 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 diff --git a/tools/confutils/conf_from_json.nim b/tools/confutils/conf_from_json.nim new file mode 100644 index 000000000..1f305774e --- /dev/null +++ b/tools/confutils/conf_from_json.nim @@ -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)