Files
logos-messaging-nim/tools/confutils/cli_args.nim
Fabiana Cecin 6fe8b4b959 fix(config): logosdeliverynode explicit flags override mode
* remove entry-layer/mode fields from wakunode2's renamed kernel_cli_args.nim
* logosdeliverynode's new cli_args.nim allows mode and optional protocol flags
* logosdeliverynode rejects --mode with --entry-layer=kernel
* delete EntryLayer from library API new/init (redundant with proc signatures)
2026-08-14 00:46:49 -03:00

1004 lines
32 KiB
Nim

## CLI configuration of logosdeliverynode.
import
std/[strutils, strformat, sequtils],
results,
chronicles,
chronos,
regex,
stew/endians2,
stint,
confutils,
confutils/defs,
confutils/std/net,
confutils/toml/defs as confTomlDefs,
confutils/toml/std/net as confTomlNet,
libp2p/crypto/curve25519,
libp2p/crypto/crypto,
libp2p/crypto/secp,
libp2p/multiaddress,
libp2p/multicodec,
nimcrypto/utils,
secp256k1,
json
import
logos_delivery/api/conf/modes,
logos_delivery/waku/factory/[waku_conf, conf_builder/conf_builder, networks_config],
logos_delivery/waku/common/[logging],
logos_delivery/waku/[
waku_enr,
node/peer_manager,
waku_core/topics/pubsub_topic,
waku_core/message/default_values,
waku_mix,
],
./entry_nodes
import ./envvar as confEnvvarDefs, ./envvar_net as confEnvvarNet
import ./kernel_cli_args
export modes, kernel_cli_args
logScope:
topics = "delivery cli args"
proc `$`*(u: EthRpcUrl): string =
## `load` and `defaultLogosDeliveryNodeConf` are generic over the conf type,
## so confutils expands its default-value printing in the caller's scope;
## this `$` must be exported for those expansions to resolve.
string(u)
type LogosDeliveryNodeConf* = object
## LogosDeliveryNodeConf is the kernel configuration with two additional
## fields.
##
## `entryLayer` lets logosdeliverynode users specify the layer-mount height,
## which is never guessed from given CLI parameters.
##
## `mode` is a messaging-only option. The kernel layer has no mode: its
## entire configuration must be given explicitly and it has its own
## per-config-option defaults which add up to what you'd expect for
## fleet-node use.
configFile* {.
desc: "Loads configuration from a TOML file (cmd-line parameters take precedence)",
name: "config-file"
.}: Opt[InputFile]
## Log configuration
logLevel* {.
desc:
"Sets the log level for process. Supported levels: TRACE, DEBUG, INFO, NOTICE, WARN, ERROR or FATAL",
defaultValue: logging.LogLevel.INFO,
name: "log-level"
.}: logging.LogLevel
logFormat* {.
desc:
"Specifies what kind of logs should be written to stdout. Supported formats: TEXT, JSON",
defaultValue: logging.LogFormat.TEXT,
name: "log-format"
.}: logging.LogFormat
rlnRelayCredPath* {.
desc: "The path for persisting rln-relay credential",
defaultValue: "",
name: "rln-relay-cred-path"
.}: string
ethClientUrls* {.
desc:
"HTTP address of an Ethereum testnet client e.g., http://localhost:8540/. Argument may be repeated.",
defaultValue: @[EthRpcUrl("http://localhost:8540/")],
name: "rln-relay-eth-client-address"
.}: seq[EthRpcUrl]
rlnRelayEthContractAddress* {.
desc: "Address of membership contract on an Ethereum testnet.",
defaultValue: "",
name: "rln-relay-eth-contract-address"
.}: string
rlnRelayChainId* {.
desc:
"Chain ID of the provided contract (optional, will fetch from RPC provider if not used)",
defaultValue: 0,
name: "rln-relay-chain-id"
.}: uint
rlnRelayCredPassword* {.
desc: "Password for encrypting RLN credentials",
defaultValue: "",
name: "rln-relay-cred-password"
.}: string
rlnRelayEthPrivateKey* {.
desc: "Private key for broadcasting transactions",
defaultValue: "",
name: "rln-relay-eth-private-key"
.}: string
# Opt-typed; desc states the default since the CLI can't auto-show it for Opt.none().
rlnRelayUserMessageLimit* {.
desc:
"Set a user message limit for the rln membership registration. Must be a positive integer. Default is " &
$DefaultRlnRelayUserMessageLimit & ".",
defaultValue: Opt.none(uint64),
name: "rln-relay-user-message-limit"
.}: Opt[uint64]
# Opt-typed; desc states the default since the CLI can't auto-show it for Opt.none().
rlnEpochSizeSec* {.
desc:
"Epoch size in seconds used to rate limit RLN memberships. Default is " &
$DefaultRlnRelayEpochSizeSec & " second.",
defaultValue: Opt.none(uint64),
name: "rln-relay-epoch-sec"
.}: Opt[uint64]
maxMessageSize* {.
desc:
"Maximum message size. Accepted units: KiB, KB, and B. e.g. 1024KiB; 1500 B; etc.",
defaultValue: DefaultMaxWakuMessageSizeStr,
name: "max-msg-size"
.}: string
case cmd* {.command, defaultValue: noCommand.}: StartUpCommand
of generateRlnKeystore:
execute* {.
desc: "Runs the registration function on-chain. By default, a dry-run will occur",
defaultValue: false,
name: "execute"
.}: bool
of noCommand:
## Application-level configuration
protectedShards* {.
desc:
"Shards and its public keys to be used for message validation, shard:pubkey. Argument may be repeated.",
defaultValue: newSeq[ProtectedShard](0),
name: "protected-shard"
.}: seq[ProtectedShard]
## General node config
preset* {.
desc:
"Network preset to use. 'twn' is The RLN-protected Waku Network (cluster 1). 'logos.dev' is the Logos Dev Network (cluster 3). 'logos.test' is the Logos Test Network (cluster 2). 'status.prod' is the Status Production Network (cluster 16, RLN off, auto-sharding with 1 shard). Overrides other values.",
defaultValue: "",
name: "preset"
.}: string
entryLayer* {.
desc:
"Top API layer to run: kernel (transport only), messaging, or channels (messaging + reliable channels).",
defaultValue: EntryLayer.channels,
name: "entry-layer"
.}: EntryLayer
mode* {.
desc:
"Node operating mode: Edge (client-only) or Core (full service node). Applies to --entry-layer=messaging|channels; rejected for --entry-layer=kernel. Default is Core.",
defaultValue: Opt.none(LogosDeliveryMode),
name: "mode"
.}: Opt[LogosDeliveryMode]
# Opt-typed; desc states the default since the CLI can't auto-show it for Opt.none().
clusterId* {.
desc: static(
"Cluster id that the node is running in. Node in a different cluster id is disconnected. Default is " &
$DefaultClusterId & "."
),
defaultValue: Opt.none(uint16),
name: "cluster-id"
.}: Opt[uint16]
agentString* {.
defaultValue: DefaultAgentString,
desc: "Node agent string which is used as identifier in network",
name: "agent-string"
.}: string
nodekey* {.desc: "P2P node private key as 64 char hex string.", name: "nodekey".}:
Opt[PrivateKey]
listenAddress* {.
defaultValue: defaultListenAddress(),
desc: "Listening address for LibP2P (and Discovery v5, if enabled) traffic.",
name: "listen-address"
.}: IpAddress
tcpPort* {.desc: "TCP listening port.", defaultValue: 60000, name: "tcp-port".}:
Port
portsShift* {.
desc: "Add a shift to all port numbers.", defaultValue: 0, name: "ports-shift"
.}: uint16
nat* {.
desc:
"Specify method to use for determining public address. " &
"Must be one of: any, none, upnp, pmp, extip:<IP>.",
defaultValue: DefaultCLINat,
name: "nat"
.}: string
extMultiAddrs* {.
desc:
"External multiaddresses to advertise to the network. Argument may be repeated.",
name: "ext-multiaddr"
.}: seq[string]
extMultiAddrsOnly* {.
desc: "Only announce external multiaddresses setup with --ext-multiaddr",
defaultValue: false,
name: "ext-multiaddr-only"
.}: bool
maxConnections* {.
desc:
"Maximum allowed number of libp2p connections. (Default: 150) that's recommended value for better connectivity",
defaultValue: 150,
name: "max-connections"
.}: int
relayServiceRatio* {.
desc:
"This percentage ratio represents the relay peers to service peers. For example, 60:40, tells that 60% of the max-connections will be used for relay protocol and the other 40% of max-connections will be reserved for other service protocols (e.g., filter, lightpush, store, metadata, etc.)",
defaultValue: "50:50",
name: "relay-service-ratio"
.}: string
colocationLimit* {.
desc:
"Max num allowed peers from the same IP. Set it to 0 to remove the limitation.",
defaultValue: defaultColocationLimit(),
name: "ip-colocation-limit"
.}: int
peerStoreCapacity* {.
desc: "Maximum stored peers in the peerstore.", name: "peer-store-capacity"
.}: Opt[int]
peerPersistence* {.
desc: "Enable peer persistence.", defaultValue: false, name: "peer-persistence"
.}: bool
## DNS addrs config
dnsAddrsNameServers* {.
desc:
"DNS name server IPs to query for DNS multiaddrs resolution. Argument may be repeated.",
defaultValue: @[
IpAddress(family: IpAddressFamily.IPv4, address_v4: [1'u8, 1, 1, 1]),
IpAddress(family: IpAddressFamily.IPv4, address_v4: [1'u8, 0, 0, 1]),
],
name: "dns-addrs-name-server"
.}: seq[IpAddress]
dns4DomainName* {.
desc: "The domain name resolving to the node's public IPv4 address",
defaultValue: "",
name: "dns4-domain-name"
.}: string
## Circuit-relay config
isRelayClient* {.
desc: """Set the node as a relay-client.
Set it to true for nodes that run behind a NAT or firewall and
hence would have reachability issues.""",
defaultValue: false,
name: "relay-client"
.}: bool
## Relay config
relay* {.
desc: "Enable relay protocol: true|false. Default is true.",
defaultValue: Opt.none(bool),
name: "relay"
.}: Opt[bool]
relayPeerExchange* {.
desc: "Enable gossipsub peer exchange in relay protocol: true|false",
defaultValue: false,
name: "relay-peer-exchange"
.}: bool
relayShardedPeerManagement* {.
desc:
"Enable experimental shard aware peer manager for relay protocol: true|false",
defaultValue: false,
name: "relay-shard-manager"
.}: bool
# Opt-typed; desc states the default since the CLI can't auto-show it for Opt.none().
rlnRelay* {.
desc:
"Enable spam protection through rln-relay: true|false. Default is " &
$DefaultRlnRelayEnabled & ".",
defaultValue: Opt.none(bool),
name: "rln-relay"
.}: Opt[bool]
rlnRelayCredIndex* {.
desc: "the index of the onchain commitment to use",
name: "rln-relay-membership-index"
.}: Opt[uint]
rlnRelayDynamic* {.
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false.",
defaultValue: Opt.none(bool),
name: "rln-relay-dynamic"
.}: Opt[bool]
entryNodes* {.
desc:
"Entry node address (enrtree:, enr:, or multiaddr). " &
"Automatically classified and distributed to DNS discovery, discv5 bootstrap, " &
"and static nodes. Argument may be repeated.",
name: "entry-node"
.}: seq[string]
staticnodes* {.
desc: "Peer multiaddr to directly connect with. Argument may be repeated.",
name: "staticnode"
.}: seq[string]
keepAlive* {.
desc:
"Deprecated since >=v0.37. This param is ignored and keep alive is always active",
defaultValue: true,
name: "keep-alive"
.}: bool
numShardsInNetwork* {.
desc:
"Enables autosharding and set number of shards in the cluster, set to `0` to use static sharding",
defaultValue: 0,
name: "num-shards-in-network"
.}: uint16
shards* {.
desc:
"Shards index to subscribe to [0..NUM_SHARDS_IN_NETWORK-1]. Argument may be repeated. Subscribes to all shards by default in auto-sharding, no shard for static sharding",
name: "shard"
.}: seq[uint16]
contentTopics* {.
desc: "Default content topic to subscribe to. Argument may be repeated.",
name: "content-topic"
.}: seq[string]
## Store and message store config
store* {.
desc: "Enable/disable waku store protocol. Default is false.",
defaultValue: Opt.none(bool),
name: "store"
.}: Opt[bool]
storenode* {.
desc: "Peer multiaddress to query for storage",
defaultValue: "",
name: "storenode"
.}: string
storeMessageRetentionPolicy* {.
desc:
"Message store retention policy. Multiple policies may be provided as a semicolon-separated string and are applied as a union. Time retention policy: 'time:<seconds>'. Capacity retention policy: 'capacity:<count>'. Size retention policy: 'size:<xMB/xGB>'. Set to 'none' to disable. Example: 'time:3600;size:1GB;capacity:100'.",
defaultValue: "time:" & $2.days.seconds,
name: "store-message-retention-policy"
.}: string
storeMessageDbUrl* {.
desc: "The database connection URL for peristent storage.",
defaultValue: "sqlite://store.sqlite3",
name: "store-message-db-url"
.}: string
storeMessageDbVacuum* {.
desc:
"Enable database vacuuming at start. Only supported by SQLite database engine.",
defaultValue: false,
name: "store-message-db-vacuum"
.}: bool
storeMessageDbMigration* {.
desc: "Enable database migration at start.",
defaultValue: true,
name: "store-message-db-migration"
.}: bool
storeMaxNumDbConnections* {.
desc: "Maximum number of simultaneous Postgres connections.",
defaultValue: 50,
name: "store-max-num-db-connections"
.}: int
storeResume* {.
desc: "Enable store resume functionality",
defaultValue: false,
name: "store-resume"
.}: bool
## Sync config
storeSync* {.
desc: "Enable store sync protocol: true|false",
defaultValue: false,
name: "store-sync"
.}: bool
storeSyncInterval* {.
desc: "Interval between store sync attempts. In seconds.",
defaultValue: 300, # 5 minutes
name: "store-sync-interval"
.}: uint32
storeSyncRange* {.
desc: "Amount of time to sync. In seconds.",
defaultValue: 3600, # 1 hours
name: "store-sync-range"
.}: uint32
storeSyncRelayJitter* {.
hidden,
desc: "Time offset to account for message propagation jitter. In seconds.",
defaultValue: 20,
name: "store-sync-relay-jitter"
.}: uint32
## Filter config
filter* {.
desc: "Enable filter protocol: true|false. Default is true.",
defaultValue: Opt.none(bool),
name: "filter"
.}: Opt[bool]
filternode* {.
desc: "Peer multiaddr to request content filtering of messages.",
defaultValue: "",
name: "filternode"
.}: string
filterSubscriptionTimeout* {.
desc:
"Timeout for filter subscription without ping or refresh it, in seconds. Only for v2 filter protocol.",
defaultValue: 300, # 5 minutes
name: "filter-subscription-timeout"
.}: uint16
filterMaxPeersToServe* {.
desc: "Maximum number of peers to serve at a time. Only for v2 filter protocol.",
defaultValue: 1000,
name: "filter-max-peers-to-serve"
.}: uint32
filterMaxCriteria* {.
desc:
"Maximum number of pubsub- and content topic combination per peers at a time. Only for v2 filter protocol.",
defaultValue: 1000,
name: "filter-max-criteria"
.}: uint32
## Lightpush config
lightpush* {.
desc: "Enable lightpush protocol: true|false. Default is true.",
defaultValue: Opt.none(bool),
name: "lightpush"
.}: Opt[bool]
lightpushnode* {.
desc: "Peer multiaddr to request lightpush of published messages.",
defaultValue: "",
name: "lightpushnode"
.}: string
## REST HTTP config
rest* {.
desc: "Enable Waku REST HTTP server: true|false",
defaultValue: false,
name: "rest"
.}: bool
restAddress* {.
desc: "Listening address of the REST HTTP server.",
defaultValue:
IpAddress(family: IpAddressFamily.IPv4, address_v4: [127'u8, 0, 0, 1]),
name: "rest-address"
.}: IpAddress
restPort* {.
desc: "Listening port of the REST HTTP server.",
defaultValue: 8645,
name: "rest-port"
.}: uint16
restRelayCacheCapacity* {.
desc: "Capacity of the Relay REST API message cache.",
defaultValue: 50,
name: "rest-relay-cache-capacity"
.}: uint32
restAdmin* {.
desc: "Enable access to REST HTTP Admin API: true|false",
defaultValue: false,
name: "rest-admin"
.}: bool
restAllowOrigin* {.
desc:
"Allow cross-origin requests from the specified origin." &
"Argument may be repeated." & "Wildcards: * or ? allowed." &
"Ex.: \"localhost:*\" or \"127.0.0.1:8080\"",
defaultValue: newSeq[string](),
name: "rest-allow-origin"
.}: seq[string]
## Metrics config
metricsServer* {.
desc: "Enable the metrics server: true|false",
defaultValue: false,
name: "metrics-server"
.}: bool
metricsServerAddress* {.
desc: "Listening address of the metrics server.",
defaultValue:
IpAddress(family: IpAddressFamily.IPv4, address_v4: [127'u8, 0, 0, 1]),
name: "metrics-server-address"
.}: IpAddress
metricsServerPort* {.
desc: "Listening HTTP port of the metrics server.",
defaultValue: 8008,
name: "metrics-server-port"
.}: uint16
metricsLogging* {.
desc: "Enable metrics logging: true|false",
defaultValue: true,
name: "metrics-logging"
.}: bool
## DNS discovery config
dnsDiscovery* {.
desc:
"Deprecated, please set dns-discovery-url instead. Enable discovering nodes via DNS",
defaultValue: false,
name: "dns-discovery"
.}: bool
dnsDiscoveryUrl* {.
desc:
"URL for DNS node list in format 'enrtree://<key>@<fqdn>', enables DNS Discovery",
defaultValue: "",
name: "dns-discovery-url"
.}: string
## Discovery v5 config
discv5Discovery* {.
desc: "Enable discovering nodes via Node Discovery v5. Default is true.",
defaultValue: Opt.none(bool),
name: "discv5-discovery"
.}: Opt[bool]
discv5UdpPort* {.
desc: "Listening UDP port for Node Discovery v5.",
defaultValue: 9000,
name: "discv5-udp-port"
.}: Port
discv5BootstrapNodes* {.
desc:
"Text-encoded ENR for bootstrap node. Used when connecting to the network. Argument may be repeated.",
name: "discv5-bootstrap-node"
.}: seq[string]
discv5EnrAutoUpdate* {.
desc:
"Discovery can automatically update its ENR with the IP address " &
"and UDP port as seen by other nodes it communicates with. " &
"This option allows to enable/disable this functionality",
defaultValue: false,
name: "discv5-enr-auto-update"
.}: bool
discv5TableIpLimit* {.
hidden,
desc: "Maximum amount of nodes with the same IP in discv5 routing tables",
defaultValue: 10,
name: "discv5-table-ip-limit"
.}: uint
discv5BucketIpLimit* {.
hidden,
desc: "Maximum amount of nodes with the same IP in discv5 routing table buckets",
defaultValue: 2,
name: "discv5-bucket-ip-limit"
.}: uint
discv5BitsPerHop* {.
hidden,
desc: "Kademlia's b variable, increase for less hops per lookup",
defaultValue: 1,
name: "discv5-bits-per-hop"
.}: int
## waku peer exchange config
peerExchange* {.
desc:
"Enable waku peer exchange protocol (responder side): true|false. Default is true.",
defaultValue: Opt.none(bool),
name: "peer-exchange"
.}: Opt[bool]
peerExchangeNode* {.
desc:
"Peer multiaddr to send peer exchange requests to. (enables peer exchange protocol requester side)",
defaultValue: "",
name: "peer-exchange-node"
.}: string
## Rendez vous
rendezvous* {.
desc: "Enable waku rendezvous discovery server. Default is true.",
defaultValue: Opt.none(bool),
name: "rendezvous"
.}: Opt[bool]
#Mix config
# Opt-typed; desc states the default since the CLI can't auto-show it for Opt.none().
mix* {.
desc: "Enable mix protocol: true|false. Default is " & $DefaultMix & ".",
defaultValue: Opt.none(bool),
name: "mix"
.}: Opt[bool]
mixkey* {.
desc:
"ED25519 private key as 64 char hex string , without 0x. If not provided, a random key will be generated.",
name: "mixkey"
.}: Opt[string]
mixnodes* {.
desc:
"Multiaddress and mix-key of mix node to be statically specified in format multiaddr:mixPubKey. Argument may be repeated.",
name: "mixnode"
.}: seq[MixNodePubInfo]
# Kademlia Discovery config
# Opt-typed; desc states the default since the CLI can't auto-show it for Opt.none().
enableKadDiscovery* {.
desc:
"Enable extended kademlia discovery. Can be enabled without bootstrap nodes for the first node in the network. Default is " &
$DefaultKadEnabled & ".",
defaultValue: Opt.none(bool),
name: "enable-kad-discovery"
.}: Opt[bool]
kadBootstrapNodes* {.
desc:
"Peer multiaddr for kademlia discovery bootstrap node (must include /p2p/<peerID>). Argument may be repeated.",
name: "kad-bootstrap-node"
.}: seq[string]
kadRandomLookupIntervalSec* {.
desc: "Interval seconds between random kademlia lookups.",
defaultValue: 60,
name: "kad-random-lookup-interval"
.}: uint32
kadServiceLookupIntervalSec* {.
desc: "Interval seconds between service-specific kademlia lookups.",
defaultValue: 60,
name: "kad-service-lookup-interval"
.}: uint32
## websocket config
websocketSupport* {.
desc: "Enable websocket: true|false",
defaultValue: false,
name: "websocket-support"
.}: bool
websocketPort* {.
desc: "WebSocket listening port.", defaultValue: 8000, name: "websocket-port"
.}: Port
websocketSecureSupport* {.
desc: "Enable secure websocket: true|false",
defaultValue: false,
name: "websocket-secure-support"
.}: bool
websocketSecureKeyPath* {.
desc: "Secure websocket key path: '/path/to/key.txt' ",
defaultValue: "",
name: "websocket-secure-key-path"
.}: string
websocketSecureCertPath* {.
desc: "Secure websocket Certificate path: '/path/to/cert.txt' ",
defaultValue: "",
name: "websocket-secure-cert-path"
.}: string
## quic config
quicSupport* {.
desc: "Enable QUIC transport: true|false",
defaultValue: false,
name: "quic-support"
.}: bool
quicPort* {.
desc: "QUIC (UDP) listening port.", defaultValue: 60000, name: "quic-port"
.}: Port
## Rate limitation config, if not set, rate limit checks will not be performed
rateLimits* {.
desc:
"Rate limit settings for different protocols." &
"Format: protocol:volume/period<unit>" &
" Where 'protocol' can be one of: <store|storev3|lightpush|px|filter> if not defined it means a global setting" &
" 'volume' and period must be an integer value. " &
" 'unit' must be one of <h|m|s|ms> - hours, minutes, seconds, milliseconds respectively. " &
"Argument may be repeated.",
defaultValue: newSeq[string](0),
name: "rate-limit"
.}: seq[string]
localStoragePath* {.
desc: "Path to store local data.",
defaultValue: "./data",
name: "local-storage-path"
.}: string
proc parseCmdArg*(T: type LogosDeliveryMode, s: string): T {.raises: [ValueError].} =
## The generic `Opt[T]` flag parser dispatches to `parseCmdArg(T, ...)` for
## the inner type, so `Opt[LogosDeliveryMode]` needs this overload (bare enum
## fields parse through confutils' internal enum support instead).
case s.strip().toLowerAscii()
of "edge":
LogosDeliveryMode.Edge
of "core":
LogosDeliveryMode.Core
else:
raise
newException(ValueError, "Invalid mode: '" & s & "' (expected 'Edge' or 'Core')")
proc completeCmdArg*(T: type LogosDeliveryMode, val: string): seq[string] =
return @[]
proc load*(T: type LogosDeliveryNodeConf, version = ""): ConfResult[T] =
try:
let conf = LogosDeliveryNodeConf.load(
version = version,
secondarySources = proc(
conf: LogosDeliveryNodeConf, sources: auto
) {.gcsafe, raises: [ConfigurationError].} =
sources.addConfigFile(Envvar, InputFile("wakunode2"))
if conf.configFile.isSome():
sources.addConfigFile(Toml, conf.configFile.get())
,
)
ok(conf)
except CatchableError:
err(getCurrentExceptionMsg())
proc defaultLogosDeliveryNodeConf*(): ConfResult[LogosDeliveryNodeConf] =
try:
let conf = LogosDeliveryNodeConf.load(version = "", cmdLine = @[])
return ok(conf)
except CatchableError:
return err("exception in defaultLogosDeliveryNodeConf: " & getCurrentExceptionMsg())
proc toWakuConf*(n: LogosDeliveryNodeConf): ConfResult[WakuConf] =
var b = WakuConfBuilder.init()
let networkPresetConf = toNetworkPresetConf(n.preset, n.clusterId).valueOr:
return err("Error determining cluster from preset: " & $error)
if networkPresetConf.isSome():
b.withNetworkPresetConf(networkPresetConf.get())
b.withLogLevel(n.logLevel)
b.withLogFormat(n.logFormat)
if n.rlnRelay.isSome():
b.rlnRelayConf.withEnabled(n.rlnRelay.get())
if n.rlnRelayCredPath != "":
b.rlnRelayConf.withCredPath(n.rlnRelayCredPath)
if n.rlnRelayCredPassword != "":
b.rlnRelayConf.withCredPassword(n.rlnRelayCredPassword)
if n.ethClientUrls.len > 0:
b.rlnRelayConf.withEthClientUrls(n.ethClientUrls.mapIt(string(it)))
if n.rlnRelayEthContractAddress != "":
b.rlnRelayConf.withEthContractAddress(n.rlnRelayEthContractAddress)
if n.rlnRelayChainId != 0:
b.rlnRelayConf.withChainId(n.rlnRelayChainId)
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())
if n.rlnRelayDynamic.isSome():
b.rlnRelayConf.withDynamic(n.rlnRelayDynamic.get())
if n.maxMessageSize != "":
b.withMaxMessageSize(n.maxMessageSize)
b.withProtectedShards(n.protectedShards)
if n.clusterId.isSome():
b.withClusterId(n.clusterId.get())
b.withAgentString(n.agentString)
if n.nodeKey.isSome():
b.withNodeKey(n.nodeKey.get())
b.withP2pListenAddress(n.listenAddress)
b.withP2pTcpPort(n.tcpPort)
b.withPortsShift(n.portsShift)
b.withNatStrategy(n.nat)
b.withExtMultiAddrs(n.extMultiAddrs)
b.withExtMultiAddrsOnly(n.extMultiAddrsOnly)
b.withMaxConnections(n.maxConnections)
if n.relayServiceRatio != "":
b.withRelayServiceRatio(n.relayServiceRatio)
b.withColocationLimit(n.colocationLimit)
if n.peerStoreCapacity.isSome:
b.withPeerStoreCapacity(n.peerStoreCapacity.get())
b.withPeerPersistence(n.peerPersistence)
b.withDnsAddrsNameServers(n.dnsAddrsNameServers)
b.withDns4DomainName(n.dns4DomainName)
b.withCircuitRelayClient(n.isRelayClient)
if n.relay.isSome():
b.withRelay(n.relay.get())
b.withRelayPeerExchange(n.relayPeerExchange)
b.withRelayShardedPeerManagement(n.relayShardedPeerManagement)
b.withStaticNodes(n.staticNodes)
# Process entry nodes - supports enrtree:, enr:, and multiaddress formats
if n.entryNodes.len > 0:
let (enrTreeUrls, bootstrapEnrs, staticNodesFromEntry) = processEntryNodes(
n.entryNodes
).valueOr:
return err("Failed to process entry nodes: " & error)
# Set ENRTree URLs for DNS discovery
if enrTreeUrls.len > 0:
for url in enrTreeUrls:
b.dnsDiscoveryConf.withEnrTreeUrl(url)
# Set ENR records as bootstrap nodes for discv5
if bootstrapEnrs.len > 0:
b.discv5Conf.withBootstrapNodes(bootstrapEnrs)
# Add static nodes (multiaddrs and those extracted from ENR entries)
if staticNodesFromEntry.len > 0:
b.withStaticNodes(staticNodesFromEntry)
if n.numShardsInNetwork != 0:
b.withNumShardsInCluster(n.numShardsInNetwork)
b.withShardingConf(AutoSharding)
elif networkPresetConf.isNone():
b.withShardingConf(StaticSharding)
# It is not possible to pass an empty sequence on the CLI
# If this is empty, it means the user did not specify any shards
if n.shards.len != 0:
b.withSubscribeShards(n.shards)
b.withContentTopics(n.contentTopics)
if n.store.isSome():
b.storeServiceConf.withEnabled(n.store.get())
b.storeServiceConf.withRetentionPolicies(n.storeMessageRetentionPolicy)
b.storeServiceConf.withDbUrl(n.storeMessageDbUrl)
b.storeServiceConf.withDbVacuum(n.storeMessageDbVacuum)
b.storeServiceConf.withDbMigration(n.storeMessageDbMigration)
b.storeServiceConf.withMaxNumDbConnections(n.storeMaxNumDbConnections)
b.storeServiceConf.withResume(n.storeResume)
# TODO: can we just use `Opt` on the CLI?
if n.storenode != "":
b.withRemoteStoreNode(n.storenode)
if n.filternode != "":
b.withRemoteFilterNode(n.filternode)
if n.lightpushnode != "":
b.withRemoteLightPushNode(n.lightpushnode)
if n.peerExchangeNode != "":
b.withRemotePeerExchangeNode(n.peerExchangeNode)
b.storeServiceConf.storeSyncConf.withEnabled(n.storeSync)
b.storeServiceConf.storeSyncConf.withIntervalSec(n.storeSyncInterval)
b.storeServiceConf.storeSyncConf.withRangeSec(n.storeSyncRange)
b.storeServiceConf.storeSyncConf.withRelayJitterSec(n.storeSyncRelayJitter)
if n.mix.isSome():
b.mixConf.withEnabled(n.mix.get())
b.withMix(n.mix.get())
b.mixConf.withMixNodes(n.mixnodes)
if n.mixkey.isSome():
b.mixConf.withMixKey(n.mixkey.get())
if n.filter.isSome():
b.filterServiceConf.withEnabled(n.filter.get())
b.filterServiceConf.withSubscriptionTimeout(n.filterSubscriptionTimeout)
b.filterServiceConf.withMaxPeersToServe(n.filterMaxPeersToServe)
b.filterServiceConf.withMaxCriteria(n.filterMaxCriteria)
if n.lightpush.isSome():
b.withLightPush(n.lightpush.get())
b.restServerConf.withEnabled(n.rest)
b.restServerConf.withListenAddress(n.restAddress)
b.restServerConf.withPort(n.restPort)
b.restServerConf.withRelayCacheCapacity(n.restRelayCacheCapacity)
b.restServerConf.withAdmin(n.restAdmin)
b.restServerConf.withAllowOrigin(n.restAllowOrigin)
b.metricsServerConf.withEnabled(n.metricsServer)
b.metricsServerConf.withHttpAddress(n.metricsServerAddress)
b.metricsServerConf.withHttpPort(n.metricsServerPort)
b.metricsServerConf.withLogging(n.metricsLogging)
if n.dnsDiscoveryUrl != "":
b.dnsDiscoveryConf.withEnrTreeUrl(n.dnsDiscoveryUrl)
b.dnsDiscoveryConf.withNameServers(n.dnsAddrsNameServers)
if n.discv5Discovery.isSome():
b.discv5Conf.withEnabled(n.discv5Discovery.get())
b.discv5Conf.withUdpPort(n.discv5UdpPort)
b.discv5Conf.withBootstrapNodes(n.discv5BootstrapNodes)
b.discv5Conf.withEnrAutoUpdate(n.discv5EnrAutoUpdate)
b.discv5Conf.withTableIpLimit(n.discv5TableIpLimit)
b.discv5Conf.withBucketIpLimit(n.discv5BucketIpLimit)
b.discv5Conf.withBitsPerHop(n.discv5BitsPerHop)
if n.peerExchange.isSome():
b.withPeerExchange(n.peerExchange.get())
if n.rendezvous.isSome():
b.withRendezvous(n.rendezvous.get())
b.webSocketConf.withEnabled(n.websocketSupport)
b.webSocketConf.withWebSocketPort(n.websocketPort)
b.webSocketConf.withSecureEnabled(n.websocketSecureSupport)
b.webSocketConf.withKeyPath(n.websocketSecureKeyPath)
b.webSocketConf.withCertPath(n.websocketSecureCertPath)
b.quicConf.withEnabled(n.quicSupport)
b.quicConf.withQuicPort(n.quicPort)
if n.rateLimits.len > 0:
b.rateLimitConf.withRateLimits(n.rateLimits)
else:
# The default protocol posture serves filter/lightpush, so default service
# rate limits must apply unless the operator sets their own.
b.rateLimitConf.withRateLimitsIfNotAssigned(
@["filter:100/1s", "lightpush:5/1s", "px:5/1s"]
)
b.withLocalStoragePath(n.localStoragePath)
if n.enableKadDiscovery.isSome():
b.kademliaDiscoveryConf.withEnabled(n.enableKadDiscovery.get())
b.kademliaDiscoveryConf.withBootstrapNodes(n.kadBootstrapNodes)
if n.kadRandomLookupIntervalSec > 0:
b.kademliaDiscoveryConf.withRandomLookupInterval(
chronos.seconds(n.kadRandomLookupIntervalSec.int64)
)
if n.kadServiceLookupIntervalSec > 0:
b.kademliaDiscoveryConf.withServiceLookupInterval(
chronos.seconds(n.kadServiceLookupIntervalSec.int64)
)
return b.build()