mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-01-05 23:43:07 +00:00
1059 lines
34 KiB
Nim
1059 lines
34 KiB
Nim
import
|
|
libp2p/crypto/crypto,
|
|
libp2p/multiaddress,
|
|
std/[macros, net, options, sequtils, strutils],
|
|
chronicles,
|
|
results
|
|
|
|
import
|
|
./waku_conf,
|
|
./networks_config,
|
|
../common/logging,
|
|
../common/utils/parse_size_units,
|
|
../waku_enr/capabilities
|
|
|
|
logScope:
|
|
topics = "waku conf builder"
|
|
|
|
proc generateWithProc(builderType, argName, argType, fromType: NimNode): NimNode =
|
|
builderType.expectKind nnkIdent
|
|
argName.expectKind nnkIdent
|
|
|
|
result = newStmtList()
|
|
|
|
let procName = ident("with" & capitalizeAscii($argName))
|
|
let builderIdent = ident("builder")
|
|
let builderVar = newDotExpr(builderIdent, ident($argName))
|
|
let resVar = ident($argName)
|
|
|
|
if argType == fromType:
|
|
result.add quote do:
|
|
proc `procName`*(`builderIdent`: var `builderType`, `resVar`: `argType`) =
|
|
`builderVar` = some(`argName`)
|
|
|
|
else:
|
|
result.add quote do:
|
|
proc `procName`*(`builderIdent`: var `builderType`, `resVar`: `fromType`) =
|
|
`builderVar` = some(`argName`.`argType`)
|
|
|
|
## A simple macro to set a property on the builder.
|
|
## For example:
|
|
##
|
|
## ```
|
|
## with(RlnRelayConfbuilder, rlnRelay, bool)
|
|
## ```
|
|
##
|
|
## Generates
|
|
##
|
|
## ```
|
|
## proc withRlnRelay*(builder: var RlnRelayConfBuilder, rlnRelay: bool) =
|
|
## builder.rlnRelay = some(rlnRelay)
|
|
## ```
|
|
macro with(builderType: untyped, argName: untyped, argType: untyped) =
|
|
result = generateWithProc(builderType, argName, argType, argType)
|
|
|
|
## A simple macro to set a property on the builder, and convert the property
|
|
## to the right type.
|
|
##
|
|
## For example:
|
|
##
|
|
## ```
|
|
## with(WakuConfBuilder, p2pPort, Port, uint16)
|
|
## ```
|
|
##
|
|
## Generates
|
|
##
|
|
## ```
|
|
## proc withp2pPort*(builder: var WakuConfBuilStoreServder, p2pPort: uint16) =
|
|
## builder.p2pPort = some(p2pPort.Port)
|
|
## ```
|
|
macro with(
|
|
builderType: untyped, argName: untyped, argType: untyped, fromType: untyped
|
|
) =
|
|
result = generateWithProc(builderType, argName, argType, fromType)
|
|
|
|
##############################
|
|
## RLN Relay Config Builder ##
|
|
##############################
|
|
type RlnRelayConfBuilder = object
|
|
enabled: Option[bool]
|
|
|
|
chainId: Option[uint]
|
|
ethClientAddress: Option[string]
|
|
ethContractAddress: Option[string]
|
|
credIndex: Option[uint]
|
|
credPassword: Option[string]
|
|
credPath: Option[string]
|
|
dynamic: Option[bool]
|
|
epochSizeSec: Option[uint64]
|
|
userMessageLimit: Option[uint64]
|
|
treePath: Option[string]
|
|
|
|
proc init*(T: type RlnRelayConfBuilder): RlnRelayConfBuilder =
|
|
RlnRelayConfBuilder()
|
|
|
|
with(RlnRelayConfbuilder, enabled, bool)
|
|
|
|
with(RlnRelayConfBuilder, chainId, uint)
|
|
with(RlnRelayConfBuilder, credIndex, uint)
|
|
with(RlnRelayConfBuilder, credPassword, string)
|
|
with(RlnRelayConfBuilder, credPath, string)
|
|
with(RlnRelayConfBuilder, dynamic, bool)
|
|
with(RlnRelayConfBuilder, ethClientAddress, string)
|
|
with(RlnRelayConfBuilder, ethContractAddress, string)
|
|
with(RlnRelayConfBuilder, epochSizeSec, uint64)
|
|
with(RlnRelayConfBuilder, userMessageLimit, uint64)
|
|
with(RlnRelayConfBuilder, treePath, string)
|
|
|
|
proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(RlnRelayConf))
|
|
|
|
if b.chainId.isNone():
|
|
return err("RLN Relay Chain Id is not specified")
|
|
|
|
let creds =
|
|
if b.credPath.isSome() and b.credPassword.isSome():
|
|
some(RlnRelayCreds(path: b.credPath.get(), password: b.credPassword.get()))
|
|
elif b.credPath.isSome() and b.credPassword.isNone():
|
|
return err("RLN Relay Credential Password is not specified but path is")
|
|
elif b.credPath.isNone() and b.credPassword.isSome():
|
|
return err("RLN Relay Credential Path is not specified but password is")
|
|
else:
|
|
none(RlnRelayCreds)
|
|
|
|
if b.dynamic.isNone():
|
|
return err("rlnRelay.dynamic is not specified")
|
|
if b.ethClientAddress.get("") == "":
|
|
return err("rlnRelay.ethClientAddress 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")
|
|
if b.treePath.isNone():
|
|
return err("rlnRelay.treePath is not specified")
|
|
|
|
return ok(
|
|
some(
|
|
RlnRelayConf(
|
|
chainId: b.chainId.get(),
|
|
credIndex: b.credIndex,
|
|
creds: creds,
|
|
dynamic: b.dynamic.get(),
|
|
ethClientAddress: b.ethClientAddress.get(),
|
|
ethContractAddress: b.ethContractAddress.get(),
|
|
epochSizeSec: b.epochSizeSec.get(),
|
|
userMessageLimit: b.userMessageLimit.get(),
|
|
treePath: b.treePath.get(),
|
|
)
|
|
)
|
|
)
|
|
|
|
###################################
|
|
## Filter Service Config Builder ##
|
|
###################################
|
|
type FilterServiceConfBuilder = object
|
|
enabled: Option[bool]
|
|
maxPeersToServe: Option[uint32]
|
|
subscriptionTimeout: Option[uint16]
|
|
maxCriteria: Option[uint32]
|
|
|
|
proc init(T: type FilterServiceConfBuilder): FilterServiceConfBuilder =
|
|
FilterServiceConfBuilder()
|
|
|
|
with(FilterServiceConfBuilder, enabled, bool)
|
|
with(FilterServiceConfBuilder, maxPeersToServe, uint32)
|
|
with(FilterServiceConfBuilder, subscriptionTimeout, uint16)
|
|
with(FilterServiceConfBuilder, maxCriteria, uint32)
|
|
|
|
proc build(b: FilterServiceConfBuilder): Result[Option[FilterServiceConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(FilterServiceConf))
|
|
|
|
return ok(
|
|
some(
|
|
FilterServiceConf(
|
|
maxPeersToServe: b.maxPeersToServe.get(500),
|
|
subscriptionTimeout: b.subscriptionTimeout.get(300),
|
|
maxCriteria: b.maxCriteria.get(1000),
|
|
)
|
|
)
|
|
)
|
|
|
|
##################################
|
|
## Store Sync Config Builder ##
|
|
##################################
|
|
type StoreSyncConfBuilder = object
|
|
enabled: Option[bool]
|
|
|
|
rangeSec: Option[uint32]
|
|
intervalSec: Option[uint32]
|
|
relayJitterSec: Option[uint32]
|
|
|
|
proc init(T: type StoreSyncConfBuilder): StoreSyncConfBuilder =
|
|
StoreSyncConfBuilder()
|
|
|
|
with(StoreSyncConfBuilder, enabled, bool)
|
|
with(StoreSyncConfBuilder, rangeSec, uint32)
|
|
with(StoreSyncConfBuilder, intervalSec, uint32)
|
|
with(StoreSyncConfBuilder, relayJitterSec, uint32)
|
|
|
|
proc build(b: StoreSyncConfBuilder): Result[Option[StoreSyncConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(StoreSyncConf))
|
|
|
|
if b.rangeSec.isNone():
|
|
return err "store.rangeSec is not specified"
|
|
if b.intervalSec.isNone():
|
|
return err "store.intervalSec is not specified"
|
|
if b.relayJitterSec.isNone():
|
|
return err "store.relayJitterSec is not specified"
|
|
|
|
return ok(
|
|
some(
|
|
StoreSyncConf(
|
|
rangeSec: b.rangeSec.get(),
|
|
intervalSec: b.intervalSec.get(),
|
|
relayJitterSec: b.relayJitterSec.get(),
|
|
)
|
|
)
|
|
)
|
|
|
|
##################################
|
|
## Store Service Config Builder ##
|
|
##################################
|
|
type StoreServiceConfBuilder = object
|
|
enabled: Option[bool]
|
|
|
|
dbMigration: Option[bool]
|
|
dbURl: Option[string]
|
|
dbVacuum: Option[bool]
|
|
legacy: Option[bool]
|
|
maxNumDbConnections: Option[int]
|
|
retentionPolicy: Option[string]
|
|
resume: Option[bool]
|
|
storeSyncConf*: StoreSyncConfBuilder
|
|
|
|
proc init(T: type StoreServiceConfBuilder): StoreServiceConfBuilder =
|
|
StoreServiceConfBuilder(storeSyncConf: StoreSyncConfBuilder.init())
|
|
|
|
with(StoreServiceConfBuilder, enabled, bool)
|
|
with(StoreServiceConfBuilder, dbMigration, bool)
|
|
with(StoreServiceConfBuilder, dbURl, string)
|
|
with(StoreServiceConfBuilder, dbVacuum, bool)
|
|
with(StoreServiceConfBuilder, legacy, bool)
|
|
with(StoreServiceConfBuilder, maxNumDbConnections, int)
|
|
with(StoreServiceConfBuilder, retentionPolicy, string)
|
|
with(StoreServiceConfBuilder, resume, bool)
|
|
|
|
proc build(b: StoreServiceConfBuilder): Result[Option[StoreServiceConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(StoreServiceConf))
|
|
|
|
if b.dbMigration.isNone():
|
|
return err "store.dbMigration is not specified"
|
|
if b.dbUrl.get("") == "":
|
|
return err "store.dbUrl is not specified"
|
|
if b.dbVacuum.isNone():
|
|
return err "store.dbVacuum is not specified"
|
|
if b.legacy.isNone():
|
|
return err "store.legacy is not specified"
|
|
if b.maxNumDbConnections.isNone():
|
|
return err "store.maxNumDbConnections is not specified"
|
|
if b.retentionPolicy.get("") == "":
|
|
return err "store.retentionPolicy is not specified"
|
|
if b.resume.isNone():
|
|
return err "store.resume is not specified"
|
|
|
|
let storeSyncConf = b.storeSyncConf.build().valueOr:
|
|
return err("Store Sync Conf failed to build")
|
|
|
|
return ok(
|
|
some(
|
|
StoreServiceConf(
|
|
dbMigration: b.dbMigration.get(),
|
|
dbURl: b.dbUrl.get(),
|
|
dbVacuum: b.dbVacuum.get(),
|
|
legacy: b.legacy.get(),
|
|
maxNumDbConnections: b.maxNumDbConnections.get(),
|
|
retentionPolicy: b.retentionPolicy.get(),
|
|
resume: b.resume.get(),
|
|
storeSyncConf: storeSyncConf,
|
|
)
|
|
)
|
|
)
|
|
|
|
################################
|
|
## REST Server Config Builder ##
|
|
################################
|
|
type RestServerConfBuilder = object
|
|
enabled: Option[bool]
|
|
|
|
allowOrigin: seq[string]
|
|
listenAddress: Option[IpAddress]
|
|
port: Option[Port]
|
|
admin: Option[bool]
|
|
relayCacheCapacity: Option[uint32]
|
|
|
|
proc init(T: type RestServerConfBuilder): RestServerConfBuilder =
|
|
RestServerConfBuilder()
|
|
|
|
proc withAllowOrigin*(builder: var RestServerConfBuilder, allowOrigin: seq[string]) =
|
|
builder.allowOrigin = concat(builder.allowOrigin, allowOrigin)
|
|
|
|
with(RestServerConfBuilder, enabled, bool)
|
|
with(RestServerConfBuilder, listenAddress, IpAddress)
|
|
with(RestServerConfBuilder, port, Port, uint16)
|
|
with(RestServerConfBuilder, admin, bool)
|
|
with(RestServerConfBuilder, relayCacheCapacity, uint32)
|
|
|
|
proc build(b: RestServerConfBuilder): Result[Option[RestServerConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(RestServerConf))
|
|
|
|
if b.listenAddress.isNone():
|
|
return err("restServer.listenAddress is not specified")
|
|
if b.port.isNone():
|
|
return err("restServer.port is not specified")
|
|
if b.relayCacheCapacity.isNone():
|
|
return err("restServer.relayCacheCapacity is not specified")
|
|
|
|
return ok(
|
|
some(
|
|
RestServerConf(
|
|
allowOrigin: b.allowOrigin,
|
|
listenAddress: b.listenAddress.get(),
|
|
port: b.port.get(),
|
|
admin: b.admin.get(false),
|
|
relayCacheCapacity: b.relayCacheCapacity.get(),
|
|
)
|
|
)
|
|
)
|
|
|
|
##################################
|
|
## DNS Discovery Config Builder ##
|
|
##################################
|
|
type DnsDiscoveryConfBuilder = object
|
|
enabled: Option[bool]
|
|
enrTreeUrl: Option[string]
|
|
nameServers: seq[IpAddress]
|
|
|
|
proc init(T: type DnsDiscoveryConfBuilder): DnsDiscoveryConfBuilder =
|
|
DnsDiscoveryConfBuilder()
|
|
|
|
with(DnsDiscoveryConfBuilder, enabled, bool)
|
|
with(DnsDiscoveryConfBuilder, enrTreeUrl, string)
|
|
|
|
proc withNameServers*(b: var DnsDiscoveryConfBuilder, nameServers: seq[IpAddress]) =
|
|
b.nameServers = concat(b.nameServers, nameServers)
|
|
|
|
proc build(b: DnsDiscoveryConfBuilder): Result[Option[DnsDiscoveryConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(DnsDiscoveryConf))
|
|
|
|
if b.nameServers.len == 0:
|
|
return err("dnsDiscovery.nameServers is not specified")
|
|
if b.enrTreeUrl.isNone():
|
|
return err("dnsDiscovery.enrTreeUrl is not specified")
|
|
|
|
return ok(
|
|
some(DnsDiscoveryConf(nameServers: b.nameServers, enrTreeUrl: b.enrTreeUrl.get()))
|
|
)
|
|
|
|
#################################
|
|
## AutoSharding Config Builder ##
|
|
#################################
|
|
type AutoShardingConfBuilder = object
|
|
enabled: Option[bool]
|
|
numShardsInNetwork*: Option[uint16]
|
|
contentTopics*: seq[string]
|
|
|
|
proc init(T: type AutoShardingConfBuilder): AutoShardingConfBuilder =
|
|
AutoShardingConfBuilder()
|
|
|
|
with(AutoShardingConfBuilder, enabled, bool)
|
|
|
|
proc build(b: AutoShardingConfBuilder): Result[Option[AutoShardingConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(AutoShardingConf))
|
|
|
|
if b.numShardsInNetwork.isNone:
|
|
return err("autoSharding.numShardsInNetwork is not specified")
|
|
|
|
return ok(
|
|
some(
|
|
AutoShardingConf(
|
|
numShardsInNetwork: b.numShardsInNetwork.get(), contentTopics: b.contentTopics
|
|
)
|
|
)
|
|
)
|
|
|
|
###########################
|
|
## Discv5 Config Builder ##
|
|
###########################
|
|
type Discv5ConfBuilder = object
|
|
enabled: Option[bool]
|
|
|
|
bootstrapNodes: seq[string]
|
|
bitsPerHop: Option[int]
|
|
bucketIpLimit: Option[uint]
|
|
discv5Only: Option[bool]
|
|
enrAutoUpdate: Option[bool]
|
|
tableIpLimit: Option[uint]
|
|
udpPort: Option[Port]
|
|
|
|
proc init(T: type Discv5ConfBuilder): Discv5ConfBuilder =
|
|
Discv5ConfBuilder()
|
|
|
|
with(Discv5ConfBuilder, enabled, bool)
|
|
with(Discv5ConfBuilder, bitsPerHop, int)
|
|
with(Discv5ConfBuilder, bucketIpLimit, uint)
|
|
with(Discv5ConfBuilder, discv5Only, bool)
|
|
with(Discv5ConfBuilder, enrAutoUpdate, bool)
|
|
with(Discv5ConfBuilder, tableIpLimit, uint)
|
|
with(Discv5ConfBuilder, udpPort, Port, uint16)
|
|
with(Discv5ConfBuilder, udpPort, Port)
|
|
|
|
proc withBootstrapNodes*(builder: var Discv5ConfBuilder, bootstrapNodes: seq[string]) =
|
|
# TODO: validate ENRs?
|
|
builder.bootstrapNodes = concat(builder.bootstrapNodes, bootstrapNodes)
|
|
|
|
proc build(b: Discv5ConfBuilder): Result[Option[Discv5Conf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(Discv5Conf))
|
|
|
|
# Discv5 is useless without bootstrap nodes
|
|
if b.bootstrapNodes.len == 0:
|
|
return err("dicv5.bootstrapNodes is not specified")
|
|
|
|
return ok(
|
|
some(
|
|
Discv5Conf(
|
|
bootstrapNodes: b.bootstrapNodes,
|
|
bitsPerHop: b.bitsPerHop.get(1),
|
|
bucketIpLimit: b.bucketIpLimit.get(2),
|
|
discv5Only: b.discv5Only.get(false),
|
|
enrAutoUpdate: b.enrAutoUpdate.get(true),
|
|
tableIpLimit: b.tableIpLimit.get(10),
|
|
udpPort: b.udpPort.get(9000.Port),
|
|
)
|
|
)
|
|
)
|
|
|
|
##############################
|
|
## WebSocket Config Builder ##
|
|
##############################
|
|
type WebSocketConfBuilder* = object
|
|
enabled: Option[bool]
|
|
webSocketPort: Option[Port]
|
|
secureEnabled: Option[bool]
|
|
keyPath: Option[string]
|
|
certPath: Option[string]
|
|
|
|
proc init*(T: type WebSocketConfBuilder): WebSocketConfBuilder =
|
|
WebSocketConfBuilder()
|
|
|
|
with(WebSocketConfBuilder, enabled, bool)
|
|
with(WebSocketConfBuilder, secureEnabled, bool)
|
|
with(WebSocketConfBuilder, webSocketPort, Port)
|
|
with(WebSocketConfBuilder, webSocketPort, Port, uint16)
|
|
with(WebSocketConfBuilder, keyPath, string)
|
|
with(WebSocketConfBuilder, certPath, string)
|
|
|
|
proc build(b: WebSocketConfBuilder): Result[Option[WebSocketConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(WebSocketConf))
|
|
|
|
if b.webSocketPort.isNone():
|
|
return err("websocket.port is not specified")
|
|
|
|
if not b.secureEnabled.get(false):
|
|
return ok(
|
|
some(
|
|
WebSocketConf(
|
|
port: b.websocketPort.get(), secureConf: none(WebSocketSecureConf)
|
|
)
|
|
)
|
|
)
|
|
|
|
if b.keyPath.get("") == "":
|
|
return err("WebSocketSecure enabled but key path is not specified")
|
|
if b.certPath.get("") == "":
|
|
return err("WebSocketSecure enabled but cert path is not specified")
|
|
|
|
return ok(
|
|
some(
|
|
WebSocketConf(
|
|
port: b.webSocketPort.get(),
|
|
secureConf: some(
|
|
WebSocketSecureConf(keyPath: b.keyPath.get(), certPath: b.certPath.get())
|
|
),
|
|
)
|
|
)
|
|
)
|
|
|
|
###################################
|
|
## Metrics Server Config Builder ##
|
|
###################################
|
|
type MetricsServerConfBuilder = object
|
|
enabled: Option[bool]
|
|
|
|
httpAddress: Option[IpAddress]
|
|
httpPort: Option[Port]
|
|
logging: Option[bool]
|
|
|
|
proc init(T: type MetricsServerConfBuilder): MetricsServerConfBuilder =
|
|
MetricsServerConfBuilder()
|
|
|
|
with(MetricsServerConfBuilder, enabled, bool)
|
|
with(MetricsServerConfBuilder, httpAddress, IpAddress)
|
|
with(MetricsServerConfBuilder, httpPort, Port, uint16)
|
|
with(MetricsServerConfBuilder, logging, bool)
|
|
|
|
proc build(b: MetricsServerConfBuilder): Result[Option[MetricsServerConf], string] =
|
|
if not b.enabled.get(false):
|
|
return ok(none(MetricsServerConf))
|
|
|
|
return ok(
|
|
some(
|
|
MetricsServerConf(
|
|
httpAddress: b.httpAddress.get(parseIpAddress("127.0.0.1")),
|
|
httpPort: b.httpPort.get(8008.Port),
|
|
logging: b.logging.get(false),
|
|
)
|
|
)
|
|
)
|
|
|
|
type MaxMessageSizeKind = enum
|
|
mmskNone
|
|
mmskStr
|
|
mmskInt
|
|
|
|
type MaxMessageSize = object
|
|
case kind: MaxMessageSizeKind
|
|
of mmskNone:
|
|
discard
|
|
of mmskStr:
|
|
str: string
|
|
of mmskInt:
|
|
bytes: uint64
|
|
|
|
## `WakuConfBuilder` is a convenient tool to accumulate
|
|
## Config parameters to build a `WakuConfig`.
|
|
## It provides some type conversion, as well as applying
|
|
## defaults in an agnostic manner (for any usage of Waku node)
|
|
#
|
|
# TODO: Sub protocol builder (eg `StoreServiceConfBuilder`
|
|
# is be better defined in the protocol module (eg store)
|
|
# and apply good defaults from this protocol PoV and make the
|
|
# decision when the dev must specify a value vs when a default
|
|
# is fine to have.
|
|
#
|
|
# TODO: Add default to most values so that when a developer uses
|
|
# the builder, it works out-of-the-box
|
|
type WakuConfBuilder* = object
|
|
nodeKey: Option[PrivateKey]
|
|
|
|
clusterId: Option[uint16]
|
|
|
|
shards: Option[seq[uint16]]
|
|
protectedShards: Option[seq[ProtectedShard]]
|
|
contentTopics: Option[seq[string]]
|
|
|
|
# Conf builders
|
|
autoShardingConf*: AutoShardingConfBuilder
|
|
dnsDiscoveryConf*: DnsDiscoveryConfBuilder
|
|
discv5Conf*: Discv5ConfBuilder
|
|
filterServiceConf*: FilterServiceConfBuilder
|
|
metricsServerConf*: MetricsServerConfBuilder
|
|
restServerConf*: RestServerConfBuilder
|
|
rlnRelayConf*: RlnRelayConfBuilder
|
|
storeServiceConf*: StoreServiceConfBuilder
|
|
webSocketConf*: WebSocketConfBuilder
|
|
# End conf builders
|
|
relay: Option[bool]
|
|
lightPush: Option[bool]
|
|
peerExchange: Option[bool]
|
|
storeSync: Option[bool]
|
|
relayPeerExchange: Option[bool]
|
|
# TODO: move within a relayConf
|
|
rendezvous: Option[bool]
|
|
discv5Only: Option[bool]
|
|
|
|
clusterConf: Option[ClusterConf]
|
|
|
|
staticNodes: seq[string]
|
|
|
|
remoteStoreNode: Option[string]
|
|
remoteLightPushNode: Option[string]
|
|
remoteFilterNode: Option[string]
|
|
remotePeerExchangeNode: Option[string]
|
|
|
|
maxMessageSize: MaxMessageSize
|
|
|
|
logLevel: Option[logging.LogLevel]
|
|
logFormat: Option[logging.LogFormat]
|
|
|
|
natStrategy: Option[string]
|
|
|
|
p2pTcpPort: Option[Port]
|
|
p2pListenAddress: Option[IpAddress]
|
|
portsShift: Option[uint16]
|
|
dns4DomainName: Option[string]
|
|
extMultiAddrs: seq[string]
|
|
extMultiAddrsOnly: Option[bool]
|
|
|
|
dnsAddrs: Option[bool]
|
|
# TODO: Option of an array is probably silly, instead, offer concat utility with `with`
|
|
dnsAddrsNameServers: Option[seq[IpAddress]]
|
|
peerPersistence: Option[bool]
|
|
peerStoreCapacity: Option[int]
|
|
maxConnections: Option[int]
|
|
colocationLimit: Option[int]
|
|
|
|
agentString: Option[string]
|
|
|
|
rateLimits: Option[seq[string]]
|
|
|
|
maxRelayPeers: Option[int]
|
|
relayShardedPeerManagement: Option[bool]
|
|
relayServiceRatio: Option[string]
|
|
circuitRelayClient: Option[bool]
|
|
keepAlive: Option[bool]
|
|
p2pReliability: Option[bool]
|
|
|
|
proc init*(T: type WakuConfBuilder): WakuConfBuilder =
|
|
WakuConfBuilder(
|
|
autoShardingConf: AutoShardingConfBuilder.init(),
|
|
dnsDiscoveryConf: DnsDiscoveryConfBuilder.init(),
|
|
discv5Conf: Discv5ConfBuilder.init(),
|
|
filterServiceConf: FilterServiceConfBuilder.init(),
|
|
metricsServerConf: MetricsServerConfBuilder.init(),
|
|
restServerConf: RestServerConfBuilder.init(),
|
|
rlnRelayConf: RlnRelayConfBuilder.init(),
|
|
storeServiceConf: StoreServiceConfBuilder.init(),
|
|
webSocketConf: WebSocketConfBuilder.init(),
|
|
)
|
|
|
|
with(WakuConfBuilder, clusterConf, ClusterConf)
|
|
with(WakuConfBuilder, nodeKey, PrivateKey)
|
|
with(WakuConfBuilder, clusterId, uint16)
|
|
with(WakuConfBuilder, shards, seq[uint16])
|
|
with(WakuConfBuilder, protectedShards, seq[ProtectedShard])
|
|
with(WakuConfBuilder, relay, bool)
|
|
with(WakuConfBuilder, lightPush, bool)
|
|
with(WakuConfBuilder, storeSync, bool)
|
|
with(WakuConfBuilder, peerExchange, bool)
|
|
with(WakuConfBuilder, relayPeerExchange, bool)
|
|
with(WakuConfBuilder, rendezvous, bool)
|
|
with(WakuConfBuilder, remoteStoreNode, string)
|
|
with(WakuConfBuilder, remoteLightPushNode, string)
|
|
with(WakuConfBuilder, remoteFilterNode, string)
|
|
with(WakuConfBuilder, remotePeerExchangeNode, string)
|
|
with(WakuConfBuilder, dnsAddrs, bool)
|
|
with(WakuConfBuilder, peerPersistence, bool)
|
|
with(WakuConfBuilder, peerStoreCapacity, int)
|
|
with(WakuConfBuilder, maxConnections, int)
|
|
with(WakuConfBuilder, dnsAddrsNameServers, seq[IpAddress])
|
|
with(WakuConfBuilder, logLevel, logging.LogLevel)
|
|
with(WakuConfBuilder, logFormat, logging.LogFormat)
|
|
with(WakuConfBuilder, p2pTcpPort, Port)
|
|
with(WakuConfBuilder, p2pTcpPort, Port, uint16)
|
|
with(WakuConfBuilder, portsShift, uint16)
|
|
with(WakuConfBuilder, p2pListenAddress, IpAddress)
|
|
with(WakuConfBuilder, extMultiAddrsOnly, bool)
|
|
with(WakuConfBuilder, dns4DomainName, string)
|
|
with(WakuConfBuilder, natStrategy, string)
|
|
with(WakuConfBuilder, agentString, string)
|
|
with(WakuConfBuilder, colocationLimit, int)
|
|
with(WakuConfBuilder, rateLimits, seq[string])
|
|
with(WakuConfBuilder, maxRelayPeers, int)
|
|
with(WakuConfBuilder, relayServiceRatio, string)
|
|
with(WakuConfBuilder, circuitRelayClient, bool)
|
|
with(WakuConfBuilder, relayShardedPeerManagement, bool)
|
|
with(WakuConfBuilder, keepAlive, bool)
|
|
with(WakuConfBuilder, p2pReliability, bool)
|
|
|
|
proc withExtMultiAddrs*(builder: var WakuConfBuilder, extMultiAddrs: seq[string]) =
|
|
builder.extMultiAddrs = concat(builder.extMultiAddrs, extMultiAddrs)
|
|
|
|
proc withMaxMessageSize*(builder: var WakuConfBuilder, maxMessageSizeBytes: uint64) =
|
|
builder.maxMessageSize = MaxMessageSize(kind: mmskInt, bytes: maxMessageSizeBytes)
|
|
|
|
proc withMaxMessageSize*(builder: var WakuConfBuilder, maxMessageSize: string) =
|
|
builder.maxMessageSize = MaxMessageSize(kind: mmskStr, str: maxMessageSize)
|
|
|
|
proc withStaticNodes*(builder: var WakuConfBuilder, staticNodes: seq[string]) =
|
|
builder.staticNodes = concat(builder.staticNodes, staticNodes)
|
|
|
|
proc nodeKey(
|
|
builder: WakuConfBuilder, rng: ref HmacDrbgContext
|
|
): Result[PrivateKey, string] =
|
|
if builder.nodeKey.isSome():
|
|
return ok(builder.nodeKey.get())
|
|
else:
|
|
warn "missing node key, generating new set"
|
|
let nodeKey = crypto.PrivateKey.random(Secp256k1, rng[]).valueOr:
|
|
error "Failed to generate key", error = error
|
|
return err("Failed to generate key: " & $error)
|
|
return ok(nodeKey)
|
|
|
|
proc applyClusterConf(builder: var WakuConfBuilder) =
|
|
# Apply cluster conf - values passed manually override cluster conf
|
|
# Should be applied **first**, before individual values are pulled
|
|
if builder.clusterConf.isNone:
|
|
return
|
|
var clusterConf = builder.clusterConf.get()
|
|
|
|
if builder.clusterId.isNone:
|
|
builder.clusterId = some(clusterConf.clusterId)
|
|
else:
|
|
warn "Cluster id was manually provided alongside a cluster conf",
|
|
used = builder.clusterId, discarded = clusterConf.clusterId
|
|
|
|
# Apply relay parameters
|
|
if builder.relay.get(false) and clusterConf.rlnRelay:
|
|
if builder.rlnRelayConf.enabled.isNone:
|
|
builder.rlnRelayConf.withEnabled(true)
|
|
else:
|
|
warn "RLN Relay was manually provided alongside a cluster conf",
|
|
used = builder.rlnRelayConf.enabled, discarded = clusterConf.rlnRelay
|
|
|
|
if builder.rlnRelayConf.ethContractAddress.isNone:
|
|
builder.rlnRelayConf.withEthContractAddress(
|
|
clusterConf.rlnRelayEthContractAddress
|
|
)
|
|
else:
|
|
warn "RLN Relay ETH Contract Address was manually provided alongside a cluster conf",
|
|
used = builder.rlnRelayConf.ethContractAddress.get().string,
|
|
discarded = clusterConf.rlnRelayEthContractAddress.string
|
|
|
|
if builder.rlnRelayConf.chainId.isNone:
|
|
builder.rlnRelayConf.withChainId(clusterConf.rlnRelayChainId)
|
|
else:
|
|
warn "RLN Relay Chain Id was manually provided alongside a cluster conf",
|
|
used = builder.rlnRelayConf.chainId, discarded = clusterConf.rlnRelayChainId
|
|
|
|
if builder.rlnRelayConf.dynamic.isNone:
|
|
builder.rlnRelayConf.withDynamic(clusterConf.rlnRelayDynamic)
|
|
else:
|
|
warn "RLN Relay Dynamic was manually provided alongside a cluster conf",
|
|
used = builder.rlnRelayConf.dynamic, discarded = clusterConf.rlnRelayDynamic
|
|
|
|
if builder.rlnRelayConf.epochSizeSec.isNone:
|
|
builder.rlnRelayConf.withEpochSizeSec(clusterConf.rlnEpochSizeSec)
|
|
else:
|
|
warn "RLN Epoch Size in Seconds was manually provided alongside a cluster conf",
|
|
used = builder.rlnRelayConf.epochSizeSec,
|
|
discarded = clusterConf.rlnEpochSizeSec
|
|
|
|
if builder.rlnRelayConf.userMessageLimit.isNone:
|
|
builder.rlnRelayConf.withUserMessageLimit(clusterConf.rlnRelayUserMessageLimit)
|
|
else:
|
|
warn "RLN Relay Dynamic was manually provided alongside a cluster conf",
|
|
used = builder.rlnRelayConf.userMessageLimit,
|
|
discarded = clusterConf.rlnRelayUserMessageLimit
|
|
# End Apply relay parameters
|
|
|
|
case builder.maxMessageSize.kind
|
|
of mmskNone:
|
|
builder.withMaxMessageSize(parseCorrectMsgSize(clusterConf.maxMessageSize))
|
|
of mmskStr, mmskInt:
|
|
warn "Max Message Size was manually provided alongside a cluster conf",
|
|
used = $builder.maxMessageSize, discarded = clusterConf.maxMessageSize
|
|
|
|
if builder.autoShardingConf.numShardsInNetwork.isNone:
|
|
builder.autoShardingConf.numShardsInNetwork = clusterConf.numShardsInNetwork
|
|
else:
|
|
warn "Num Shards In Network was manually provided alongside a cluster conf",
|
|
used = builder.autoShardingConf.numShardsInNetwork,
|
|
discarded = clusterConf.numShardsInNetwork
|
|
|
|
if clusterConf.discv5Discovery:
|
|
if builder.discv5Conf.enabled.isNone:
|
|
builder.discv5Conf.withEnabled(clusterConf.discv5Discovery)
|
|
|
|
if builder.discv5Conf.bootstrapNodes.len == 0 and
|
|
clusterConf.discv5BootstrapNodes.len > 0:
|
|
builder.discv5Conf.withBootstrapNodes(clusterConf.discv5BootstrapNodes)
|
|
|
|
proc build*(
|
|
builder: var WakuConfBuilder, rng: ref HmacDrbgContext = crypto.newRng()
|
|
): Result[WakuConf, string] =
|
|
## Return a WakuConf that contains all mandatory parameters
|
|
## Applies some sane defaults that are applicable across any usage
|
|
## of libwaku. It aims to be agnostic so it does not apply a
|
|
## default when it is opinionated.
|
|
|
|
let relay =
|
|
if builder.relay.isSome():
|
|
builder.relay.get()
|
|
else:
|
|
warn "whether to mount relay is not specified, defaulting to not mounting"
|
|
false
|
|
|
|
let lightPush =
|
|
if builder.lightPush.isSome():
|
|
builder.lightPush.get()
|
|
else:
|
|
warn "whether to mount lightPush is not specified, defaulting to not mounting"
|
|
false
|
|
|
|
let peerExchange =
|
|
if builder.peerExchange.isSome():
|
|
builder.peerExchange.get()
|
|
else:
|
|
warn "whether to mount peerExchange is not specified, defaulting to not mounting"
|
|
false
|
|
|
|
let storeSync =
|
|
if builder.storeSync.isSome():
|
|
builder.storeSync.get()
|
|
else:
|
|
warn "whether to mount storeSync is not specified, defaulting to not mounting"
|
|
false
|
|
|
|
let rendezvous =
|
|
if builder.rendezvous.isSome():
|
|
builder.rendezvous.get()
|
|
else:
|
|
warn "whether to mount rendezvous is not specified, defaulting to not mounting"
|
|
false
|
|
|
|
let relayPeerExchange = builder.relayPeerExchange.get(false)
|
|
|
|
applyClusterConf(builder)
|
|
|
|
let nodeKey = ?nodeKey(builder, rng)
|
|
|
|
if builder.clusterId.isNone():
|
|
return err("Cluster Id was not specified")
|
|
|
|
let shards =
|
|
if builder.shards.isSome():
|
|
builder.shards.get()
|
|
elif builder.autoShardingConf.numShardsInNetwork.isSome():
|
|
info "shards not specified and auto-sharding is enabled via numShardsInNetwork, subscribing to all shards in network"
|
|
let upperShard: uint16 = builder.autoShardingConf.numShardsInNetwork.get() - 1
|
|
toSeq(0.uint16 .. upperShard)
|
|
else:
|
|
return err("")
|
|
|
|
let protectedShards = builder.protectedShards.get(@[])
|
|
|
|
let maxMessageSizeBytes =
|
|
case builder.maxMessageSize.kind
|
|
of mmskInt:
|
|
builder.maxMessageSize.bytes
|
|
of mmskStr:
|
|
?parseMsgSize(builder.maxMessageSize.str)
|
|
else:
|
|
warn "Max Message Size not specified, defaulting to 150KiB"
|
|
parseCorrectMsgSize("150KiB")
|
|
|
|
let contentTopics = builder.contentTopics.get(@[])
|
|
|
|
# Build sub-configs
|
|
let autoShardingConf = builder.autoShardingConf.build().valueOr:
|
|
return err("AutoSharding Conf building failed: " & $error)
|
|
|
|
let discv5Conf = builder.discv5Conf.build().valueOr:
|
|
return err("Discv5 Conf building failed: " & $error)
|
|
|
|
let dnsDiscoveryConf = builder.dnsDiscoveryConf.build().valueOr:
|
|
return err("DNS Discovery Conf building failed: " & $error)
|
|
|
|
let filterServiceConf = builder.filterServiceConf.build().valueOr:
|
|
return err("Filter Service Conf building failed: " & $error)
|
|
|
|
let metricsServerConf = builder.metricsServerConf.build().valueOr:
|
|
return err("Metrics Server Conf building failed: " & $error)
|
|
|
|
let restServerConf = builder.restServerConf.build().valueOr:
|
|
return err("REST Server Conf building failed: " & $error)
|
|
|
|
let rlnRelayConf = builder.rlnRelayConf.build().valueOr:
|
|
return err("RLN Relay Conf building failed: " & $error)
|
|
|
|
let storeServiceConf = builder.storeServiceConf.build().valueOr:
|
|
return err("Store Conf building failed: " & $error)
|
|
|
|
let webSocketConf = builder.webSocketConf.build().valueOr:
|
|
return err("WebSocket Conf building failed: " & $error)
|
|
# End - Build sub-configs
|
|
|
|
let logLevel =
|
|
if builder.logLevel.isSome():
|
|
builder.logLevel.get()
|
|
else:
|
|
warn "Log Level not specified, defaulting to INFO"
|
|
logging.LogLevel.INFO
|
|
|
|
let logFormat =
|
|
if builder.logFormat.isSome():
|
|
builder.logFormat.get()
|
|
else:
|
|
warn "Log Format not specified, defaulting to TEXT"
|
|
logging.LogFormat.TEXT
|
|
|
|
let natStrategy =
|
|
if builder.natStrategy.isSome():
|
|
builder.natStrategy.get()
|
|
else:
|
|
warn "Nat Strategy is not specified, defaulting to none"
|
|
"none"
|
|
|
|
let p2pTcpPort =
|
|
if builder.p2pTcpPort.isSome():
|
|
builder.p2pTcpPort.get()
|
|
else:
|
|
warn "P2P Listening TCP Port is not specified, listening on 60000"
|
|
60000.Port
|
|
|
|
let p2pListenAddress =
|
|
if builder.p2pListenAddress.isSome():
|
|
builder.p2pListenAddress.get()
|
|
else:
|
|
warn "P2P listening address not specified, listening on 0.0.0.0"
|
|
(static parseIpAddress("0.0.0.0"))
|
|
|
|
let portsShift =
|
|
if builder.portsShift.isSome():
|
|
builder.portsShift.get()
|
|
else:
|
|
warn "Ports Shift is not specified, defaulting to 0"
|
|
0.uint16
|
|
|
|
let dns4DomainName =
|
|
if builder.dns4DomainName.isSome():
|
|
let d = builder.dns4DomainName.get()
|
|
if d.string != "":
|
|
some(d)
|
|
else:
|
|
none(string)
|
|
else:
|
|
none(string)
|
|
|
|
var extMultiAddrs: seq[MultiAddress] = @[]
|
|
for s in builder.extMultiAddrs:
|
|
let m = MultiAddress.init(s).valueOr:
|
|
return err("Invalid multiaddress provided: " & s)
|
|
extMultiAddrs.add(m)
|
|
|
|
let extMultiAddrsOnly =
|
|
if builder.extMultiAddrsOnly.isSome():
|
|
builder.extMultiAddrsOnly.get()
|
|
else:
|
|
warn "Whether to only announce external multiaddresses is not specified, defaulting to false"
|
|
false
|
|
|
|
let dnsAddrs =
|
|
if builder.dnsAddrs.isSome():
|
|
builder.dnsAddrs.get()
|
|
else:
|
|
warn "Whether to resolve DNS multiaddresses was not specified, defaulting to false."
|
|
false
|
|
|
|
let dnsAddrsNameServers =
|
|
if builder.dnsAddrsNameServers.isSome():
|
|
builder.dnsAddrsNameServers.get()
|
|
else:
|
|
warn "DNS name servers IPs not provided, defaulting to Cloudflare's."
|
|
@[parseIpAddress("1.1.1.1"), parseIpAddress("1.0.0.1")]
|
|
|
|
let peerPersistence =
|
|
if builder.peerPersistence.isSome():
|
|
builder.peerPersistence.get()
|
|
else:
|
|
warn "Peer persistence not specified, defaulting to false"
|
|
false
|
|
|
|
let maxConnections =
|
|
if builder.maxConnections.isSome():
|
|
builder.maxConnections.get()
|
|
else:
|
|
warn "Max Connections was not specified, defaulting to 300"
|
|
300
|
|
|
|
# TODO: Do the git version thing here
|
|
let agentString = builder.agentString.get("nwaku")
|
|
|
|
# TODO: use `DefaultColocationLimit`. the user of this value should
|
|
# probably be defining a config object
|
|
let colocationLimit = builder.colocationLimit.get(5)
|
|
let rateLimits = builder.rateLimits.get(newSeq[string](0))
|
|
|
|
# TODO: is there a strategy for experimental features? delete vs promote
|
|
let relayShardedPeerManagement = builder.relayShardedPeerManagement.get(false)
|
|
|
|
let wakuFlags = CapabilitiesBitfield.init(
|
|
lightpush = lightPush,
|
|
filter = filterServiceConf.isSome,
|
|
store = storeServiceConf.isSome,
|
|
relay = relay,
|
|
sync = storeServiceConf.isSome() and storeServiceConf.get().storeSyncConf.isSome,
|
|
)
|
|
|
|
let wakuConf = WakuConf(
|
|
# confs
|
|
storeServiceConf: storeServiceConf,
|
|
filterServiceConf: filterServiceConf,
|
|
discv5Conf: discv5Conf,
|
|
rlnRelayConf: rlnRelayConf,
|
|
metricsServerConf: metricsServerConf,
|
|
restServerConf: restServerConf,
|
|
dnsDiscoveryConf: dnsDiscoveryConf,
|
|
# end confs
|
|
nodeKey: nodeKey,
|
|
clusterId: builder.clusterId.get(),
|
|
numShardsInNetwork: numShardsInNetwork,
|
|
contentTopics: contentTopics,
|
|
shards: shards,
|
|
protectedShards: protectedShards,
|
|
relay: relay,
|
|
lightPush: lightPush,
|
|
peerExchange: peerExchange,
|
|
rendezvous: rendezvous,
|
|
remoteStoreNode: builder.remoteStoreNode,
|
|
remoteLightPushNode: builder.remoteLightPushNode,
|
|
remoteFilterNode: builder.remoteFilterNode,
|
|
remotePeerExchangeNode: builder.remotePeerExchangeNode,
|
|
relayPeerExchange: relayPeerExchange,
|
|
maxMessageSizeBytes: maxMessageSizeBytes,
|
|
logLevel: logLevel,
|
|
logFormat: logFormat,
|
|
# TODO: Separate builders
|
|
networkConf: NetworkConfig(
|
|
natStrategy: natStrategy,
|
|
p2pTcpPort: p2pTcpPort,
|
|
dns4DomainName: dns4DomainName,
|
|
p2pListenAddress: p2pListenAddress,
|
|
extMultiAddrs: extMultiAddrs,
|
|
extMultiAddrsOnly: extMultiAddrsOnly,
|
|
),
|
|
portsShift: portsShift,
|
|
webSocketConf: webSocketConf,
|
|
dnsAddrs: dnsAddrs,
|
|
dnsAddrsNameServers: dnsAddrsNameServers,
|
|
peerPersistence: peerPersistence,
|
|
peerStoreCapacity: builder.peerStoreCapacity,
|
|
maxConnections: maxConnections,
|
|
agentString: agentString,
|
|
colocationLimit: colocationLimit,
|
|
maxRelayPeers: builder.maxRelayPeers,
|
|
relayServiceRatio: builder.relayServiceRatio.get("60:40"),
|
|
rateLimits: rateLimits,
|
|
circuitRelayClient: builder.circuitRelayClient.get(false),
|
|
keepAlive: builder.keepAlive.get(true),
|
|
staticNodes: builder.staticNodes,
|
|
relayShardedPeerManagement: relayShardedPeerManagement,
|
|
p2pReliability: builder.p2pReliability.get(false),
|
|
wakuFlags: wakuFlags,
|
|
)
|
|
|
|
?wakuConf.validate()
|
|
|
|
return ok(wakuConf)
|