Igor Sirotin 2d96e55a09 feat: add status.prod network preset (cluster 16, RLN off)
Add a `status.prod` network preset usable from the Messaging API and CLI
(`--preset=status.prod`). It targets the live status.prod network that
Status runs on:

- cluster-id 16
- RLN disabled
- static sharding (fleet shards 1, 32, 64, 128, 256; Status uses 32 & 64)
- max message size 1024KiB
- bootstrap via the status.prod DNS-discovery enrtree and the three fleet boot nodes

Config sourced from https://fleets.waku.org/ and each host's /config.toml.

Closes #3906

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:50:19 +00:00

1231 lines
39 KiB
Nim

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/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,
],
../../tools/rln_keystore_generator/rln_keystore_generator,
./entry_nodes
import ./envvar as confEnvvarDefs, ./envvar_net as confEnvvarNet
export
confTomlDefs, confTomlNet, confEnvvarDefs, confEnvvarNet, ProtectedShard,
DefaultMaxWakuMessageSizeStr, DefaultAgentString
logScope:
topics = "waku cli args"
# 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
type StartUpCommand* = enum
noCommand # default, runs waku
generateRlnKeystore # generates a new RLN keystore
type WakuMode* {.pure.} = enum
noMode # default - use explicit CLI flags as-is
Core # full service node
Edge # client-only node
type WakuNodeConf* = object
configFile* {.
desc: "Loads configuration from a TOML file (cmd-line parameters take precedence)",
name: "config-file"
.}: Option[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
# 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 " &
$DefaultRlnRelayUserMessageLimit & ".",
defaultValue: none(uint64),
name: "rln-relay-user-message-limit"
.}: 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 " &
$DefaultRlnRelayEpochSizeSec & " second.",
defaultValue: none(uint64),
name: "rln-relay-epoch-sec"
.}: Option[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
mode* {.
desc:
"Node operation mode. 'Core' enables relay+service protocols. 'Edge' enables client-only protocols. Default: explicit CLI flags used.",
defaultValue: WakuMode.noMode,
name: "mode"
.}: WakuMode
preset* {.
desc:
"Network preset to use. 'twn' is The RLN-protected Waku Network (cluster 1). 'logos.dev' is the Logos Dev Network (cluster 2). '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
# Option-typed; desc states the default since the CLI can't auto-show it for none().
clusterId* {.
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"
.}: Option[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".}:
Option[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"
.}: Option[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",
defaultValue: DefaultCLIRelay,
name: "relay"
.}: 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
# 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. Default is " &
$DefaultRlnRelayEnabled & ".",
defaultValue: none(bool),
name: "rln-relay"
.}: Option[bool]
rlnRelayCredIndex* {.
desc: "the index of the onchain commitment to use",
name: "rln-relay-membership-index"
.}: Option[uint]
rlnRelayDynamic* {.
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false.",
defaultValue: none(bool),
name: "rln-relay-dynamic"
.}: Option[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", defaultValue: false, name: "store"
.}: 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", defaultValue: false, name: "filter"
.}: 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",
defaultValue: false,
name: "lightpush"
.}: bool
lightpushnode* {.
desc: "Peer multiaddr to request lightpush of published messages.",
defaultValue: "",
name: "lightpushnode"
.}: 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. Default is """ &
$DefaultP2pReliability & ".",
defaultValue: none(bool),
name: "reliability"
.}: Option[bool]
## 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
# 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. Default is " &
$DefaultDiscv5Enabled & ".",
defaultValue: none(bool),
name: "discv5-discovery"
.}: Option[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",
defaultValue: DefaultCLIPeerExchange,
name: "peer-exchange"
.}: 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",
defaultValue: DefaultCLIRendezvous,
name: "rendezvous"
.}: bool
#Mix config
# 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:
"ED25519 private key as 64 char hex string , without 0x. If not provided, a random key will be generated.",
name: "mixkey"
.}: Option[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
# 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. Default is " &
$DefaultKadEnabled & ".",
defaultValue: none(bool),
name: "enable-kad-discovery"
.}: Option[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
## Parsing
# NOTE: Keys are different in nim-libp2p
proc parseCmdArg*(T: type crypto.PrivateKey, p: string): T =
try:
let key = SkPrivateKey.init(utils.fromHex(p)).tryGet()
crypto.PrivateKey(scheme: Secp256k1, skkey: key)
except CatchableError:
raise newException(ValueError, "Invalid private key")
proc parseCmdArg*[T](_: type seq[T], s: string): seq[T] {.raises: [ValueError].} =
var
inputSeq: JsonNode
res: seq[T] = @[]
try:
inputSeq = s.parseJson()
except Exception:
raise newException(ValueError, fmt"Could not parse sequence: {s}")
for entry in inputSeq:
let formattedString = ($entry).strip(chars = {'\"'})
res.add(parseCmdArg(T, formattedString))
return res
proc completeCmdArg*(T: type crypto.PrivateKey, val: string): seq[string] =
return @[]
# TODO: Remove when removing protected-topic configuration
proc isNumber(x: string): bool =
try:
discard parseInt(x)
result = true
except ValueError:
result = false
proc parseCmdArg*(T: type MixNodePubInfo, p: string): T =
let elements = p.split(":")
if elements.len != 2:
raise newException(
ValueError, "Invalid format for mix node expected multiaddr:mixPublicKey"
)
let multiaddr = MultiAddress.init(elements[0]).valueOr:
raise newException(ValueError, "Invalid multiaddress format")
if not multiaddr.contains(multiCodec("ip4")).get():
raise newException(
ValueError, "Invalid format for ip address, expected a ipv4 multiaddress"
)
return MixNodePubInfo(
multiaddr: elements[0], pubKey: intoCurve25519Key(ncrutils.fromHex(elements[1]))
)
proc parseCmdArg*(T: type ProtectedShard, p: string): T =
let elements = p.split(":")
if elements.len != 2:
raise newException(
ValueError, "Invalid format for protected shard expected shard:publickey"
)
let publicKey = secp256k1.SkPublicKey.fromHex(elements[1]).valueOr:
raise newException(ValueError, "Invalid public key")
if isNumber(elements[0]):
return ProtectedShard(shard: uint16.parseCmdArg(elements[0]), key: publicKey)
# TODO: Remove when removing protected-topic configuration
let shard = RelayShard.parse(elements[0]).valueOr:
raise newException(
ValueError,
"Invalid pubsub topic. Pubsub topics must be in the format /waku/2/rs/<cluster-id>/<shard-id>",
)
return ProtectedShard(shard: shard.shardId, key: publicKey)
proc completeCmdArg*(T: type ProtectedShard, val: string): seq[string] =
return @[]
proc completeCmdArg*(T: type IpAddress, val: string): seq[string] =
return @[]
proc defaultListenAddress*(): IpAddress =
# TODO: Should probably listen on both ipv4 and ipv6 by default.
(static IpAddress(family: IpAddressFamily.IPv4, address_v4: [0'u8, 0, 0, 0]))
proc defaultColocationLimit*(): int =
return DefaultColocationLimit
proc completeCmdArg*(T: type Port, val: string): seq[string] =
return @[]
proc completeCmdArg*(T: type EthRpcUrl, val: string): seq[string] =
return @[]
proc parseCmdArg*(T: type EthRpcUrl, s: string): T =
## allowed patterns:
## http://url:port
## https://url:port
## http://url:port/path
## https://url:port/path
## http://url/with/path
## http://url:port/path?query
## https://url:port/path?query
## https://username:password@url:port/path
## https://username:password@url:port/path?query
## supports IPv4, IPv6, URL-encoded credentials
## disallowed patterns:
## any valid/invalid ws or wss url
var httpPattern =
re2"^(https?):\/\/(([^\s:@]*(?:%[0-9A-Fa-f]{2})*):([^\s:@]*(?:%[0-9A-Fa-f]{2})*)@)?((?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|\[[0-9a-fA-F:]+\])(?::([0-9]{1,5}))?(\/[^\s?#]*)?(\?[^\s#]*)?(#[^\s]*)?$"
var wsPattern =
re2"^(wss?):\/\/([\w-]+(\.[\w-]+)+)(:[0-9]{1,5})?(\/[\w.,@?^=%&:\/~+#-]*)?$"
if regex.match(s, wsPattern):
raise newException(
ValueError, "Websocket RPC URL is not supported, Please use an HTTP URL"
)
if not regex.match(s, httpPattern):
raise newException(ValueError, "Invalid HTTP RPC URL")
return EthRpcUrl(s)
## Load
proc readValue*(
r: var TomlReader, value: var crypto.PrivateKey
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(crypto.PrivateKey, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc readValue*(
r: var EnvvarReader, value: var crypto.PrivateKey
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(crypto.PrivateKey, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc readValue*(
r: var TomlReader, value: var MixNodePubInfo
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(MixNodePubInfo, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc readValue*(
r: var EnvvarReader, value: var MixNodePubInfo
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(MixNodePubInfo, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc readValue*(
r: var TomlReader, value: var ProtectedShard
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(ProtectedShard, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc readValue*(
r: var EnvvarReader, value: var ProtectedShard
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(ProtectedShard, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc readValue*(
r: var TomlReader, value: var EthRpcUrl
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(EthRpcUrl, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc readValue*(
r: var EnvvarReader, value: var EthRpcUrl
) {.raises: [SerializationError].} =
try:
value = parseCmdArg(EthRpcUrl, r.readValue(string))
except CatchableError:
raise newException(SerializationError, getCurrentExceptionMsg())
proc load*(T: type WakuNodeConf, version = ""): ConfResult[T] =
try:
let conf = WakuNodeConf.load(
version = version,
secondarySources = proc(
conf: WakuNodeConf, 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 defaultWakuNodeConf*(): ConfResult[WakuNodeConf] =
try:
let conf = WakuNodeConf.load(version = "", cmdLine = @[])
return ok(conf)
except CatchableError:
return err("exception in defaultWakuNodeConf: " & getCurrentExceptionMsg())
proc toKeystoreGeneratorConf*(n: WakuNodeConf): RlnKeystoreGeneratorConf =
RlnKeystoreGeneratorConf(
execute: n.execute,
chainId: UInt256.fromBytesBE(n.rlnRelayChainId.toBytesBE()),
ethClientUrls: n.ethClientUrls.mapIt(string(it)),
ethContractAddress: n.rlnRelayEthContractAddress,
userMessageLimit: n.rlnRelayUserMessageLimit.get(DefaultRlnRelayUserMessageLimit),
ethPrivateKey: n.rlnRelayEthPrivateKey,
credPath: n.rlnRelayCredPath,
credPassword: n.rlnRelayCredPassword,
)
proc toNetworkPresetConf(
preset: string, clusterId: Option[uint16]
): ConfResult[Option[NetworkPresetConf]] =
var lcPreset = toLowerAscii(preset)
if clusterId.isSome() and clusterId.get() == 1:
warn(
"TWN - The Waku Network configuration will not be applied when `--cluster-id=1` is passed in future releases. Use `--preset=twn` instead."
)
lcPreset = "twn"
if clusterId.isSome() and clusterId.get() == 2:
warn(
"Logos.dev - Logos.dev configuration will not be applied when `--cluster-id=2` is passed in future releases. Use `--preset=logos.dev` instead."
)
lcPreset = "logos.dev"
case lcPreset
of "":
ok(none(NetworkPresetConf))
of "twn":
ok(some(NetworkPresetConf.TheWakuNetworkConf()))
of "logos.dev", "logosdev":
ok(some(NetworkPresetConf.LogosDevConf()))
of "logos.test", "logostest":
ok(some(NetworkPresetConf.LogosTestConf()))
of "status.prod", "statusprod":
ok(some(NetworkPresetConf.StatusProdConf()))
else:
err("Invalid --preset value passed: " & lcPreset)
proc toWakuConf*(n: WakuNodeConf): 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)
b.withRelay(n.relay)
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)
b.storeServiceConf.withEnabled(n.store)
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 `Option` 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())
b.filterServiceConf.withEnabled(n.filter)
b.filterServiceConf.withSubscriptionTimeout(n.filterSubscriptionTimeout)
b.filterServiceConf.withMaxPeersToServe(n.filterMaxPeersToServe)
b.filterServiceConf.withMaxCriteria(n.filterMaxCriteria)
b.withLightPush(n.lightpush)
if n.reliabilityEnabled.isSome():
b.withP2pReliability(n.reliabilityEnabled.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)
b.withPeerExchange(n.peerExchange)
b.withRendezvous(n.rendezvous)
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)
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)
)
# Mode-driven configuration overrides
case n.mode
of WakuMode.Core:
b.withRelay(true)
b.filterServiceConf.withEnabled(true)
b.withLightPush(true)
b.discv5Conf.withEnabled(true)
b.withPeerExchange(true)
b.withRendezvous(true)
b.rateLimitConf.withRateLimitsIfNotAssigned(
@["filter:100/1s", "lightpush:5/1s", "px:5/1s"]
)
of WakuMode.Edge:
b.withPeerExchange(true)
b.withRelay(false)
b.filterServiceConf.withEnabled(false)
b.withLightPush(false)
b.storeServiceConf.withEnabled(false)
of WakuMode.noMode:
discard # use explicit CLI flags as-is
return b.build()