feat: improve config v3 (#4015)

* remove --mode from the CLI
* move WakuMode to the messaging layer
* expose store backend (db url, max connections) and a remote store node on the messaging surface
* wakunode2 with no flags now runs as a full service node (store still opt-in)
* add rateLimitMessagesPerEpoch
* channel rate-limiting auto-enables if epochPeriodSec or messagesPerEpoch is set
* fix JSON conf parser to be generic (works over all config types)
* messaging config = mode + preset + messagingOverrides + channelsOverrides
* add full messaging plus selective kernel config options to MessagingClientConf
* mode (Core/Edge) expands to kernel protocol flags in the messaging layer
* create_node parses the messaging config, drops the flat WakuNodeConf JSON entrypoint
* wire channelsOverrides (segmentation/SDS/rate-limit) into channel creation
* fix liblogosdelivery.h comments and README for the new config shape
* messaging conf tests: switch names, reject-unknown, set-twice, field->kernel
* add kernel log-level, log-format, nodekey to the messaging surface
* Port 0 (ephemeral) default for messaging entry points
* KernelConf alias for WakuNodeConf
* rewrite the FFI examples to the new config shape
* C/C++ examples use preset status.prod
* drop operator-only confs from the examples
* remove duplicate tests & misc test fixes
* Delete p2pReliability from Kernel (Waku) resolver and config (keep preset definition)
* Delete NodeConfig API (deprecation completed by p2pReliability removal from kernel)
* Rename test_messaging_conf.nim to test_conf.nim (tests Logos Delivery config in general)
* Rename messaging_conf_json.nim to logos_delivery_conf_json.nim
* Add logos_delivery_conf.nim (defines LogosDeliveryConf aggregate)
* misc docs/comments cleanups
This commit is contained in:
Fabiana Cecin 2026-07-09 12:21:41 -03:00 committed by GitHub
parent 0a1700e239
commit 90fa5fa91f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 893 additions and 1287 deletions

View File

@ -50,12 +50,6 @@ struct ConfigNode
int relay; int relay;
char peers[2048]; char peers[2048];
int store; int store;
char storeNode[2048];
char storeRetentionPolicy[64];
char storeDbUrl[256];
int storeVacuum;
int storeDbMigration;
int storeMaxNumDbConnections;
}; };
// libwaku Context // libwaku Context
@ -290,42 +284,24 @@ int main(int argc, char **argv)
cfgNode.relay = 1; cfgNode.relay = 1;
cfgNode.store = 0; cfgNode.store = 0;
snprintf(cfgNode.storeNode, 2048, "");
snprintf(cfgNode.storeRetentionPolicy, 64, "time:6000000");
snprintf(cfgNode.storeDbUrl, 256, "postgres://postgres:test123@localhost:5432/postgres");
cfgNode.storeVacuum = 0;
cfgNode.storeDbMigration = 0;
cfgNode.storeMaxNumDbConnections = 30;
parse_args(argc, argv, &cfgNode); parse_args(argc, argv, &cfgNode);
char jsonConfig[5000]; char jsonConfig[5000];
snprintf(jsonConfig, 5000, "{ \ snprintf(jsonConfig, 5000, "{ \
\"clusterId\": 16, \ \"mode\": \"Core\", \
\"shards\": [ 1, 32, 64, 128, 256 ], \ \"preset\": \"status.prod\", \
\"numShardsInNetwork\": 257, \ \"messagingOverrides\": { \
\"listenAddress\": \"%s\", \ \"listen-address\": \"%s\", \
\"tcpPort\": %d, \ \"tcp-port\": %d, \
\"relay\": %s, \ \"store\": %s, \
\"store\": %s, \ \"log-level\": \"DEBUG\", \
\"storeMessageDbUrl\": \"%s\", \ \"discv5-udp-port\": 9999 \
\"storeMessageRetentionPolicy\": \"%s\", \ } \
\"storeMaxNumDbConnections\": %d , \
\"logLevel\": \"DEBUG\", \
\"discv5Discovery\": true, \
\"discv5BootstrapNodes\": \
[\"enr:-QEKuED9AJm2HGgrRpVaJY2nj68ao_QiPeUT43sK-aRM7sMJ6R4G11OSDOwnvVacgN1sTw-K7soC5dzHDFZgZkHU0u-XAYJpZIJ2NIJpcISnYxMvim11bHRpYWRkcnO4WgAqNiVib290LTAxLmRvLWFtczMuc3RhdHVzLnByb2Quc3RhdHVzLmltBnZfACw2JWJvb3QtMDEuZG8tYW1zMy5zdGF0dXMucHJvZC5zdGF0dXMuaW0GAbveA4Jyc40AEAUAAQAgAEAAgAEAiXNlY3AyNTZrMaEC3rRtFQSgc24uWewzXaxTY8hDAHB8sgnxr9k8Rjb5GeSDdGNwgnZfg3VkcIIjKIV3YWt1Mg0\", \"enr:-QEcuED7ww5vo2rKc1pyBp7fubBUH-8STHEZHo7InjVjLblEVyDGkjdTI9VdqmYQOn95vuQH-Htku17WSTzEufx-Wg4mAYJpZIJ2NIJpcIQihw1Xim11bHRpYWRkcnO4bAAzNi5ib290LTAxLmdjLXVzLWNlbnRyYWwxLWEuc3RhdHVzLnByb2Quc3RhdHVzLmltBnZfADU2LmJvb3QtMDEuZ2MtdXMtY2VudHJhbDEtYS5zdGF0dXMucHJvZC5zdGF0dXMuaW0GAbveA4Jyc40AEAUAAQAgAEAAgAEAiXNlY3AyNTZrMaECxjqgDQ0WyRSOilYU32DA5k_XNlDis3m1VdXkK9xM6kODdGNwgnZfg3VkcIIjKIV3YWt1Mg0\", \"enr:-QEcuEAoShWGyN66wwusE3Ri8hXBaIkoHZHybUB8cCPv5v3ypEf9OCg4cfslJxZFANl90s-jmMOugLUyBx4EfOBNJ6_VAYJpZIJ2NIJpcIQI2hdMim11bHRpYWRkcnO4bAAzNi5ib290LTAxLmFjLWNuLWhvbmdrb25nLWMuc3RhdHVzLnByb2Quc3RhdHVzLmltBnZfADU2LmJvb3QtMDEuYWMtY24taG9uZ2tvbmctYy5zdGF0dXMucHJvZC5zdGF0dXMuaW0GAbveA4Jyc40AEAUAAQAgAEAAgAEAiXNlY3AyNTZrMaEDP7CbRk-YKJwOFFM4Z9ney0GPc7WPJaCwGkpNRyla7mCDdGNwgnZfg3VkcIIjKIV3YWt1Mg0\"], \
\"discv5UdpPort\": 9999, \
\"dnsDiscoveryUrl\": \"enrtree://AMOJVZX4V6EXP7NTJPMAYJYST2QP6AJXYW76IU6VGJS7UVSNDYZG4@boot.prod.status.nodes.status.im\", \
\"dnsDiscoveryNameServers\": [\"8.8.8.8\", \"1.0.0.1\"] \
}", }",
cfgNode.host, cfgNode.host,
cfgNode.port, cfgNode.port,
cfgNode.relay ? "true" : "false", cfgNode.store ? "true" : "false");
cfgNode.store ? "true" : "false",
cfgNode.storeDbUrl,
cfgNode.storeRetentionPolicy,
cfgNode.storeMaxNumDbConnections);
ctx = logosdelivery_create_node(jsonConfig, event_handler, userData); ctx = logosdelivery_create_node(jsonConfig, event_handler, userData);
waitForCallback(); waitForCallback();

View File

@ -240,18 +240,14 @@ int main(int argc, char **argv)
char jsonConfig[2048]; char jsonConfig[2048];
snprintf(jsonConfig, 2048, "{ \ snprintf(jsonConfig, 2048, "{ \
\"host\": \"%s\", \ \"mode\": \"Core\", \
\"port\": %d, \ \"preset\": \"status.prod\", \
\"relay\": true, \ \"messagingOverrides\": { \
\"clusterId\": 16, \ \"listen-address\": \"%s\", \
\"shards\": [ 1, 32, 64, 128, 256 ], \ \"tcp-port\": %d, \
\"logLevel\": \"FATAL\", \ \"log-level\": \"FATAL\", \
\"discv5Discovery\": true, \ \"discv5-udp-port\": 9999 \
\"discv5BootstrapNodes\": \ } \
[\"enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw\", \"enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw\"], \
\"discv5UdpPort\": 9999, \
\"dnsDiscoveryUrl\": \"enrtree://AMOJVZX4V6EXP7NTJPMAYJYST2QP6AJXYW76IU6VGJS7UVSNDYZG4@boot.prod.status.nodes.status.im\", \
\"dnsDiscoveryNameServers\": [\"8.8.8.8\", \"1.0.0.1\"] \
}", }",
cfgNode.host, cfgNode.host,
cfgNode.port); cfgNode.port);

View File

@ -293,12 +293,16 @@ type WakuMessageHash = string
type WakuPubsubTopic = string type WakuPubsubTopic = string
type WakuContentTopic = string type WakuContentTopic = string
type MessagingOverrides struct {
ListenAddress string `json:"listen-address,omitempty"`
Port int `json:"tcp-port,omitempty"`
NodeKey string `json:"nodekey,omitempty"`
LogLevel string `json:"log-level,omitempty"`
}
type WakuConfig struct { type WakuConfig struct {
Host string `json:"host,omitempty"` Mode string `json:"mode"`
Port int `json:"port,omitempty"` MessagingOverrides MessagingOverrides `json:"messagingOverrides"`
NodeKey string `json:"key,omitempty"`
EnableRelay bool `json:"relay"`
LogLevel string `json:"logLevel"`
} }
type WakuNode struct { type WakuNode struct {
@ -563,11 +567,13 @@ func (self *WakuNode) WakuGetMyENR() (string, error) {
func main() { func main() {
config := WakuConfig{ config := WakuConfig{
Host: "0.0.0.0", Mode: "Core",
Port: 30304, MessagingOverrides: MessagingOverrides{
NodeKey: "11d0dcea28e86f81937a3bd1163473c7fbc0a0db54fd72914849bc47bdf78710", ListenAddress: "0.0.0.0",
EnableRelay: true, Port: 30304,
LogLevel: "DEBUG", NodeKey: "11d0dcea28e86f81937a3bd1163473c7fbc0a0db54fd72914849bc47bdf78710",
LogLevel: "DEBUG",
},
} }
node, err := WakuNew(config) node, err := WakuNew(config)

View File

@ -300,22 +300,17 @@ actor WakuActor {
private func initializeNode() async -> Bool { private func initializeNode() async -> Bool {
let config = """ let config = """
{ {
"tcpPort": 60000, "mode": "Edge",
"clusterId": 1, "messagingOverrides": {
"shards": [0], "tcp-port": 60000,
"relay": false, "cluster-id": 1,
"lightpush": true, "log-level": "DEBUG",
"filter": true, "entry-node": [
"logLevel": "DEBUG", "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw",
"discv5Discovery": true, "enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw"
"discv5BootstrapNodes": [ ],
"enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", "discv5-udp-port": 9999
"enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw" }
],
"discv5UdpPort": 9999,
"dnsDiscovery": true,
"dnsDiscoveryUrl": "enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im",
"dnsDiscoveryNameServers": ["8.8.8.8", "1.0.0.1"]
} }
""" """

View File

@ -162,10 +162,13 @@ function App(): React.JSX.Element {
const onClickNew = async () => { const onClickNew = async () => {
const config = { const config = {
host: '0.0.0.0', mode: 'Core',
port: 42342, messagingOverrides: {
key: '1122334455667788990011223344556677889900112233445566778899000022', 'listen-address': '0.0.0.0',
relay: true, 'tcp-port': 42342,
nodekey:
'1122334455667788990011223344556677889900112233445566778899000022',
},
}; };
waku = await WakuFactory.createInstance(config); waku = await WakuFactory.createInstance(config);
}; };

View File

@ -17,11 +17,13 @@ function create_random_string(length) {
var wakuMod = require('bindings')('waku'); var wakuMod = require('bindings')('waku');
var cfg = `{ var cfg = `{
"host": "0.0.0.0", "mode": "Core",
"port": 60001, "messagingOverrides": {
"key": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9", "listen-address": "0.0.0.0",
"relay": true "tcp-port": 60001,
"logLevel": "DEBUG" "nodekey": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9",
"log-level": "DEBUG"
}
}` }`
function event_handler(event) { function event_handler(event) {

View File

@ -55,15 +55,16 @@ args = parser.parse_args()
# The next 'json_config' is the item passed to the 'logosdelivery_create_node'. # The next 'json_config' is the item passed to the 'logosdelivery_create_node'.
json_config = "{ \ json_config = "{ \
\"host\": \"%s\", \ \"mode\": \"Core\", \
\"port\": %d, \ \"messagingOverrides\": { \
\"key\": \"%s\", \ \"listen-address\": \"%s\", \
\"relay\": %s ,\ \"tcp-port\": %d, \
\"logLevel\": \"DEBUG\" \ \"nodekey\": \"%s\", \
\"log-level\": \"DEBUG\" \
} \
}" % (args.host, }" % (args.host,
int(args.port), int(args.port),
args.key, args.key)
"true" if args.relay else "false")
callback_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p, ctypes.c_size_t) callback_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p, ctypes.c_size_t)

View File

@ -17,18 +17,16 @@ int main(int argc, char *argv[]) {
QString jsonConfig = R"( QString jsonConfig = R"(
{ {
"tcpPort": 60000, "mode": "Core",
"relay": true, "messagingOverrides": {
"logLevel": "TRACE", "tcp-port": 60000,
"discv5Discovery": true, "log-level": "TRACE",
"discv5BootstrapNodes": [ "entry-node": [
"enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw",
"enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw" "enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw"
], ],
"discv5UdpPort": 9999, "discv5-udp-port": 9999
"dnsDiscovery": true, }
"dnsDiscoveryUrl": "enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im",
"dnsDiscoveryNameServers": ["8.8.8.8", "1.0.0.1"]
} }
)"; )";

View File

@ -50,11 +50,13 @@ where
fn main() { fn main() {
let config_json = "\ let config_json = "\
{ \ { \
\"host\": \"127.0.0.1\",\ \"mode\": \"Core\",\
\"port\": 60000, \ \"messagingOverrides\": { \
\"key\": \"0d714a1fada214dead6dc9c7274581ec20ff292451866e7d6d677dc818e8ccd2\", \ \"listen-address\": \"127.0.0.1\",\
\"relay\": true ,\ \"tcp-port\": 60000, \
\"logLevel\": \"DEBUG\" \"nodekey\": \"0d714a1fada214dead6dc9c7274581ec20ff292451866e7d6d677dc818e8ccd2\", \
\"log-level\": \"DEBUG\"
}
}"; }";
unsafe { unsafe {

View File

@ -33,15 +33,22 @@ void *logosdelivery_create_node(
{ {
"mode": "Core", "mode": "Core",
"preset": "logos.dev", "preset": "logos.dev",
"listenAddress": "0.0.0.0", "messagingOverrides": {
"tcpPort": 60000, "listen-address": "0.0.0.0",
"discv5UdpPort": 9000 "tcp-port": 60000,
"discv5-udp-port": 9000
}
} }
``` ```
Configuration uses flat field names matching `WakuNodeConf` in `tools/confutils/cli_args.nim`. The configuration object has four optional top-level keys: `mode` (`"Core"` or
Use `"preset"` to select a network preset (e.g., `"twn"`, `"logos.dev"`, `"status.prod"`) which `"Edge"`, defaults to `"Core"`), `preset`, `messagingOverrides` (per-field node
auto-configures entry nodes, cluster ID, sharding, and other network-specific settings. config overrides), and `channelsOverrides` (reliable-channel overrides).
Override keys accept the config field name or its CLI switch name (e.g.
`"clusterId"` or `"cluster-id"`); unknown keys are rejected.
Use `"preset"` to select a network preset (e.g., `"twn"`, `"logos.dev"`,
`"status.prod"`) which auto-configures entry nodes, cluster ID, sharding, and
other network-specific settings.
Available presets: Available presets:

View File

@ -129,12 +129,14 @@ void simple_callback(int ret, const char *msg, size_t len, void *userData) {
int main() { int main() {
printf("=== Logos Messaging API (LMAPI) Example ===\n\n"); printf("=== Logos Messaging API (LMAPI) Example ===\n\n");
// Configuration JSON using WakuNodeConf field names (flat structure). // Layered messaging config: {mode, preset, messagingOverrides, channelsOverrides}.
// Field names match Nim identifiers from WakuNodeConf in tools/confutils/cli_args.nim. // Override keys are MessagingClientConf field or CLI switch names.
const char *config = "{" const char *config = "{"
"\"logLevel\": \"INFO\","
"\"mode\": \"Core\"," "\"mode\": \"Core\","
"\"preset\": \"logos.dev\"" "\"preset\": \"logos.dev\","
"\"messagingOverrides\": {"
"\"log-level\": \"INFO\""
"}"
"}"; "}";
printf("1. Creating node...\n"); printf("1. Creating node...\n");

View File

@ -22,9 +22,14 @@ extern "C"
// Creates a new instance of the node from the given configuration JSON. // Creates a new instance of the node from the given configuration JSON.
// Returns a pointer to the Context needed by the rest of the API functions. // Returns a pointer to the Context needed by the rest of the API functions.
// Configuration should be in JSON format using WakuNodeConf field names. // The configuration is a JSON object with these optional keys:
// Field names match Nim identifiers from WakuNodeConf (camelCase). // "mode": "Core" | "Edge" (messaging role; defaults to "Core")
// Example: {"mode": "Core", "clusterId": 42, "relay": true} // "preset": "<network preset>" (e.g. "twn")
// "messagingOverrides": { ... } (per-field messaging config overrides)
// "channelsOverrides": { ... } (per-field reliable-channel overrides)
// Override keys accept the config field name or its CLI switch name (e.g.
// "clusterId" or "cluster-id"). Unknown keys are rejected.
// Example: {"mode":"Core","messagingOverrides":{"cluster-id":42,"log-level":"INFO"}}
void *logosdelivery_create_node( void *logosdelivery_create_node(
const char *configJson, const char *configJson,
FFICallBack callback, FFICallBack callback,

View File

@ -7,7 +7,7 @@ import
logos_delivery/api/types, logos_delivery/api/types,
logos_delivery/waku/api/events/health_events, logos_delivery/waku/api/events/health_events,
logos_delivery/waku/api/events/peer_events, logos_delivery/waku/api/events/peer_events,
tools/confutils/conf_from_json, logos_delivery/api/logos_delivery_conf_json,
../declare_lib, ../declare_lib,
../json_event ../json_event
@ -17,10 +17,10 @@ proc `%`*(id: RequestId): JsonNode =
registerReqFFI(CreateNodeRequest, ctx: ptr FFIContext[LogosDelivery]): registerReqFFI(CreateNodeRequest, ctx: ptr FFIContext[LogosDelivery]):
proc(configJson: cstring): Future[Result[string, string]] {.async.} = proc(configJson: cstring): Future[Result[string, string]] {.async.} =
let conf = parseNodeConfFromJson($configJson).valueOr: let conf = parseLogosDeliveryConf($configJson).valueOr:
error "Failed to assemble WakuNodeConf from JSON", error "Failed to parse Logos Delivery configuration JSON",
error = error, configJson = $configJson error = error, configJson = $configJson
return err("failed parseNodeConfFromJson " & error) return err("failed parseLogosDeliveryConf " & error)
ctx.myLib[] = (await LogosDelivery.new(conf)).valueOr: ctx.myLib[] = (await LogosDelivery.new(conf)).valueOr:
let errMsg = $error let errMsg = $error

View File

@ -1,534 +0,0 @@
import logos_delivery/waku/compat/option_valueor
import std/[net, options]
import results
import json_serialization, json_serialization/std/options as json_options
import
logos_delivery/waku/common/utils/parse_size_units,
logos_delivery/waku/common/logging,
logos_delivery/waku/factory/waku_conf,
logos_delivery/waku/factory/conf_builder/conf_builder,
logos_delivery/waku/factory/networks_config,
tools/confutils/entry_nodes
export json_serialization, json_options
type AutoShardingConfig* = object
numShardsInCluster*: uint16
type RlnConfig* = object
contractAddress*: string
chainId*: uint
epochSizeSec*: uint64
type NetworkingConfig* = object
listenIpv4*: string
p2pTcpPort*: uint16
discv5UdpPort*: uint16
type MessageValidation* = object
maxMessageSize*: string # Accepts formats like "150 KiB", "1500 B"
rlnConfig*: Option[RlnConfig]
type ProtocolsConfig* = object
entryNodes: seq[string]
staticStoreNodes: seq[string]
clusterId: uint16
autoShardingConfig: AutoShardingConfig
messageValidation: MessageValidation
const DefaultNetworkingConfig* =
NetworkingConfig(listenIpv4: "0.0.0.0", p2pTcpPort: 60000, discv5UdpPort: 9000)
const DefaultAutoShardingConfig* = AutoShardingConfig(numShardsInCluster: 1)
const DefaultMessageValidation* =
MessageValidation(maxMessageSize: "150 KiB", rlnConfig: none(RlnConfig))
proc init*(
T: typedesc[ProtocolsConfig],
entryNodes: seq[string],
staticStoreNodes: seq[string] = @[],
clusterId: uint16,
autoShardingConfig: AutoShardingConfig = DefaultAutoShardingConfig,
messageValidation: MessageValidation = DefaultMessageValidation,
): T =
return T(
entryNodes: entryNodes,
staticStoreNodes: staticStoreNodes,
clusterId: clusterId,
autoShardingConfig: autoShardingConfig,
messageValidation: messageValidation,
)
const TheWakuNetworkPreset* = ProtocolsConfig(
entryNodes: @[
"enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im"
],
staticStoreNodes: @[],
clusterId: 1,
autoShardingConfig: AutoShardingConfig(numShardsInCluster: 8),
messageValidation: MessageValidation(
maxMessageSize: "150 KiB",
rlnConfig: some(
RlnConfig(
contractAddress: "0xB9cd878C90E49F797B4431fBF4fb333108CB90e6",
chainId: 59141,
epochSizeSec: 600, # 10 minutes
)
),
),
)
type WakuMode* {.pure.} = enum
Edge
Core
type NodeConfig* {.
requiresInit, deprecated: "Use WakuNodeConf from tools/confutils/cli_args instead"
.} = object
mode: WakuMode
protocolsConfig: ProtocolsConfig
networkingConfig: NetworkingConfig
ethRpcEndpoints: seq[string]
p2pReliability: bool
logLevel: LogLevel
logFormat: LogFormat
proc init*(
T: typedesc[NodeConfig],
mode: WakuMode = WakuMode.Core,
protocolsConfig: ProtocolsConfig = TheWakuNetworkPreset,
networkingConfig: NetworkingConfig = DefaultNetworkingConfig,
ethRpcEndpoints: seq[string] = @[],
p2pReliability: bool = false,
logLevel: LogLevel = LogLevel.INFO,
logFormat: LogFormat = LogFormat.TEXT,
): T =
return T(
mode: mode,
protocolsConfig: protocolsConfig,
networkingConfig: networkingConfig,
ethRpcEndpoints: ethRpcEndpoints,
p2pReliability: p2pReliability,
logLevel: logLevel,
logFormat: logFormat,
)
# -- Getters for ProtocolsConfig (private fields) - used for testing --
proc entryNodes*(c: ProtocolsConfig): seq[string] =
c.entryNodes
proc staticStoreNodes*(c: ProtocolsConfig): seq[string] =
c.staticStoreNodes
proc clusterId*(c: ProtocolsConfig): uint16 =
c.clusterId
proc autoShardingConfig*(c: ProtocolsConfig): AutoShardingConfig =
c.autoShardingConfig
proc messageValidation*(c: ProtocolsConfig): MessageValidation =
c.messageValidation
# -- Getters for NodeConfig (private fields) - used for testing --
proc mode*(c: NodeConfig): WakuMode =
c.mode
proc protocolsConfig*(c: NodeConfig): ProtocolsConfig =
c.protocolsConfig
proc networkingConfig*(c: NodeConfig): NetworkingConfig =
c.networkingConfig
proc ethRpcEndpoints*(c: NodeConfig): seq[string] =
c.ethRpcEndpoints
proc p2pReliability*(c: NodeConfig): bool =
c.p2pReliability
proc logLevel*(c: NodeConfig): LogLevel =
c.logLevel
proc logFormat*(c: NodeConfig): LogFormat =
c.logFormat
proc toWakuConf*(
nodeConfig: NodeConfig
): Result[WakuConf, string] {.deprecated: "Use WakuNodeConf.toWakuConf instead".} =
var b = WakuConfBuilder.init()
# Apply log configuration
b.withLogLevel(nodeConfig.logLevel)
b.withLogFormat(nodeConfig.logFormat)
# Apply networking configuration
let networkingConfig = nodeConfig.networkingConfig
let ip = parseIpAddress(networkingConfig.listenIpv4)
b.withP2pListenAddress(ip)
b.withP2pTcpPort(networkingConfig.p2pTcpPort)
b.discv5Conf.withUdpPort(networkingConfig.discv5UdpPort)
case nodeConfig.mode
of Core:
b.withRelay(true)
# Metadata is always mounted
b.filterServiceConf.withEnabled(true)
b.filterServiceConf.withMaxPeersToServe(20)
b.withLightPush(true)
b.discv5Conf.withEnabled(true)
b.withPeerExchange(true)
b.withRendezvous(true)
# TODO: fix store as client usage
b.rateLimitConf.withRateLimits(@["filter:100/1s", "lightpush:5/1s", "px:5/1s"])
of Edge:
# All client side protocols are mounted by default
# Peer exchange client is always enabled and start_node will start the px loop
# Metadata is always mounted
b.withPeerExchange(true)
# switch off all service side protocols and relay
b.withRelay(false)
b.filterServiceConf.withEnabled(false)
b.withLightPush(false)
b.storeServiceConf.withEnabled(false)
# Leave discv5 and rendezvous for user choice
## Network Conf
let protocolsConfig = nodeConfig.protocolsConfig
# Set cluster ID
b.withClusterId(protocolsConfig.clusterId)
# Set sharding configuration
b.withShardingConf(ShardingConfKind.AutoSharding)
let autoShardingConfig = protocolsConfig.autoShardingConfig
b.withNumShardsInCluster(autoShardingConfig.numShardsInCluster)
# Process entry nodes - supports enrtree:, enr:, and multiaddress formats
if protocolsConfig.entryNodes.len > 0:
let (enrTreeUrls, bootstrapEnrs, staticNodesFromEntry) = processEntryNodes(
protocolsConfig.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)
b.dnsDiscoveryconf.withNameServers(
@[parseIpAddress("1.1.1.1"), parseIpAddress("1.0.0.1")]
)
# 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)
# TODO: verify behaviour
# Set static store nodes
if protocolsConfig.staticStoreNodes.len > 0:
b.withStaticNodes(protocolsConfig.staticStoreNodes)
# Set message validation
let msgValidation = protocolsConfig.messageValidation
let maxSizeBytes = parseMsgSize(msgValidation.maxMessageSize).valueOr:
return err("Failed to parse max message size: " & error)
b.withMaxMessageSize(maxSizeBytes)
# Set RLN config if provided
if msgValidation.rlnConfig.isSome():
let rlnConfig = msgValidation.rlnConfig.get()
b.rlnRelayConf.withEnabled(true)
b.rlnRelayConf.withEthContractAddress(rlnConfig.contractAddress)
b.rlnRelayConf.withChainId(rlnConfig.chainId)
b.rlnRelayConf.withEpochSizeSec(rlnConfig.epochSizeSec)
b.rlnRelayConf.withDynamic(true)
b.rlnRelayConf.withEthClientUrls(nodeConfig.ethRpcEndpoints)
# TODO: we should get rid of those two
b.rlnRelayconf.withUserMessageLimit(100)
## Various configurations
b.withNatStrategy("any")
b.withP2PReliability(nodeConfig.p2pReliability)
let wakuConf = b.build().valueOr:
return err("Failed to build configuration: " & error)
wakuConf.validate().isOkOr:
return err("Failed to validate configuration: " & error)
return ok(wakuConf)
# ---- JSON serialization (writeValue / readValue) ----
# ---------- AutoShardingConfig ----------
proc writeValue*(w: var JsonWriter, val: AutoShardingConfig) {.raises: [IOError].} =
w.beginRecord()
w.writeField("numShardsInCluster", val.numShardsInCluster)
w.endRecord()
proc readValue*(
r: var JsonReader, val: var AutoShardingConfig
) {.raises: [SerializationError, IOError].} =
var numShardsInCluster: Option[uint16]
for fieldName in readObjectFields(r):
case fieldName
of "numShardsInCluster":
numShardsInCluster = some(r.readValue(uint16))
else:
r.raiseUnexpectedField(fieldName, "AutoShardingConfig")
if numShardsInCluster.isNone():
r.raiseUnexpectedValue("Missing required field 'numShardsInCluster'")
val = AutoShardingConfig(numShardsInCluster: numShardsInCluster.get())
# ---------- RlnConfig ----------
proc writeValue*(w: var JsonWriter, val: RlnConfig) {.raises: [IOError].} =
w.beginRecord()
w.writeField("contractAddress", val.contractAddress)
w.writeField("chainId", val.chainId)
w.writeField("epochSizeSec", val.epochSizeSec)
w.endRecord()
proc readValue*(
r: var JsonReader, val: var RlnConfig
) {.raises: [SerializationError, IOError].} =
var
contractAddress: Option[string]
chainId: Option[uint]
epochSizeSec: Option[uint64]
for fieldName in readObjectFields(r):
case fieldName
of "contractAddress":
contractAddress = some(r.readValue(string))
of "chainId":
chainId = some(r.readValue(uint))
of "epochSizeSec":
epochSizeSec = some(r.readValue(uint64))
else:
r.raiseUnexpectedField(fieldName, "RlnConfig")
if contractAddress.isNone():
r.raiseUnexpectedValue("Missing required field 'contractAddress'")
if chainId.isNone():
r.raiseUnexpectedValue("Missing required field 'chainId'")
if epochSizeSec.isNone():
r.raiseUnexpectedValue("Missing required field 'epochSizeSec'")
val = RlnConfig(
contractAddress: contractAddress.get(),
chainId: chainId.get(),
epochSizeSec: epochSizeSec.get(),
)
# ---------- NetworkingConfig ----------
proc writeValue*(w: var JsonWriter, val: NetworkingConfig) {.raises: [IOError].} =
w.beginRecord()
w.writeField("listenIpv4", val.listenIpv4)
w.writeField("p2pTcpPort", val.p2pTcpPort)
w.writeField("discv5UdpPort", val.discv5UdpPort)
w.endRecord()
proc readValue*(
r: var JsonReader, val: var NetworkingConfig
) {.raises: [SerializationError, IOError].} =
var
listenIpv4: Option[string]
p2pTcpPort: Option[uint16]
discv5UdpPort: Option[uint16]
for fieldName in readObjectFields(r):
case fieldName
of "listenIpv4":
listenIpv4 = some(r.readValue(string))
of "p2pTcpPort":
p2pTcpPort = some(r.readValue(uint16))
of "discv5UdpPort":
discv5UdpPort = some(r.readValue(uint16))
else:
r.raiseUnexpectedField(fieldName, "NetworkingConfig")
if listenIpv4.isNone():
r.raiseUnexpectedValue("Missing required field 'listenIpv4'")
if p2pTcpPort.isNone():
r.raiseUnexpectedValue("Missing required field 'p2pTcpPort'")
if discv5UdpPort.isNone():
r.raiseUnexpectedValue("Missing required field 'discv5UdpPort'")
val = NetworkingConfig(
listenIpv4: listenIpv4.get(),
p2pTcpPort: p2pTcpPort.get(),
discv5UdpPort: discv5UdpPort.get(),
)
# ---------- MessageValidation ----------
proc writeValue*(w: var JsonWriter, val: MessageValidation) {.raises: [IOError].} =
w.beginRecord()
w.writeField("maxMessageSize", val.maxMessageSize)
w.writeField("rlnConfig", val.rlnConfig)
w.endRecord()
proc readValue*(
r: var JsonReader, val: var MessageValidation
) {.raises: [SerializationError, IOError].} =
var
maxMessageSize: Option[string]
rlnConfig: Option[Option[RlnConfig]]
for fieldName in readObjectFields(r):
case fieldName
of "maxMessageSize":
maxMessageSize = some(r.readValue(string))
of "rlnConfig":
rlnConfig = some(r.readValue(Option[RlnConfig]))
else:
r.raiseUnexpectedField(fieldName, "MessageValidation")
if maxMessageSize.isNone():
r.raiseUnexpectedValue("Missing required field 'maxMessageSize'")
val = MessageValidation(
maxMessageSize: maxMessageSize.get(), rlnConfig: rlnConfig.get(none(RlnConfig))
)
# ---------- ProtocolsConfig ----------
proc writeValue*(w: var JsonWriter, val: ProtocolsConfig) {.raises: [IOError].} =
w.beginRecord()
w.writeField("entryNodes", val.entryNodes)
w.writeField("staticStoreNodes", val.staticStoreNodes)
w.writeField("clusterId", val.clusterId)
w.writeField("autoShardingConfig", val.autoShardingConfig)
w.writeField("messageValidation", val.messageValidation)
w.endRecord()
proc readValue*(
r: var JsonReader, val: var ProtocolsConfig
) {.raises: [SerializationError, IOError].} =
var
entryNodes: Option[seq[string]]
staticStoreNodes: Option[seq[string]]
clusterId: Option[uint16]
autoShardingConfig: Option[AutoShardingConfig]
messageValidation: Option[MessageValidation]
for fieldName in readObjectFields(r):
case fieldName
of "entryNodes":
entryNodes = some(r.readValue(seq[string]))
of "staticStoreNodes":
staticStoreNodes = some(r.readValue(seq[string]))
of "clusterId":
clusterId = some(r.readValue(uint16))
of "autoShardingConfig":
autoShardingConfig = some(r.readValue(AutoShardingConfig))
of "messageValidation":
messageValidation = some(r.readValue(MessageValidation))
else:
r.raiseUnexpectedField(fieldName, "ProtocolsConfig")
if entryNodes.isNone():
r.raiseUnexpectedValue("Missing required field 'entryNodes'")
if clusterId.isNone():
r.raiseUnexpectedValue("Missing required field 'clusterId'")
val = ProtocolsConfig.init(
entryNodes = entryNodes.get(),
staticStoreNodes = staticStoreNodes.get(@[]),
clusterId = clusterId.get(),
autoShardingConfig = autoShardingConfig.get(DefaultAutoShardingConfig),
messageValidation = messageValidation.get(DefaultMessageValidation),
)
# ---------- NodeConfig ----------
proc writeValue*(w: var JsonWriter, val: NodeConfig) {.raises: [IOError].} =
w.beginRecord()
w.writeField("mode", val.mode)
w.writeField("protocolsConfig", val.protocolsConfig)
w.writeField("networkingConfig", val.networkingConfig)
w.writeField("ethRpcEndpoints", val.ethRpcEndpoints)
w.writeField("p2pReliability", val.p2pReliability)
w.writeField("logLevel", val.logLevel)
w.writeField("logFormat", val.logFormat)
w.endRecord()
proc readValue*(
r: var JsonReader, val: var NodeConfig
) {.raises: [SerializationError, IOError].} =
var
mode: Option[WakuMode]
protocolsConfig: Option[ProtocolsConfig]
networkingConfig: Option[NetworkingConfig]
ethRpcEndpoints: Option[seq[string]]
p2pReliability: Option[bool]
logLevel: Option[LogLevel]
logFormat: Option[LogFormat]
for fieldName in readObjectFields(r):
case fieldName
of "mode":
mode = some(r.readValue(WakuMode))
of "protocolsConfig":
protocolsConfig = some(r.readValue(ProtocolsConfig))
of "networkingConfig":
networkingConfig = some(r.readValue(NetworkingConfig))
of "ethRpcEndpoints":
ethRpcEndpoints = some(r.readValue(seq[string]))
of "p2pReliability":
p2pReliability = some(r.readValue(bool))
of "logLevel":
logLevel = some(r.readValue(LogLevel))
of "logFormat":
logFormat = some(r.readValue(LogFormat))
else:
r.raiseUnexpectedField(fieldName, "NodeConfig")
val = NodeConfig.init(
mode = mode.get(WakuMode.Core),
protocolsConfig = protocolsConfig.get(TheWakuNetworkPreset),
networkingConfig = networkingConfig.get(DefaultNetworkingConfig),
ethRpcEndpoints = ethRpcEndpoints.get(@[]),
p2pReliability = p2pReliability.get(false),
logLevel = logLevel.get(LogLevel.INFO),
logFormat = logFormat.get(LogFormat.TEXT),
)
# ---------- Decode helper ----------
# Json.decode returns T via `result`, which conflicts with {.requiresInit.}
# on Nim 2.x. This helper avoids the issue by using readValue into a var.
proc decodeNodeConfigFromJson*(
jsonStr: string
): NodeConfig {.
raises: [SerializationError],
deprecated: "Use WakuNodeConf with fieldPairs-based JSON parsing instead"
.} =
var val = NodeConfig.init() # default-initialized
try:
var stream = unsafeMemoryInput(jsonStr)
var reader = (JsonReader[DefaultFlavor].init(stream))
reader.readValue(val)
except IOError as err:
raise (ref SerializationError)(msg: err.msg)
return val

View File

@ -0,0 +1,4 @@
import tools/confutils/cli_args
export cli_args
type KernelConf* = WakuNodeConf ## Alias of the CLI-facing `WakuNodeConf`.

View File

@ -0,0 +1,31 @@
{.push raises: [].}
import results
import logos_delivery/api/messaging_conf
import logos_delivery/channels/reliable_channel_manager
export messaging_conf, reliable_channel_manager
type LogosDeliveryConf* = object ## Aggregates the per-layer config objects.
kernelConf*: KernelConf
messagingConf*: MessagingClientConf
channelsConf*: ReliableChannelManagerConf
proc init*(
T: type LogosDeliveryConf,
mode: WakuMode,
preset: string,
messagingOverrides: MessagingClientConf,
channelsOverrides: ReliableChannelManagerConf,
): ConfResult[LogosDeliveryConf] =
let merged = merge(?resolvePreset(preset), messagingOverrides)
var kernelConf = ?toKernelConf(merged, mode)
kernelConf.preset = preset
return ok(
LogosDeliveryConf(
kernelConf: kernelConf, messagingConf: merged, channelsConf: channelsOverrides
)
)
{.pop.}

View File

@ -0,0 +1,86 @@
{.push raises: [].}
import std/[json, options, strutils, tables]
import results
import tools/confutils/conf_from_json
import logos_delivery/api/logos_delivery_conf
const
# Lowercased, since `collectJsonFields` keys the object case-insensitively.
KeyMode = "mode"
KeyPreset = "preset"
KeyMessagingOverrides = "messagingoverrides"
KeyChannelsOverrides = "channelsoverrides"
proc parseMode(s: string): Result[WakuMode, string] =
case s.strip().toLowerAscii()
of "core":
return ok(WakuMode.Core)
of "edge":
return ok(WakuMode.Edge)
else:
return err("invalid mode: '" & s & "' (expected 'Core' or 'Edge')")
proc parseOverrides[T](node: JsonNode, label: string): Result[T, string] =
if node.kind != JObject:
return err(label & " must be a JSON object")
var fields = ?collectJsonFields(node)
var conf = T()
?applyJsonFieldsToConf(
conf,
fields,
"Failed to parse " & label & " field",
"Unrecognized " & label & " option(s) found",
)
return ok(conf)
proc parseLogosDeliveryConf*(jsonStr: string): ConfResult[LogosDeliveryConf] =
var node: JsonNode
try:
node = parseJson(jsonStr)
except CatchableError as e:
return err("invalid JSON: " & e.msg)
if node.kind != JObject:
return err("configuration JSON must be an object")
var top = ?collectJsonFields(node)
var mode = WakuMode.Core
var preset = ""
var messagingOverrides = MessagingClientConf()
var channelsOverrides = ReliableChannelManagerConf()
if top.hasKey(KeyMode):
let (_, v) = top.getOrDefault(KeyMode)
if v.kind != JString:
return err("mode must be a string")
mode = ?parseMode(v.getStr())
top.del(KeyMode)
if top.hasKey(KeyPreset):
let (_, v) = top.getOrDefault(KeyPreset)
if v.kind != JString:
return err("preset must be a string")
preset = v.getStr().strip()
top.del(KeyPreset)
if top.hasKey(KeyMessagingOverrides):
let (_, v) = top.getOrDefault(KeyMessagingOverrides)
messagingOverrides = ?parseOverrides[MessagingClientConf](v, "messagingOverrides")
top.del(KeyMessagingOverrides)
if top.hasKey(KeyChannelsOverrides):
let (_, v) = top.getOrDefault(KeyChannelsOverrides)
channelsOverrides =
?parseOverrides[ReliableChannelManagerConf](v, "channelsOverrides")
top.del(KeyChannelsOverrides)
if top.len > 0:
var keys: seq[string]
for _, (k, _) in pairs(top):
keys.add(k)
return err("Unrecognized configuration option(s) found: " & keys.join(", "))
return LogosDeliveryConf.init(mode, preset, messagingOverrides, channelsOverrides)
{.pop.}

View File

@ -0,0 +1,95 @@
import std/options
import std/net
import results
import logos_delivery/api/kernel_conf
import logos_delivery/messaging/messaging_client
import logos_delivery/waku/factory/networks_config
export kernel_conf, messaging_client
type WakuMode* {.pure.} = enum
Edge # client-only node
Core # full service node
proc toKernelConf*(self: MessagingClientConf, mode: WakuMode): ConfResult[KernelConf] =
## Mode sets the protocol flags; set fields map to their kernel counterpart.
var conf = ?defaultWakuNodeConf()
case mode
of WakuMode.Core:
conf.relay = true
conf.filter = true
conf.lightpush = true
conf.discv5Discovery = some(true)
conf.peerExchange = true
conf.rendezvous = true
of WakuMode.Edge:
conf.peerExchange = true
conf.relay = false
conf.filter = false
conf.lightpush = false
conf.store = false
if self.store.isSome():
conf.store = self.store.get()
if self.storeMessageDbUrl.isSome():
conf.storeMessageDbUrl = self.storeMessageDbUrl.get()
if self.storeMessageRetentionPolicy.isSome():
conf.storeMessageRetentionPolicy = self.storeMessageRetentionPolicy.get()
if self.storeMaxNumDbConnections.isSome():
conf.storeMaxNumDbConnections = self.storeMaxNumDbConnections.get()
if self.storenode.isSome():
conf.storenode = self.storenode.get()
if self.clusterId.isSome():
conf.clusterId = self.clusterId
if self.numShardsInCluster.isSome():
conf.numShardsInNetwork = self.numShardsInCluster.get()
if self.listenIpv4.isSome():
conf.listenAddress = self.listenIpv4.get()
if self.maxMessageSize.isSome():
conf.maxMessageSize = self.maxMessageSize.get()
if self.entryNodes.isSome():
conf.entryNodes = self.entryNodes.get()
if self.ethRpcEndpoints.isSome():
conf.ethClientUrls = self.ethRpcEndpoints.get()
if self.rlnContractAddress.isSome():
conf.rlnRelayEthContractAddress = self.rlnContractAddress.get()
conf.rlnRelay = some(true)
if self.rlnChainId.isSome():
conf.rlnRelayChainId = self.rlnChainId.get()
if self.rlnEpochSizeSec.isSome():
conf.rlnEpochSizeSec = some(self.rlnEpochSizeSec.get().uint64)
if self.logLevel.isSome():
conf.logLevel = self.logLevel.get()
if self.logFormat.isSome():
conf.logFormat = self.logFormat.get()
if self.nodeKey.isSome():
conf.nodekey = self.nodeKey
conf.tcpPort = self.p2pTcpPort.get(Port(0))
conf.discv5UdpPort = self.discv5UdpPort.get(Port(0))
conf.websocketPort = self.websocketPort.get(Port(0))
conf.quicPort = self.quicPort.get(Port(0))
conf.websocketSupport = self.websocketSupport.get(false)
conf.quicSupport = self.quicSupport.get(true)
return ok(conf)
proc merge*(base, overrides: MessagingClientConf): MessagingClientConf =
var m = base
for _, mField, oField in fieldPairs(m, overrides):
when oField is Option:
if oField.isSome():
mField = oField
return m
proc resolvePreset*(preset: string): ConfResult[MessagingClientConf] =
## Preset to messaging-only fields. Kernel-mirrored fields stay unset; the
## kernel resolves those from `conf.preset`.
let npcOpt = ?toNetworkPresetConf(preset, none(uint16))
if npcOpt.isNone():
return ok(MessagingClientConf())
let npc = npcOpt.get()
return ok(MessagingClientConf(reliabilityEnabled: some(npc.p2pReliability)))

View File

@ -8,7 +8,7 @@ import logos_delivery/channels/reliable_channel_manager
import logos_delivery/channels/reliable_channel import logos_delivery/channels/reliable_channel
import logos_delivery/waku/persistency/sds_persistency import logos_delivery/waku/persistency/sds_persistency
# ReliableChannel, SendHandler, config and wire-version markers. # ReliableChannel, config and wire-version markers.
export reliable_channel export reliable_channel
const SdsJobId = "sds" const SdsJobId = "sds"
@ -33,22 +33,32 @@ proc createReliableChannel*(
contentTopic: ContentTopic, contentTopic: ContentTopic,
senderId: SdsParticipantID, senderId: SdsParticipantID,
): Result[ChannelId, string] = ): Result[ChannelId, string] =
## Encryption and egress providers must be installed (or `setNoopEncryption()`)
## before traffic flows on the channel.
if self.channels.hasKey(channelId): if self.channels.hasKey(channelId):
return err("channel already exists: " & channelId) return err("channel already exists: " & channelId)
let cc = self.conf
let segConfig = SegmentationConfig( let segConfig = SegmentationConfig(
segmentSizeBytes: DefaultSegmentSizeBytes, segmentSizeBytes: cc.segmentationSegmentSizeBytes.get(DefaultSegmentSizeBytes),
enableReedSolomon: false, enableReedSolomon: cc.segmentationEnableReedSolomon.get(false),
persistence: nil, persistence: nil,
) )
let sdsConfig = SdsConfig( let sdsConfig = SdsConfig(
acknowledgementTimeoutMs: DefaultAcknowledgementTimeoutMs, acknowledgementTimeoutMs:
maxRetransmissions: DefaultMaxRetransmissions, cc.sdsAcknowledgementTimeoutMs.get(DefaultAcknowledgementTimeoutMs),
causalHistorySize: DefaultCausalHistorySize, maxRetransmissions: cc.sdsMaxRetransmissions.get(DefaultMaxRetransmissions),
causalHistorySize: cc.sdsCausalHistorySize.get(DefaultCausalHistorySize),
persistence: sdsPersistence(), persistence: sdsPersistence(),
) )
let rateConfig = RateLimitConfig( let rateConfig = RateLimitConfig(
epochPeriodSec: DefaultEpochPeriodSec, messagesPerEpoch: DefaultMessagesPerEpoch # Setting a rate-limit parameter implies enabling; an explicit
# rateLimitEnabled still wins.
enabled: cc.rateLimitEnabled.get(
cc.rateLimitEpochPeriodSec.isSome() or cc.rateLimitMessagesPerEpoch.isSome()
),
epochPeriodSec: cc.rateLimitEpochPeriodSec.get(DefaultEpochPeriodSec),
messagesPerEpoch: cc.rateLimitMessagesPerEpoch.get(DefaultMessagesPerEpoch),
) )
let chn = ReliableChannel.new( let chn = ReliableChannel.new(

View File

@ -13,8 +13,6 @@ import stew/byteutils
import brokers/broker_context import brokers/broker_context
import logos_delivery/messaging/messaging_client
import logos_delivery/messaging/api/send
import logos_delivery/api/types import logos_delivery/api/types
import logos_delivery/api/reliable_channel_manager_api import logos_delivery/api/reliable_channel_manager_api
@ -24,12 +22,22 @@ export reliable_channel
type type
ReliableChannelManagerConf* = object ReliableChannelManagerConf* = object
## Per-layer config object for the reliable ## All-`Option` partial; unset fields fall back to `createReliableChannel` defaults.
## channel API. Placeholder for now (segmentation / SDS / rate-limit defaults segmentationEnableReedSolomon*: Option[bool]
## will move here in a follow-up PR); kept so each layer owns its own config. ## Add Reed-Solomon parity segments for recovery of lost segments.
segmentationSegmentSizeBytes*: Option[int] ## Maximum segment size in bytes.
sdsAcknowledgementTimeoutMs*: Option[int]
## Time to wait before retransmitting an unacknowledged message.
sdsMaxRetransmissions*: Option[int]
## Maximum retransmission attempts before delivery fails.
sdsCausalHistorySize*: Option[int] ## Number of message ids kept in causal history.
rateLimitEnabled*: Option[bool] ## Enable rate limiting.
rateLimitEpochPeriodSec*: Option[int] ## Rate-limit epoch length in seconds.
rateLimitMessagesPerEpoch*: Option[int] ## Messages allowed per rate-limit epoch.
ReliableChannelManager* = ref object ## Implements `ReliableChannelApi`. ReliableChannelManager* = ref object ## Implements `ReliableChannelApi`.
channels*: Table[ChannelId, ReliableChannel] ## read by `channels/api.nim` channels*: Table[ChannelId, ReliableChannel] ## read by `channels/api.nim`
conf*: ReliableChannelManagerConf
brokerCtx*: BrokerContext brokerCtx*: BrokerContext
proc new*( proc new*(
@ -37,7 +45,13 @@ proc new*(
conf: ReliableChannelManagerConf, conf: ReliableChannelManagerConf,
brokerCtx: BrokerContext = globalBrokerContext(), brokerCtx: BrokerContext = globalBrokerContext(),
): Result[T, string] = ): Result[T, string] =
return ok(T(channels: initTable[ChannelId, ReliableChannel](), brokerCtx: brokerCtx)) return ok(
T(
channels: initTable[ChannelId, ReliableChannel](),
conf: conf,
brokerCtx: brokerCtx,
)
)
proc start*(self: ReliableChannelManager): Result[void, string] = proc start*(self: ReliableChannelManager): Result[void, string] =
## Placeholder: per-channel listeners are installed in `ReliableChannel.new`, ## Placeholder: per-channel listeners are installed in `ReliableChannel.new`,

View File

@ -9,6 +9,7 @@
{.push raises: [].} {.push raises: [].}
import std/options
import results, chronos, chronicles import results, chronos, chronicles
# Each layer has a core module (type + new/start/stop) and an api/ folder whose # Each layer has a core module (type + new/start/stop) and an api/ folder whose
@ -41,6 +42,8 @@ import logos_delivery/messaging/api/[subscription, send]
export subscription, send export subscription, send
import logos_delivery/api/events/messaging_client_events import logos_delivery/api/events/messaging_client_events
export messaging_client_events export messaging_client_events
import logos_delivery/api/messaging_conf
export messaging_conf
# Reliable Channel layer # Reliable Channel layer
import logos_delivery/channels/reliable_channel_manager import logos_delivery/channels/reliable_channel_manager
@ -58,63 +61,68 @@ import logos_delivery/waku/factory/waku_conf
import logos_delivery/waku/factory/app_callbacks import logos_delivery/waku/factory/app_callbacks
import tools/confutils/cli_args import tools/confutils/cli_args
import logos_delivery/waku/node/health_monitor/online_monitor import logos_delivery/waku/node/health_monitor/online_monitor
import logos_delivery/api/logos_delivery_conf
export logos_delivery_conf
logScope: logScope:
topics = "logosdelivery" topics = "logosdelivery"
type type LogosDelivery* = ref object ## Entry point. Holds one instance of each API layer.
LogosDeliveryConf* = object waku*: Waku
## Aggregates the per-layer config objects. For now messagingClient*: MessagingClient
## the sub-configs are derived from `WakuConf`; richer per-layer configuration reliableChannelManager*: ReliableChannelManager
## (and how it is sourced) lands in a follow-up PR.
waku*: WakuConf
messaging*: MessagingClientConf
reliableChannel*: ReliableChannelManagerConf
LogosDelivery* = ref object ## Entry point. Holds one instance of each API layer.
waku*: Waku
messagingClient*: MessagingClient
reliableChannelManager*: ReliableChannelManager
proc init*(T: type LogosDeliveryConf, wakuConf: WakuConf): LogosDeliveryConf =
## Builds the aggregated config from a `WakuConf`. The messaging / reliable
## channel layers carry trivial config today; this is the seam where their
## dedicated config will be threaded through later.
LogosDeliveryConf(
waku: wakuConf,
messaging: MessagingClientConf(useP2PReliability: wakuConf.p2pReliability),
reliableChannel: ReliableChannelManagerConf(),
)
proc new*( proc new*(
T: type LogosDelivery, conf: WakuNodeConf, appCallbacks: AppCallbacks = nil T: type LogosDelivery, conf: LogosDeliveryConf, appCallbacks: AppCallbacks = nil
): Future[Result[LogosDelivery, string]] {.async.} = ): Future[Result[LogosDelivery, string]] {.async.} =
## Single entry point, from the CLI configuration type. Derives the aggregated ## Builds the stack bottom-up from a resolved per-layer config.
## per-layer config, then creates the full stack bottom-up so each layer can let wakuConf = conf.kernelConf.toWakuConf().valueOr:
## chain onto the one below.
let wakuConf = conf.toWakuConf().valueOr:
return err("failed to handle the configuration: " & error) return err("failed to handle the configuration: " & error)
let layerConf = LogosDeliveryConf.init(wakuConf) let waku = (await Waku.new(wakuConf, appCallbacks)).valueOr:
let waku = (await Waku.new(layerConf.waku, appCallbacks)).valueOr:
return err("failed to create Waku: " & error) return err("failed to create Waku: " & error)
let messagingClient = MessagingClient.new(layerConf.messaging, waku).valueOr: let messagingClient = MessagingClient.new(conf.messagingConf, waku).valueOr:
return err("failed to create MessagingClient: " & error) return err("failed to create MessagingClient: " & error)
let reliableChannelManager = ReliableChannelManager.new( let reliableChannelManager = ReliableChannelManager.new(
layerConf.reliableChannel, waku.brokerCtx conf.channelsConf, waku.brokerCtx
).valueOr: ).valueOr:
return err("failed to create ReliableChannelManager: " & error) return err("failed to create ReliableChannelManager: " & error)
return ok( return ok(
T( LogosDelivery(
waku: waku, waku: waku,
messagingClient: messagingClient, messagingClient: messagingClient,
reliableChannelManager: reliableChannelManager, reliableChannelManager: reliableChannelManager,
) )
) )
proc new*(
T: type LogosDelivery, conf: WakuNodeConf, appCallbacks: AppCallbacks = nil
): Future[Result[LogosDelivery, string]] {.async.} =
## Builds the full stack from a kernel `WakuNodeConf`.
return await LogosDelivery.new(
LogosDeliveryConf(
kernelConf: conf,
messagingConf: MessagingClientConf(),
channelsConf: ReliableChannelManagerConf(),
),
appCallbacks,
)
proc new*(
T: type LogosDelivery,
mode: WakuMode = WakuMode.Core,
preset: string = "",
messagingOverrides: MessagingClientConf = MessagingClientConf(),
channelsOverrides: ReliableChannelManagerConf = ReliableChannelManagerConf(),
appCallbacks: AppCallbacks = nil,
): Future[Result[LogosDelivery, string]] {.async.} =
## Messaging entry point (app dev). Builds the full stack from preset, mode and overrides.
let conf = LogosDeliveryConf.init(mode, preset, messagingOverrides, channelsOverrides).valueOr:
return err("failed to synthesize configuration: " & error)
return await LogosDelivery.new(conf, appCallbacks)
proc start*(self: LogosDelivery): Future[Result[void, string]] {.async.} = proc start*(self: LogosDelivery): Future[Result[void, string]] {.async.} =
## Starts each layer bottom-up: transport first, then messaging, then channels. ## Starts each layer bottom-up: transport first, then messaging, then channels.
if self.waku.isNil(): if self.waku.isNil():
@ -137,11 +145,13 @@ proc start*(self: LogosDelivery): Future[Result[void, string]] {.async.} =
proc stop*(self: LogosDelivery): Future[Result[void, string]] {.async.} = proc stop*(self: LogosDelivery): Future[Result[void, string]] {.async.} =
## Stops in reverse order so higher layers drain before their dependencies. ## Stops in reverse order so higher layers drain before their dependencies.
await self.reliableChannelManager.stop() if not self.reliableChannelManager.isNil():
await self.messagingClient.stop() await self.reliableChannelManager.stop()
if not self.messagingClient.isNil():
(await self.waku.stop()).isOkOr: await self.messagingClient.stop()
return err("failed to stop Waku: " & error) if not self.waku.isNil():
(await self.waku.stop()).isOkOr:
return err("failed to stop Waku: " & error)
return ok() return ok()

View File

@ -1,11 +1,17 @@
## Messaging layer core: the `MessagingClient` type plus its construction and ## Messaging layer core: the `MessagingClient` type plus its construction and
## lifecycle. The public operations (subscribe / unsubscribe / send) live in ## lifecycle. The public operations (subscribe / unsubscribe / send) live in
## `messaging/api.nim`. ## `messaging/api.nim`.
import std/[options, net]
import results, chronos import results, chronos
import confutils/defs
import libp2p/crypto/crypto
import logos_delivery/waku/common/logging
import logos_delivery/api/kernel_conf
import chronicles import chronicles
import import
logos_delivery/api/messaging_client_api, logos_delivery/api/messaging_client_api,
logos_delivery/waku/waku, logos_delivery/waku/waku,
logos_delivery/waku/factory/conf_builder/waku_conf_builder,
logos_delivery/messaging/delivery_service/[recv_service, send_service] logos_delivery/messaging/delivery_service/[recv_service, send_service]
# Surfaces the messaging API interface (and its Message* events) to consumers. # Surfaces the messaging API interface (and its Message* events) to consumers.
@ -13,10 +19,45 @@ export messaging_client_api
type type
MessagingClientConf* = object MessagingClientConf* = object
## Per-layer config object for the messaging API. clusterId* {.name: "cluster-id".}: Option[uint16] ## Network cluster id.
## Kept intentionally minimal for now; the full config surface lands in a numShardsInCluster* {.name: "num-shards-in-network".}: Option[uint16]
## follow-up PR. Today it only carries the p2p reliability toggle. ## Number of shards in the cluster.
useP2PReliability*: bool p2pTcpPort* {.name: "tcp-port".}: Option[Port] ## TCP listening port.
discv5UdpPort* {.name: "discv5-udp-port".}: Option[Port] ## discv5 UDP port.
websocketSupport* {.name: "websocket-support".}: Option[bool]
## Enable the websocket transport.
websocketPort* {.name: "websocket-port".}: Option[Port] ## Websocket listening port.
quicSupport* {.name: "quic-support".}: Option[bool] ## Enable the QUIC transport.
quicPort* {.name: "quic-port".}: Option[Port] ## QUIC (UDP) listening port.
listenIpv4* {.name: "listen-address".}: Option[IpAddress] ## Inbound bind address.
maxMessageSize* {.name: "max-msg-size".}: Option[string]
## Maximum accepted message size (e.g. "150 KiB").
entryNodes* {.name: "entry-node".}: Option[seq[string]]
## Bootstrap / connectivity nodes (enrtree or multiaddr).
ethRpcEndpoints* {.name: "rln-relay-eth-client-address".}: Option[seq[EthRpcUrl]]
## Ethereum RPC endpoints (required for RLN validation); multiple for fail-over.
rlnContractAddress* {.name: "rln-relay-eth-contract-address".}: Option[string]
## RLN contract address; when set, RLN validation is enabled.
rlnChainId* {.name: "rln-relay-chain-id".}: Option[uint]
## Chain id the RLN contract is deployed on.
rlnEpochSizeSec* {.name: "rln-relay-epoch-sec".}: Option[uint]
## RLN epoch size, in seconds.
reliabilityEnabled* {.name: "reliability".}: Option[bool]
## Enable store-based send reliability.
store*: Option[bool] ## Enable the store protocol.
storenode* {.name: "storenode".}: Option[string]
storeMessageDbUrl* {.name: "store-message-db-url".}: Option[string]
## Database connection URL for the store service's persistent storage.
storeMessageRetentionPolicy* {.name: "store-message-retention-policy".}:
Option[string] ## Store retention policy (e.g. "time:3600;size:1GB").
storeMaxNumDbConnections* {.name: "store-max-num-db-connections".}: Option[int]
## Maximum number of simultaneous store database connections.
logLevel* {.name: "log-level".}: Option[logging.LogLevel]
## Process log level (TRACE..FATAL); applied by the kernel on node creation.
logFormat* {.name: "log-format".}: Option[logging.LogFormat]
## Process log format (TEXT or JSON); applied by the kernel on node creation.
nodeKey* {.name: "nodekey".}: Option[crypto.PrivateKey]
## P2P node private key (64-char hex): stable identity / peerId across restarts.
MessagingClient* = ref object MessagingClient* = ref object
brokerCtx*: BrokerContext brokerCtx*: BrokerContext
@ -30,7 +71,8 @@ proc new*(
): Result[T, string] = ): Result[T, string] =
## The messaging layer chains onto Waku: it drives the underlying Waku kernel ## The messaging layer chains onto Waku: it drives the underlying Waku kernel
## for transport while exposing its own send/recv API. ## for transport while exposing its own send/recv API.
let sendService = ?SendService.new(conf.useP2PReliability, waku) let reliability = conf.reliabilityEnabled.get(DefaultP2pReliability)
let sendService = ?SendService.new(reliability, waku)
let recvService = RecvService.new(waku) let recvService = RecvService.new(waku)
return ok( return ok(
T( T(

View File

@ -170,7 +170,6 @@ type WakuConfBuilder* = object
relayShardedPeerManagement: Option[bool] relayShardedPeerManagement: Option[bool]
relayServiceRatio: Option[string] relayServiceRatio: Option[string]
circuitRelayClient: Option[bool] circuitRelayClient: Option[bool]
p2pReliability: Option[bool]
localStoragePath: Option[string] localStoragePath: Option[string]
@ -310,9 +309,6 @@ proc withRelayShardedPeerManagement*(
) = ) =
b.relayShardedPeerManagement = some(relayShardedPeerManagement) b.relayShardedPeerManagement = some(relayShardedPeerManagement)
proc withP2pReliability*(b: var WakuConfBuilder, p2pReliability: bool) =
b.p2pReliability = some(p2pReliability)
proc withLocalStoragePath*(b: var WakuConfBuilder, localStoragePath: string) = proc withLocalStoragePath*(b: var WakuConfBuilder, localStoragePath: string) =
b.localStoragePath = some(localStoragePath) b.localStoragePath = some(localStoragePath)
@ -462,10 +458,6 @@ proc applyNetworkPresetConf(builder: var WakuConfBuilder) =
checkSetPresetValueToField( checkSetPresetValueToField(
builder.mix, networkPresetConf.mix, "Mix was provided alongside a network conf" builder.mix, networkPresetConf.mix, "Mix was provided alongside a network conf"
) )
checkSetPresetValueToField(
builder.p2pReliability, networkPresetConf.p2pReliability,
"P2P Reliability was provided alongside a network conf",
)
# Process entry nodes from network config - classify and distribute # Process entry nodes from network config - classify and distribute
if networkPresetConf.entryNodes.len > 0: if networkPresetConf.entryNodes.len > 0:
@ -841,7 +833,6 @@ proc build*(
circuitRelayClient: builder.circuitRelayClient.get(DefaultCircuitRelayClient), circuitRelayClient: builder.circuitRelayClient.get(DefaultCircuitRelayClient),
staticNodes: builder.staticNodes, staticNodes: builder.staticNodes,
relayShardedPeerManagement: relayShardedPeerManagement, relayShardedPeerManagement: relayShardedPeerManagement,
p2pReliability: builder.p2pReliability.get(DefaultP2pReliability),
wakuFlags: wakuFlags, wakuFlags: wakuFlags,
localStoragePath: builder.localStoragePath.get(DefaultStoragePath), localStoragePath: builder.localStoragePath.get(DefaultStoragePath),
) )

View File

@ -155,8 +155,6 @@ type WakuConf* {.requiresInit.} = ref object
# TODO: use proper type # TODO: use proper type
relayServiceRatio*: string relayServiceRatio*: string
p2pReliability*: bool
localStoragePath*: string localStoragePath*: string
proc logConf*(conf: WakuConf) = proc logConf*(conf: WakuConf) =

View File

@ -3,6 +3,7 @@
import import
./test_entry_nodes, ./test_entry_nodes,
./test_node_conf, ./test_node_conf,
./test_conf,
./test_api_send, ./test_api_send,
./test_api_subscription, ./test_api_subscription,
./test_api_receive, ./test_api_receive,

View File

@ -91,9 +91,8 @@ suite "LM API health checking":
serviceNode.wakuRelay.subscribe(DefaultShard, dummyHandler) serviceNode.wakuRelay.subscribe(DefaultShard, dummyHandler)
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
var conf = defaultWakuNodeConf().valueOr: var conf = MessagingClientConf().toKernelConf(Core).valueOr:
raiseAssert error raiseAssert error
conf.mode = Core
conf.listenAddress = parseIpAddress("0.0.0.0") conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0) conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0) conf.discv5UdpPort = Port(0)
@ -272,9 +271,8 @@ suite "LM API health checking":
var edgeWaku: LogosDelivery var edgeWaku: LogosDelivery
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
var edgeConf = defaultWakuNodeConf().valueOr: var edgeConf = MessagingClientConf().toKernelConf(Edge).valueOr:
raiseAssert error raiseAssert error
edgeConf.mode = Edge
edgeConf.listenAddress = parseIpAddress("0.0.0.0") edgeConf.listenAddress = parseIpAddress("0.0.0.0")
edgeConf.tcpPort = Port(0) edgeConf.tcpPort = Port(0)
edgeConf.discv5UdpPort = Port(0) edgeConf.discv5UdpPort = Port(0)

View File

@ -21,6 +21,7 @@ import
] ]
import logos_delivery/waku/factory/waku_conf import logos_delivery/waku/factory/waku_conf
import tools/confutils/cli_args import tools/confutils/cli_args
import logos_delivery/api/messaging_conf
const TestTimeout = chronos.seconds(60) const TestTimeout = chronos.seconds(60)
@ -82,15 +83,13 @@ proc waitForConnectionStatus(
await EventConnectionStatusChange.dropListener(brokerCtx, handle) await EventConnectionStatusChange.dropListener(brokerCtx, handle)
proc createApiNodeConf(numShards: uint16 = 1): WakuNodeConf = proc createApiNodeConf(numShards: uint16 = 1): WakuNodeConf =
var conf = defaultWakuNodeConf().valueOr: var conf = MessagingClientConf().toKernelConf(messaging_conf.WakuMode.Core).valueOr:
raiseAssert error raiseAssert error
conf.mode = cli_args.WakuMode.Core
conf.listenAddress = parseIpAddress("0.0.0.0") conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0) conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0) conf.discv5UdpPort = Port(0)
conf.clusterId = some(3'u16) conf.clusterId = some(3'u16)
conf.numShardsInNetwork = numShards conf.numShardsInNetwork = numShards
conf.reliabilityEnabled = some(true)
conf.rest = false conf.rest = false
result = conf result = conf

View File

@ -8,6 +8,7 @@ import ../waku_archive/archive_utils
import logos_delivery, logos_delivery/waku/[waku_node, waku_core, waku_relay/protocol] import logos_delivery, logos_delivery/waku/[waku_node, waku_core, waku_relay/protocol]
import logos_delivery/waku/factory/waku_conf import logos_delivery/waku/factory/waku_conf
import tools/confutils/cli_args import tools/confutils/cli_args
import logos_delivery/api/messaging_conf
type SendEventOutcome {.pure.} = enum type SendEventOutcome {.pure.} = enum
Sent Sent
@ -119,16 +120,16 @@ proc validate(
for requestId in manager.errorRequestIds: for requestId in manager.errorRequestIds:
check requestId == expectedRequestId check requestId == expectedRequestId
proc createApiNodeConf(mode: cli_args.WakuMode = cli_args.WakuMode.Core): WakuNodeConf = proc createApiNodeConf(
var conf = defaultWakuNodeConf().valueOr: mode: messaging_conf.WakuMode = messaging_conf.WakuMode.Core
raiseAssert error ): WakuNodeConf =
conf.mode = mode var conf = MessagingClientConf().toKernelConf(mode).valueOr:
raiseAssert error
conf.listenAddress = parseIpAddress("0.0.0.0") conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0) conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0) conf.discv5UdpPort = Port(0)
conf.clusterId = some(3'u16) conf.clusterId = some(3'u16)
conf.numShardsInNetwork = 1 conf.numShardsInNetwork = 1
conf.reliabilityEnabled = some(true)
conf.rest = false conf.rest = false
result = conf result = conf
@ -353,7 +354,7 @@ suite "Waku API - Send":
## connected to a lightpush-capable peer must deliver through lightpush. ## connected to a lightpush-capable peer must deliver through lightpush.
var node: LogosDelivery var node: LogosDelivery
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
node = (await LogosDelivery.new(createApiNodeConf(cli_args.WakuMode.Edge))).valueOr: node = (await LogosDelivery.new(createApiNodeConf(messaging_conf.WakuMode.Edge))).valueOr:
raiseAssert error raiseAssert error
(await node.start()).isOkOr: (await node.start()).isOkOr:
raiseAssert "Failed to start Waku node: " & error raiseAssert "Failed to start Waku node: " & error
@ -388,7 +389,7 @@ suite "Waku API - Send":
## later retry must deliver the queued message. ## later retry must deliver the queued message.
var node: LogosDelivery var node: LogosDelivery
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
node = (await LogosDelivery.new(createApiNodeConf(cli_args.WakuMode.Edge))).valueOr: node = (await LogosDelivery.new(createApiNodeConf(messaging_conf.WakuMode.Edge))).valueOr:
raiseAssert error raiseAssert error
(await node.start()).isOkOr: (await node.start()).isOkOr:
raiseAssert "Failed to start Waku node: " & error raiseAssert "Failed to start Waku node: " & error
@ -479,7 +480,7 @@ suite "Waku API - Send":
var node: LogosDelivery var node: LogosDelivery
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
node = (await LogosDelivery.new(createApiNodeConf(cli_args.WakuMode.Edge))).valueOr: node = (await LogosDelivery.new(createApiNodeConf(messaging_conf.WakuMode.Edge))).valueOr:
raiseAssert error raiseAssert error
(await node.start()).isOkOr: (await node.start()).isOkOr:
raiseAssert "Failed to start Waku node: " & error raiseAssert "Failed to start Waku node: " & error

View File

@ -18,6 +18,7 @@ import
] ]
import logos_delivery/waku/factory/waku_conf import logos_delivery/waku/factory/waku_conf
import tools/confutils/cli_args import tools/confutils/cli_args
import logos_delivery/api/messaging_conf
const TestTimeout = chronos.seconds(10) const TestTimeout = chronos.seconds(10)
const NegativeTestTimeout = chronos.seconds(2) const NegativeTestTimeout = chronos.seconds(2)
@ -68,17 +69,15 @@ type TestNetwork = ref object
publisherPeerInfo: RemotePeerInfo publisherPeerInfo: RemotePeerInfo
proc createApiNodeConf( proc createApiNodeConf(
mode: cli_args.WakuMode = cli_args.WakuMode.Core, numShards: uint16 = 1 mode: messaging_conf.WakuMode = messaging_conf.WakuMode.Core, numShards: uint16 = 1
): WakuNodeConf = ): WakuNodeConf =
var conf = defaultWakuNodeConf().valueOr: var conf = MessagingClientConf().toKernelConf(mode).valueOr:
raiseAssert error raiseAssert error
conf.mode = mode
conf.listenAddress = parseIpAddress("0.0.0.0") conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0) conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0) conf.discv5UdpPort = Port(0)
conf.clusterId = some(3'u16) conf.clusterId = some(3'u16)
conf.numShardsInNetwork = numShards conf.numShardsInNetwork = numShards
conf.reliabilityEnabled = some(true)
conf.rest = false conf.rest = false
result = conf result = conf
@ -90,7 +89,7 @@ proc setupSubscriberNode(conf: WakuNodeConf): Future[LogosDelivery] {.async.} =
return node return node
proc setupNetwork( proc setupNetwork(
numShards: uint16 = 1, mode: cli_args.WakuMode = cli_args.WakuMode.Core numShards: uint16 = 1, mode: messaging_conf.WakuMode = messaging_conf.WakuMode.Core
): Future[TestNetwork] {.async.} = ): Future[TestNetwork] {.async.} =
var net = TestNetwork() var net = TestNetwork()
@ -100,7 +99,7 @@ proc setupNetwork(
"Failed to mount metadata" "Failed to mount metadata"
) )
(await net.publisher.mountRelay()).expect("Failed to mount relay") (await net.publisher.mountRelay()).expect("Failed to mount relay")
if mode == cli_args.WakuMode.Edge: if mode == messaging_conf.WakuMode.Edge:
await net.publisher.mountFilter() await net.publisher.mountFilter()
await net.publisher.mountLibp2pPing() await net.publisher.mountLibp2pPing()
await net.publisher.start() await net.publisher.start()
@ -119,7 +118,7 @@ proc setupNetwork(
"Failed to sub publisher" "Failed to sub publisher"
) )
if mode == cli_args.WakuMode.Edge: if mode == messaging_conf.WakuMode.Edge:
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
net.meshBuddy = newTestWakuNode(generateSecp256k1Key()) net.meshBuddy = newTestWakuNode(generateSecp256k1Key())
net.meshBuddy.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( net.meshBuddy.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect(
@ -474,7 +473,7 @@ suite "Messaging API, SubscriptionManager":
await verifyNetworkState(activeSubs) await verifyNetworkState(activeSubs)
asyncTest "Subscription API, edge node subscribe and receive message": asyncTest "Subscription API, edge node subscribe and receive message":
let net = await setupNetwork(1, cli_args.WakuMode.Edge) let net = await setupNetwork(1, messaging_conf.WakuMode.Edge)
defer: defer:
await net.teardown() await net.teardown()
@ -496,7 +495,7 @@ suite "Messaging API, SubscriptionManager":
check eventManager.receivedMessages[0].contentTopic == testTopic check eventManager.receivedMessages[0].contentTopic == testTopic
asyncTest "Subscription API, edge node ignores unsubscribed content topics": asyncTest "Subscription API, edge node ignores unsubscribed content topics":
let net = await setupNetwork(1, cli_args.WakuMode.Edge) let net = await setupNetwork(1, messaging_conf.WakuMode.Edge)
defer: defer:
await net.teardown() await net.teardown()
@ -518,7 +517,7 @@ suite "Messaging API, SubscriptionManager":
check eventManager.receivedMessages.len == 0 check eventManager.receivedMessages.len == 0
asyncTest "Subscription API, edge node unsubscribe stops message receipt": asyncTest "Subscription API, edge node unsubscribe stops message receipt":
let net = await setupNetwork(1, cli_args.WakuMode.Edge) let net = await setupNetwork(1, messaging_conf.WakuMode.Edge)
defer: defer:
await net.teardown() await net.teardown()
@ -543,7 +542,7 @@ suite "Messaging API, SubscriptionManager":
check eventManager.receivedMessages.len == 0 check eventManager.receivedMessages.len == 0
asyncTest "Subscription API, edge node overlapping topics isolation": asyncTest "Subscription API, edge node overlapping topics isolation":
let net = await setupNetwork(1, cli_args.WakuMode.Edge) let net = await setupNetwork(1, messaging_conf.WakuMode.Edge)
defer: defer:
await net.teardown() await net.teardown()
@ -572,7 +571,7 @@ suite "Messaging API, SubscriptionManager":
check eventManager.receivedMessages[0].contentTopic == topicB check eventManager.receivedMessages[0].contentTopic == topicB
asyncTest "Subscription API, edge node resubscribe after unsubscribe": asyncTest "Subscription API, edge node resubscribe after unsubscribe":
let net = await setupNetwork(1, cli_args.WakuMode.Edge) let net = await setupNetwork(1, messaging_conf.WakuMode.Edge)
defer: defer:
await net.teardown() await net.teardown()
@ -657,7 +656,7 @@ suite "Messaging API, SubscriptionManager":
await meshBuddy.connectToNodes(@[publisherPeerInfo]) await meshBuddy.connectToNodes(@[publisherPeerInfo])
let conf = createApiNodeConf(cli_args.WakuMode.Edge, numShards) let conf = createApiNodeConf(messaging_conf.WakuMode.Edge, numShards)
var subscriber: LogosDelivery var subscriber: LogosDelivery
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
subscriber = subscriber =
@ -785,7 +784,7 @@ suite "Messaging API, SubscriptionManager":
await meshBuddy.connectToNodes(@[publisherPeerInfo]) await meshBuddy.connectToNodes(@[publisherPeerInfo])
await sparePeer.connectToNodes(@[publisherPeerInfo]) await sparePeer.connectToNodes(@[publisherPeerInfo])
let conf = createApiNodeConf(cli_args.WakuMode.Edge, numShards) let conf = createApiNodeConf(messaging_conf.WakuMode.Edge, numShards)
var subscriber: LogosDelivery var subscriber: LogosDelivery
lockNewGlobalBrokerContext: lockNewGlobalBrokerContext:
subscriber = subscriber =

261
tests/api/test_conf.nim Normal file
View File

@ -0,0 +1,261 @@
{.used.}
import std/[options, net], results, chronos, testutils/unittests
import brokers/broker_context
import logos_delivery
import logos_delivery/api/logos_delivery_conf_json
import logos_delivery/waku/factory/[waku_conf, networks_config]
import logos_delivery/waku/common/logging
suite "MessagingClientConf - mode expansion (toKernelConf)":
test "Core mode enables relay + service protocols":
let kc = MessagingClientConf().toKernelConf(WakuMode.Core).valueOr:
raiseAssert error
check:
kc.relay == true
kc.filter == true
kc.lightpush == true
kc.discv5Discovery == some(true)
kc.peerExchange == true
kc.rendezvous == true
test "Edge mode is client-only (no relay/filter/lightpush/store)":
let kc = MessagingClientConf().toKernelConf(WakuMode.Edge).valueOr:
raiseAssert error
check:
kc.relay == false
kc.filter == false
kc.lightpush == false
kc.store == false
kc.peerExchange == true
kc.discv5Discovery == some(true) # discovery stays on; mode does not force it off
suite "MessagingClientConf - field mapping + transport policy":
test "set fields are written to their kernel counterparts":
let mc = MessagingClientConf(
clusterId: some(3'u16),
numShardsInCluster: some(4'u16),
maxMessageSize: some("150KiB"),
)
let kc = mc.toKernelConf(WakuMode.Core).valueOr:
raiseAssert error
check:
kc.clusterId == some(3'u16)
kc.numShardsInNetwork == 4
kc.maxMessageSize == "150KiB"
test "messaging transport defaults: ephemeral ports, websocket off, quic on":
let kc = MessagingClientConf().toKernelConf(WakuMode.Core).valueOr:
raiseAssert error
check:
kc.tcpPort == Port(0)
kc.discv5UdpPort == Port(0)
kc.websocketSupport == false
kc.quicSupport == true
test "explicit transport overrides win":
let mc = MessagingClientConf(
p2pTcpPort: some(Port(1234)),
websocketSupport: some(true),
quicSupport: some(false),
)
let kc = mc.toKernelConf(WakuMode.Core).valueOr:
raiseAssert error
check:
kc.tcpPort == Port(1234)
kc.websocketSupport == true
kc.quicSupport == false
suite "MessagingClientConf - preset resolution":
test "resolvePreset lifts only messaging-exclusive fields, not kernel-mirrored ones":
let mc = resolvePreset("twn").valueOr:
raiseAssert error
check:
mc.reliabilityEnabled.isSome()
mc.clusterId.isNone()
mc.maxMessageSize.isNone()
test "resolvePreset does not lift entryNodes":
let mc = resolvePreset("logostest").valueOr:
raiseAssert error
check mc.entryNodes.isNone()
test "empty preset resolves to an empty config":
let mc = resolvePreset("").valueOr:
raiseAssert error
check:
mc.clusterId.isNone()
mc.maxMessageSize.isNone()
test "a messaging override of a kernel-mirrored field wins over the preset":
let presetConf = resolvePreset("logos.dev").valueOr:
raiseAssert error
let merged = merge(presetConf, MessagingClientConf(numShardsInCluster: some(1'u16)))
var kernelConf = toKernelConf(merged, WakuMode.Core).valueOr:
raiseAssert error
kernelConf.preset = "logos.dev"
let wakuConf = kernelConf.toWakuConf().valueOr:
raiseAssert error
check:
wakuConf.shardingConf.kind == AutoSharding
wakuConf.shardingConf.numShardsInCluster == 1
suite "MessagingClientConf - merge (override wins)":
test "a set override field wins; unset keeps the base":
let base = MessagingClientConf(clusterId: some(1'u16), maxMessageSize: some("1MB"))
let overrides = MessagingClientConf(clusterId: some(2'u16))
let mc = merge(base, overrides)
check:
mc.clusterId == some(2'u16) # override wins
mc.maxMessageSize == some("1MB") # base preserved
suite "parseLogosDeliveryConf - JSON parsing":
test "empty object -> Core (default), empty preset, empty overrides":
let lc = parseLogosDeliveryConf("{}").valueOr:
raiseAssert error
check:
lc.kernelConf.relay == true # Core enables relay
lc.kernelConf.preset == ""
lc.messagingConf.clusterId.isNone()
test "mode + preset are parsed":
let lc = parseLogosDeliveryConf("""{"mode": "Edge", "preset": "logostest"}""").valueOr:
raiseAssert error
check:
lc.kernelConf.preset == "logostest"
lc.kernelConf.relay == false # Edge disables relay
test "messagingOverrides parsed into the partial":
let lc = parseLogosDeliveryConf(
"""{"mode": "Core", "messagingOverrides": {"clusterId": 7, "reliabilityEnabled": true}}"""
).valueOr:
raiseAssert error
check:
lc.messagingConf.clusterId == some(7'u16)
lc.messagingConf.reliabilityEnabled == some(true)
test "channelsOverrides parsed into the partial":
let lc = parseLogosDeliveryConf(
"""{"channelsOverrides": {"rateLimitEnabled": true, "sdsMaxRetransmissions": 9}}"""
).valueOr:
raiseAssert error
check:
lc.channelsConf.rateLimitEnabled == some(true)
lc.channelsConf.sdsMaxRetransmissions == some(9)
test "invalid mode is rejected (Core or Edge only)":
check parseLogosDeliveryConf("""{"mode": "bogus"}""").isErr()
check parseLogosDeliveryConf("""{"mode": "noMode"}""").isErr()
check parseLogosDeliveryConf("""{"mode": ""}""").isErr()
test "invalid JSON is rejected":
check parseLogosDeliveryConf("{ not json }").isErr()
test "unknown top-level keys are rejected":
check parseLogosDeliveryConf("""{"logLevel": "INFO", "mode": "Core"}""").isErr()
test "override keys accept CLI switch names":
let lc = parseLogosDeliveryConf(
"""{"messagingOverrides": {"cluster-id": 7, "reliability": true, "tcp-port": 1234}}"""
).valueOr:
raiseAssert error
check:
lc.messagingConf.clusterId == some(7'u16)
lc.messagingConf.reliabilityEnabled == some(true)
lc.messagingConf.p2pTcpPort == some(Port(1234))
test "unknown keys inside an overrides body are rejected":
check parseLogosDeliveryConf("""{"messagingOverrides": {"bogusKey": 1}}""").isErr()
check parseLogosDeliveryConf("""{"channelsOverrides": {"bogusKey": 1}}""").isErr()
test "a field set via both its name and its switch name is rejected":
check parseLogosDeliveryConf(
"""{"messagingOverrides": {"clusterId": 5, "cluster-id": 5}}"""
)
.isErr()
test "logLevel and nodeKey parse via switch names and map to the kernel":
let lc = parseLogosDeliveryConf(
"""{"messagingOverrides": {"log-level": "DEBUG", "log-format": "JSON", "nodekey": "0d714a1fada214dead6dc9c7274581ec20ff292451866e7d6d677dc818e8ccd2"}}"""
).valueOr:
raiseAssert error
check:
lc.kernelConf.logLevel == logging.LogLevel.DEBUG
lc.kernelConf.logFormat == logging.LogFormat.JSON
lc.kernelConf.nodekey.isSome()
test "a null value leaves the field unset":
let lc = parseLogosDeliveryConf(
"""{"messagingOverrides": {"store": null, "clusterId": 7}}"""
).valueOr:
raiseAssert error
check:
lc.messagingConf.store.isNone()
lc.messagingConf.clusterId == some(7'u16)
test "an invalid Ethereum RPC URL is rejected at parse time":
check parseLogosDeliveryConf(
"""{"messagingOverrides": {"rln-relay-eth-client-address": ["ws://node:8546"]}}"""
)
.isErr()
let lc = parseLogosDeliveryConf(
"""{"messagingOverrides": {"rln-relay-eth-client-address": ["http://localhost:8540/"]}}"""
).valueOr:
raiseAssert error
check lc.kernelConf.ethClientUrls.len == 1
test "store backend fields parse and map to the kernel":
let lc = parseLogosDeliveryConf(
"""{"messagingOverrides": {"store": true, "store-message-db-url": "sqlite://test.db", "store-message-retention-policy": "time:3600", "store-max-num-db-connections": 7}}"""
).valueOr:
raiseAssert error
check:
lc.kernelConf.store == true
lc.kernelConf.storeMessageDbUrl == "sqlite://test.db"
lc.kernelConf.storeMessageRetentionPolicy == "time:3600"
lc.kernelConf.storeMaxNumDbConnections == 7
suite "LogosDelivery.new - construction (the app-dev entry)":
asyncTest "builds the full messaging stack from mode + overrides":
var node: LogosDelivery
lockNewGlobalBrokerContext:
node = (
await LogosDelivery.new(
WakuMode.Core,
"",
MessagingClientConf(
clusterId: some(3'u16),
numShardsInCluster: some(1'u16),
listenIpv4: some(parseIpAddress("0.0.0.0")),
reliabilityEnabled: some(true),
),
)
).valueOr:
raiseAssert error
check:
not node.waku.isNil()
not node.messagingClient.isNil()
not node.reliableChannelManager.isNil()
(await node.stop()).expect("stop")
asyncTest "builds from a network preset":
var node: LogosDelivery
lockNewGlobalBrokerContext:
node = (
await LogosDelivery.new(
WakuMode.Core,
"logostest",
MessagingClientConf(listenIpv4: some(parseIpAddress("0.0.0.0"))),
)
).valueOr:
raiseAssert error
check not node.waku.isNil()
(await node.stop()).expect("stop")
suite "MessagingClientConf - store override":
test "store opt-in overrides the mode default; protocol flags follow the mode":
let kc = MessagingClientConf(store: some(true)).toKernelConf(WakuMode.Edge).valueOr:
raiseAssert error
check:
kc.store == true # Edge defaults store off; the explicit opt-in wins
kc.relay == false # protocols are owned by the mode, not overridable

View File

@ -1,196 +1,10 @@
{.used.} {.used.}
import std/[options, strutils], results, stint, testutils/unittests import std/options, results, testutils/unittests
import json_serialization, confutils, confutils/std/net
import import
tools/confutils/cli_args, tools/confutils/cli_args,
tools/confutils/conf_from_json,
logos_delivery/api/api_conf,
logos_delivery/waku/factory/waku_conf, logos_delivery/waku/factory/waku_conf,
logos_delivery/waku/factory/networks_config, logos_delivery/waku/factory/networks_config
logos_delivery/waku/factory/conf_builder/conf_builder,
logos_delivery/waku/common/logging
suite "WakuNodeConf - mode-driven toWakuConf":
test "Core mode enables service protocols":
## Given
var conf = defaultWakuNodeConf().valueOr:
raiseAssert error
conf.mode = Core
conf.clusterId = some(1'u16)
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == true
wakuConf.lightPush == true
wakuConf.peerExchangeService == true
wakuConf.rendezvous == true
wakuConf.clusterId == 1
test "Edge mode disables service protocols":
## Given
var conf = defaultWakuNodeConf().valueOr:
raiseAssert error
conf.mode = Edge
conf.clusterId = some(1'u16)
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == false
wakuConf.lightPush == false
wakuConf.filterServiceConf.isSome() == false
wakuConf.storeServiceConf.isSome() == false
wakuConf.peerExchangeService == true
test "noMode uses explicit CLI flags as-is":
## Given
var conf = defaultWakuNodeConf().valueOr:
raiseAssert error
conf.mode = cli_args.WakuMode.noMode
conf.relay = true
conf.lightpush = false
conf.clusterId = some(5'u16)
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == true
wakuConf.lightPush == false
wakuConf.clusterId == 5
test "Core mode overrides individual protocol flags":
## Given - user sets relay=false but mode=Core should override
var conf = defaultWakuNodeConf().valueOr:
raiseAssert error
conf.mode = Core
conf.relay = false # will be overridden by Core mode
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == true # mode overrides
suite "WakuNodeConf - JSON parsing with fieldPairs":
test "Empty JSON produces valid default conf":
## Given / When
let confRes = parseNodeConfFromJson("{}")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.mode == cli_args.WakuMode.noMode
conf.clusterId.isNone()
conf.logLevel == logging.LogLevel.INFO
test "JSON with mode and clusterId":
## Given / When
let confRes = parseNodeConfFromJson("""{"mode": "Core", "clusterId": 42}""")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.mode == Core
conf.clusterId == some(42'u16)
test "JSON with Edge mode":
## Given / When
let confRes = parseNodeConfFromJson("""{"mode": "Edge"}""")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.mode == Edge
test "JSON with logLevel":
## Given / When
let confRes = parseNodeConfFromJson("""{"logLevel": "DEBUG"}""")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.logLevel == logging.LogLevel.DEBUG
test "JSON with sharding config":
## Given / When
let confRes =
parseNodeConfFromJson("""{"clusterId": 99, "numShardsInNetwork": 16}""")
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.clusterId == some(99'u16)
conf.numShardsInNetwork == 16
test "JSON with unknown fields is rejected":
## Given / When
let confRes = parseNodeConfFromJson("""{"unknownField": true, "clusterId": 5}""")
## Then - the parser rejects unrecognized config keys
check confRes.isErr()
test "JSON config keys accepts cli_args pragma names":
## Given / When
let confRes = parseNodeConfFromJson(
"""{"cluster-id": 7, "reliability": true, "staticnode": ["/ip4/127.0.0.1/tcp/60000"]}"""
)
## Then
require confRes.isOk()
let conf = confRes.get()
check:
conf.clusterId == some(7'u16)
conf.reliabilityEnabled == some(true)
conf.staticNodes == @["/ip4/127.0.0.1/tcp/60000"]
test "JSON config accepts field-name form":
## Given / When
let confRes = parseNodeConfFromJson("""{"reliabilityEnabled": true}""")
## Then
require confRes.isOk()
check confRes.get().reliabilityEnabled == some(true)
test "JSON config rejects option set via both field name and cli name":
## Given / When
let confRes = parseNodeConfFromJson("""{"clusterId": 5, "cluster-id": 5}""")
## Then
require confRes.isErr()
check "set twice" in confRes.error
test "Invalid JSON syntax returns error":
## Given / When
let confRes = parseNodeConfFromJson("{ not valid json }")
## Then
check confRes.isErr()
suite "WakuNodeConf - preset integration": suite "WakuNodeConf - preset integration":
test "TWN preset applies TheWakuNetworkConf": test "TWN preset applies TheWakuNetworkConf":
@ -260,6 +74,25 @@ suite "WakuNodeConf - preset integration":
wakuConf.shardingConf.numShardsInCluster == 1 wakuConf.shardingConf.numShardsInCluster == 1
wakuConf.rlnRelayConf.isNone() wakuConf.rlnRelayConf.isNone()
test "Invalid preset returns error":
## Given
var conf = defaultWakuNodeConf().valueOr:
raiseAssert error
conf.preset = "status.prod"
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.clusterId == 16
wakuConf.shardingConf.kind == AutoSharding
wakuConf.shardingConf.numShardsInCluster == 1
wakuConf.rlnRelayConf.isNone()
test "Invalid preset returns error": test "Invalid preset returns error":
## Given ## Given
var conf = defaultWakuNodeConf().valueOr: var conf = defaultWakuNodeConf().valueOr:
@ -271,202 +104,3 @@ suite "WakuNodeConf - preset integration":
## Then ## Then
check wakuConfRes.isErr() check wakuConfRes.isErr()
suite "WakuNodeConf JSON -> WakuConf integration":
test "Core mode JSON config produces valid WakuConf":
## Given
let confRes = parseNodeConfFromJson(
"""{"mode": "Core", "clusterId": 55, "numShardsInNetwork": 6}"""
)
require confRes.isOk()
let conf = confRes.get()
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == true
wakuConf.lightPush == true
wakuConf.peerExchangeService == true
wakuConf.clusterId == 55
wakuConf.shardingConf.numShardsInCluster == 6
test "Edge mode JSON config produces valid WakuConf":
## Given
let confRes = parseNodeConfFromJson("""{"mode": "Edge", "clusterId": 1}""")
require confRes.isOk()
let conf = confRes.get()
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == false
wakuConf.lightPush == false
wakuConf.peerExchangeService == true
test "JSON with preset produces valid WakuConf":
## Given
let confRes = parseNodeConfFromJson("""{"mode": "Core", "preset": "logosdev"}""")
require confRes.isOk()
let conf = confRes.get()
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.clusterId == 2
wakuConf.relay == true
test "JSON with static nodes":
## Given
let confRes = parseNodeConfFromJson(
"""{"mode": "Core", "clusterId": 42, "staticnodes": ["/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc"]}"""
)
require confRes.isOk()
let conf = confRes.get()
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.staticNodes.len == 1
test "JSON with max message size":
## Given
let confRes =
parseNodeConfFromJson("""{"clusterId": 42, "maxMessageSize": "100KiB"}""")
require confRes.isOk()
let conf = confRes.get()
## When
let wakuConfRes = conf.toWakuConf()
## Then
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.maxMessageSizeBytes == 100'u64 * 1024'u64
# ---- Deprecated NodeConfig tests (kept for backward compatibility) ----
{.push warning[Deprecated]: off.}
import logos_delivery/api/api_conf
suite "NodeConfig (deprecated) - toWakuConf":
test "Minimal configuration":
let nodeConfig = NodeConfig.init(ethRpcEndpoints = @["http://someaddress"])
let wakuConfRes = api_conf.toWakuConf(nodeConfig)
let wakuConf = wakuConfRes.valueOr:
raiseAssert error
wakuConf.validate().isOkOr:
raiseAssert error
check:
wakuConf.clusterId == 1
wakuConf.shardingConf.numShardsInCluster == 8
wakuConf.staticNodes.len == 0
test "Edge mode configuration":
let protocolsConfig = ProtocolsConfig.init(entryNodes = @[], clusterId = 1)
let nodeConfig =
NodeConfig.init(mode = api_conf.WakuMode.Edge, protocolsConfig = protocolsConfig)
let wakuConfRes = api_conf.toWakuConf(nodeConfig)
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == false
wakuConf.lightPush == false
wakuConf.peerExchangeService == true
test "Core mode configuration":
let protocolsConfig = ProtocolsConfig.init(entryNodes = @[], clusterId = 1)
let nodeConfig =
NodeConfig.init(mode = api_conf.WakuMode.Core, protocolsConfig = protocolsConfig)
let wakuConfRes = api_conf.toWakuConf(nodeConfig)
require wakuConfRes.isOk()
let wakuConf = wakuConfRes.get()
require wakuConf.validate().isOk()
check:
wakuConf.relay == true
wakuConf.lightPush == true
wakuConf.peerExchangeService == true
{.pop.}
suite "WakuConfBuilder - store retention policies":
test "Multiple retention policies":
## Given
var b = WakuConfBuilder.init()
b.withP2pTcpPort(0'u16)
b.storeServiceConf.withEnabled(true)
b.storeServiceConf.withDbUrl("sqlite://test.db")
b.storeServiceConf.withRetentionPolicies(
"time:86400 ; capacity:10000; size : 50GB"
)
## When
let wakuConf = b.build().valueOr:
raiseAssert error
## Then
require wakuConf.storeServiceConf.isSome()
let storeConf = wakuConf.storeServiceConf.get()
check storeConf.retentionPolicies == @["time:86400", "capacity:10000", "size:50GB"]
test "Duplicated retention policies returns error":
## Given
var b = WakuConfBuilder.init()
b.storeServiceConf.withEnabled(true)
b.storeServiceConf.withDbUrl("sqlite://test.db")
b.storeServiceConf.withRetentionPolicies("time:86400;time:800;capacity:10000")
## When
let wakuConfRes = b.build()
check wakuConfRes.isErr()
check wakuConfRes.error.contains("duplicated retention policy type")
test "Incorrect retention policy type returns error":
## Given
var b = WakuConfBuilder.init()
b.storeServiceConf.withEnabled(true)
b.storeServiceConf.withDbUrl("sqlite://test.db")
b.storeServiceConf.withRetentionPolicies("capaity:10000")
## When
let wakuConfRes = b.build()
## Then
check wakuConfRes.isErr()
check wakuConfRes.error.contains("unknown retention policy type")
test "Store disabled - no retention policy applied":
## Given
var b = WakuConfBuilder.init()
b.withP2pTcpPort(0'u16)
# storeServiceConf not enabled
## When
let wakuConf = b.build().valueOr:
raiseAssert error
## Then
check wakuConf.storeServiceConf.isNone()

View File

@ -13,6 +13,7 @@ import logos_delivery/waku/factory/waku_conf
import logos_delivery/api/events/messaging_client_events as waku_message_events import logos_delivery/api/events/messaging_client_events as waku_message_events
import logos_delivery/api/messaging_client_api import logos_delivery/api/messaging_client_api
import tools/confutils/cli_args import tools/confutils/cli_args
import logos_delivery/api/messaging_conf
import logos_delivery/channels/reliable_channel_manager import logos_delivery/channels/reliable_channel_manager
import logos_delivery/channels/encryption/noop_encryption import logos_delivery/channels/encryption/noop_encryption
@ -27,15 +28,13 @@ import snapshot_codec
const TestTimeout = chronos.seconds(15) const TestTimeout = chronos.seconds(15)
proc createApiNodeConf(): WakuNodeConf = proc createApiNodeConf(): WakuNodeConf =
var conf = defaultWakuNodeConf().valueOr: var conf = MessagingClientConf().toKernelConf(messaging_conf.WakuMode.Core).valueOr:
raiseAssert error raiseAssert error
conf.mode = cli_args.WakuMode.Core
conf.listenAddress = parseIpAddress("0.0.0.0") conf.listenAddress = parseIpAddress("0.0.0.0")
conf.tcpPort = Port(0) conf.tcpPort = Port(0)
conf.discv5UdpPort = Port(0) conf.discv5UdpPort = Port(0)
conf.clusterId = some(3'u16) conf.clusterId = some(3'u16)
conf.numShardsInNetwork = 1 conf.numShardsInNetwork = 1
conf.reliabilityEnabled = some(true)
conf.rest = false conf.rest = false
return conf return conf
@ -166,7 +165,7 @@ suite "Reliable Channel - ingress":
suite "Reliable Channel - send state machine": suite "Reliable Channel - send state machine":
asyncTest "MessageSentEvent finalises the channelReqId as Sent": asyncTest "MessageSentEvent finalises the channelReqId as Sent":
## Drives the real send pipeline (`send` -> segmentation -> SDS -> ## Drives the real send pipeline (`send` -> segmentation -> SDS ->
## rate_limit -> encrypt -> dispatch) via a fake `SendHandler` that ## rate_limit -> encrypt -> dispatch) via a fake `MessagingSend` provider that
## returns a canned `RequestId` instead of hitting the network. ## returns a canned `RequestId` instead of hitting the network.
## Emitting the delivery-layer `MessageSentEvent` must drive the ## Emitting the delivery-layer `MessageSentEvent` must drive the
## channel-level state machine through `Confirmed` and produce a ## channel-level state machine through `Confirmed` and produce a
@ -234,7 +233,7 @@ suite "Reliable Channel - send state machine":
## Two `send()` calls -> two independent `channelReqId`s, each with ## Two `send()` calls -> two independent `channelReqId`s, each with
## one segment under the current segmentation skeleton ## one segment under the current segmentation skeleton
## (`performSegmentation` always emits exactly one segment). The ## (`performSegmentation` always emits exactly one segment). The
## fake `SendHandler` returns distinct `messagingReqId`s; finalising ## fake `MessagingSend` provider returns distinct `messagingReqId`s; finalising
## the first emits `ChannelMessageSentEvent` for its `channelReqId`, ## the first emits `ChannelMessageSentEvent` for its `channelReqId`,
## finalising the second as a failure emits `ChannelMessageErrorEvent` ## finalising the second as a failure emits `ChannelMessageErrorEvent`
## for the other. ## for the other.
@ -329,7 +328,7 @@ suite "Reliable Channel - send state machine":
## `channelReqId` cannot be produced through the real pipeline. ## `channelReqId` cannot be produced through the real pipeline.
## Implement once segmentation does real chunking: send a payload ## Implement once segmentation does real chunking: send a payload
## larger than `DefaultSegmentSizeBytes`, capture the N ## larger than `DefaultSegmentSizeBytes`, capture the N
## `messagingReqId`s from a fake `SendHandler`, finalise some, and ## `messagingReqId`s from a fake `MessagingSend` provider, finalise some, and
## assert prune only fires once every sibling is final. ## assert prune only fires once every sibling is final.
skip() skip()

View File

@ -238,7 +238,7 @@ suite "Health Monitor - events":
node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA) node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA)
) )
let ds = MessagingClient let ds = MessagingClient
.new(MessagingClientConf(useP2PReliability: false), waku) .new(MessagingClientConf(reliabilityEnabled: some(false)), waku)
.expect("Failed to create MessagingClient") .expect("Failed to create MessagingClient")
ds.start().expect("Failed to start MessagingClient") ds.start().expect("Failed to start MessagingClient")
@ -349,7 +349,7 @@ suite "Health Monitor - events":
node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA) node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA)
) )
let ds = MessagingClient let ds = MessagingClient
.new(MessagingClientConf(useP2PReliability: false), waku) .new(MessagingClientConf(reliabilityEnabled: some(false)), waku)
.expect("Failed to create MessagingClient") .expect("Failed to create MessagingClient")
ds.start().expect("Failed to start MessagingClient") ds.start().expect("Failed to start MessagingClient")
let subMgr = nodeA.subscriptionManager let subMgr = nodeA.subscriptionManager

View File

@ -12,9 +12,8 @@ import logos_delivery/waku/factory/conf_builder/conf_builder
suite "LogosDelivery API - Create node": suite "LogosDelivery API - Create node":
asyncTest "Create node with minimal configuration": asyncTest "Create node with minimal configuration":
## Given ## Given
var nodeConf = defaultWakuNodeConf().valueOr: var nodeConf = MessagingClientConf().toKernelConf(Core).valueOr:
raiseAssert "defaultWakuNodeConf failed: " & error raiseAssert "toKernelConf failed: " & error
nodeConf.mode = Core
nodeConf.clusterId = some(3'u16) nodeConf.clusterId = some(3'u16)
nodeConf.rest = false nodeConf.rest = false
@ -32,9 +31,8 @@ suite "LogosDelivery API - Create node":
asyncTest "Create node with full configuration": asyncTest "Create node with full configuration":
## Given ## Given
var nodeConf = defaultWakuNodeConf().valueOr: var nodeConf = MessagingClientConf().toKernelConf(Core).valueOr:
raiseAssert "defaultWakuNodeConf failed: " & error raiseAssert "toKernelConf failed: " & error
nodeConf.mode = Core
nodeConf.clusterId = some(99'u16) nodeConf.clusterId = some(99'u16)
nodeConf.rest = false nodeConf.rest = false
nodeConf.numShardsInNetwork = 16 nodeConf.numShardsInNetwork = 16
@ -64,9 +62,8 @@ suite "LogosDelivery API - Create node":
asyncTest "Create node with mixed entry nodes (enrtree, multiaddr)": asyncTest "Create node with mixed entry nodes (enrtree, multiaddr)":
## Given ## Given
var nodeConf = defaultWakuNodeConf().valueOr: var nodeConf = MessagingClientConf().toKernelConf(Core).valueOr:
raiseAssert "defaultWakuNodeConf failed: " & error raiseAssert "toKernelConf failed: " & error
nodeConf.mode = Core
nodeConf.clusterId = some(42'u16) nodeConf.clusterId = some(42'u16)
nodeConf.rest = false nodeConf.rest = false
nodeConf.entryNodes = @[ nodeConf.entryNodes = @[

View File

@ -60,11 +60,6 @@ type StartUpCommand* = enum
noCommand # default, runs waku noCommand # default, runs waku
generateRlnKeystore # generates a new RLN keystore 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 type WakuNodeConf* = object
configFile* {. configFile* {.
desc: "Loads configuration from a TOML file (cmd-line parameters take precedence)", desc: "Loads configuration from a TOML file (cmd-line parameters take precedence)",
@ -166,13 +161,6 @@ type WakuNodeConf* = object
.}: seq[ProtectedShard] .}: seq[ProtectedShard]
## General node config ## 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* {. preset* {.
desc: 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.", "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.",
@ -442,7 +430,7 @@ hence would have reachability issues.""",
## Filter config ## Filter config
filter* {. filter* {.
desc: "Enable filter protocol: true|false", defaultValue: false, name: "filter" desc: "Enable filter protocol: true|false", defaultValue: true, name: "filter"
.}: bool .}: bool
filternode* {. filternode* {.
@ -474,7 +462,7 @@ hence would have reachability issues.""",
## Lightpush config ## Lightpush config
lightpush* {. lightpush* {.
desc: "Enable lightpush protocol: true|false", desc: "Enable lightpush protocol: true|false",
defaultValue: false, defaultValue: true,
name: "lightpush" name: "lightpush"
.}: bool .}: bool
@ -484,16 +472,6 @@ hence would have reachability issues.""",
name: "lightpushnode" name: "lightpushnode"
.}: string .}: 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 HTTP config
rest* {. rest* {.
desc: "Enable Waku REST HTTP server: true|false", desc: "Enable Waku REST HTTP server: true|false",
@ -577,12 +555,9 @@ hence would have reachability issues.""",
.}: string .}: string
## Discovery v5 config ## Discovery v5 config
# Option-typed; desc states the default since the CLI can't auto-show it for none().
discv5Discovery* {. discv5Discovery* {.
desc: desc: "Enable discovering nodes via Node Discovery v5. Default is true.",
"Enable discovering nodes via Node Discovery v5. Default is " & defaultValue: some(true),
$DefaultDiscv5Enabled & ".",
defaultValue: none(bool),
name: "discv5-discovery" name: "discv5-discovery"
.}: Option[bool] .}: Option[bool]
@ -977,7 +952,7 @@ proc toKeystoreGeneratorConf*(n: WakuNodeConf): RlnKeystoreGeneratorConf =
credPassword: n.rlnRelayCredPassword, credPassword: n.rlnRelayCredPassword,
) )
proc toNetworkPresetConf( proc toNetworkPresetConf*(
preset: string, clusterId: Option[uint16] preset: string, clusterId: Option[uint16]
): ConfResult[Option[NetworkPresetConf]] = ): ConfResult[Option[NetworkPresetConf]] =
var lcPreset = toLowerAscii(preset) var lcPreset = toLowerAscii(preset)
@ -1146,8 +1121,6 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
b.filterServiceConf.withMaxCriteria(n.filterMaxCriteria) b.filterServiceConf.withMaxCriteria(n.filterMaxCriteria)
b.withLightPush(n.lightpush) b.withLightPush(n.lightpush)
if n.reliabilityEnabled.isSome():
b.withP2pReliability(n.reliabilityEnabled.get())
b.restServerConf.withEnabled(n.rest) b.restServerConf.withEnabled(n.rest)
b.restServerConf.withListenAddress(n.restAddress) b.restServerConf.withListenAddress(n.restAddress)
@ -1190,6 +1163,12 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
if n.rateLimits.len > 0: if n.rateLimits.len > 0:
b.rateLimitConf.withRateLimits(n.rateLimits) 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) b.withLocalStoragePath(n.localStoragePath)
@ -1206,25 +1185,4 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] =
chronos.seconds(n.kadServiceLookupIntervalSec.int64) 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() return b.build()

View File

@ -1,6 +1,9 @@
import std/[json, macros, strutils, tables] import std/[json, macros, options, strutils, tables]
import confutils, confutils/defs, confutils/std/net, results import confutils, confutils/defs, confutils/std/net, results
import ./cli_args
# The shared JSON walker is `raises: []` so the messaging FFI parser (also
# `raises: []`) can build on it.
{.push raises: [].}
proc collectJsonFields*( proc collectJsonFields*(
jsonNode: JsonNode jsonNode: JsonNode
@ -12,7 +15,7 @@ proc collectJsonFields*(
for key, value in jsonNode: for key, value in jsonNode:
let lowerKey = key.toLowerAscii() let lowerKey = key.toLowerAscii()
if jsonFields.hasKey(lowerKey): if jsonFields.hasKey(lowerKey):
let firstKey = jsonFields[lowerKey][0] let firstKey = jsonFields.getOrDefault(lowerKey)[0]
return err( return err(
"Duplicate configuration option (case-insensitive): '" & firstKey & "' and '" & "Duplicate configuration option (case-insensitive): '" & firstKey & "' and '" &
key & "'" key & "'"
@ -27,7 +30,7 @@ proc unknownKeysError(
var keys = newSeq[string]() var keys = newSeq[string]()
for _, (jsonKey, _) in pairs(jsonFields): for _, (jsonKey, _) in pairs(jsonFields):
keys.add(jsonKey) keys.add(jsonKey)
return prefix & ": " & $keys return prefix & ": " & keys.join(", ")
proc jsonScalarToString(node: JsonNode): Result[string, string] = proc jsonScalarToString(node: JsonNode): Result[string, string] =
## Convert a scalar JSON value to its string form. ## Convert a scalar JSON value to its string form.
@ -40,20 +43,55 @@ proc jsonScalarToString(node: JsonNode): Result[string, string] =
return ok($node.getFloat()) return ok($node.getFloat())
of JBool: of JBool:
return ok($node.getBool()) return ok($node.getBool())
of JNull:
return ok("")
else: else:
return err("expected scalar JSON value, got " & $node.kind) return err("expected scalar JSON value, got " & $node.kind)
proc applyJsonFieldsToConf( proc parseScalarInto[U](
conf: var WakuNodeConf, jsonValue: JsonNode, confField, jsonKey, prefix: string
): Result[U, string] =
## Parse a scalar JSON value into `U` via confutils `parseCmdArg`, same as the CLI.
let s = jsonScalarToString(jsonValue).valueOr:
return
err(prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & error)
try:
ok(parseCmdArg(U, s))
except CatchableError as e:
err(
prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & e.msg &
". Value: " & s
)
proc parseSeqInto[U](
jsonValue: JsonNode, confField, jsonKey, prefix: string
): Result[seq[U], string] =
## Parse a JSON array into `seq[U]`, each element via `parseCmdArg`.
if jsonValue.kind != JArray:
return err(
prefix & " '" & confField & "' from JSON key '" & jsonKey &
"' must be a JSON array"
)
var res: seq[U]
for item in jsonValue:
let s = jsonScalarToString(item).valueOr:
return
err(prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & error)
try:
res.add(parseCmdArg(U, s))
except CatchableError as e:
return err(
prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & e.msg &
". Value: " & s
)
ok(res)
proc applyJsonFieldsToConf*[T](
conf: var T,
jsonFields: var Table[string, (string, JsonNode)], jsonFields: var Table[string, (string, JsonNode)],
parseErrPrefix: string, parseErrPrefix: string,
unknownErrPrefix: string, unknownErrPrefix: string,
): Result[void, string] = ): Result[void, string] =
## Walk `conf`'s fields and write each one matched (case-insensitive) by ## Write each conf field matched (case-insensitive) by name or CLI `name:` pragma.
## `jsonFields`. seq fields take a JArray (full replace); scalar fields ## JSON `null` leaves the field unset; unknown or non-`parseCmdArg` keys error.
## take any scalar JSON kind. Errors on leftover unknown keys.
for confField, confValue in fieldPairs(conf): for confField, confValue in fieldPairs(conf):
# Match a field by its name or by its CLI name: pragma; case-insensitive. # Match a field by its name or by its CLI name: pragma; case-insensitive.
var matchKey = "" var matchKey = ""
@ -66,71 +104,52 @@ proc applyJsonFieldsToConf(
if matchKey != "": # field-name form already present: set twice if matchKey != "": # field-name form already present: set twice
return err( return err(
"config option '" & confField & "' was set twice, via '" & "config option '" & confField & "' was set twice, via '" &
jsonFields[matchKey][0] & "' and '" & jsonFields[lowerCliName][0] & "'" jsonFields.getOrDefault(matchKey)[0] & "' and '" &
jsonFields.getOrDefault(lowerCliName)[0] & "'"
) )
matchKey = lowerCliName matchKey = lowerCliName
if matchKey != "": if matchKey != "":
let (jsonKey, jsonValue) = jsonFields[matchKey] let (jsonKey, jsonValue) = jsonFields.getOrDefault(matchKey)
when confValue is seq: if jsonValue.kind == JNull:
if jsonValue.kind != JArray: # JSON null leaves the field unset; it keeps its default.
return err( jsonFields.del(matchKey)
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey &
"' must be a JSON array"
)
var newSeq: typeof(confValue) = @[]
for item in jsonValue:
let formattedItem = jsonScalarToString(item).valueOr:
return err(
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " &
error
)
try:
type ElemType = typeof(confValue[0])
newSeq.add(parseCmdArg(ElemType, formattedItem))
except CatchableError as e:
return err(
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " &
e.msg & ". Value: " & formattedItem
)
confValue = newSeq
else: else:
let formattedString = jsonScalarToString(jsonValue).valueOr: when confValue is Option:
return err( type Inner = typeof(confValue.get())
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & when Inner is seq:
error type Elem = typeof(confValue.get()[0])
) when compiles(parseCmdArg(Elem, "")):
try: confValue =
confValue = parseCmdArg(typeof(confValue), formattedString) some(?parseSeqInto[Elem](jsonValue, confField, jsonKey, parseErrPrefix))
except CatchableError as e: jsonFields.del(matchKey)
return err( else:
parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & return err("config option '" & jsonKey & "' cannot be set via JSON")
e.msg & ". Value: " & formattedString else:
) when compiles(parseCmdArg(Inner, "")):
jsonFields.del(matchKey) confValue = some(
?parseScalarInto[Inner](jsonValue, confField, jsonKey, parseErrPrefix)
)
jsonFields.del(matchKey)
else:
return err("config option '" & jsonKey & "' cannot be set via JSON")
elif confValue is seq:
type Elem = typeof(confValue[0])
when compiles(parseCmdArg(Elem, "")):
confValue =
?parseSeqInto[Elem](jsonValue, confField, jsonKey, parseErrPrefix)
jsonFields.del(matchKey)
else:
return err("config option '" & jsonKey & "' cannot be set via JSON")
else:
when compiles(parseCmdArg(typeof(confValue), "")):
confValue = ?parseScalarInto[typeof(confValue)](
jsonValue, confField, jsonKey, parseErrPrefix
)
jsonFields.del(matchKey)
else:
return err("config option '" & jsonKey & "' cannot be set via JSON")
if jsonFields.len > 0: if jsonFields.len > 0:
return err(unknownKeysError(jsonFields, unknownErrPrefix)) return err(unknownKeysError(jsonFields, unknownErrPrefix))
return ok() ok()
proc assembleFullConf*( {.pop.}
jsonFields: Table[string, (string, JsonNode)]
): Result[WakuNodeConf, string] =
## Build a WakuNodeConf from a flat JSON object whose keys are WakuNodeConf field
## names or their CLI `name:` pragma equivalents.
var conf = ?defaultWakuNodeConf()
var fields = jsonFields
?applyJsonFieldsToConf(
conf, fields, "Failed to parse field", "Unrecognized configuration option(s) found"
)
return ok(conf)
proc parseNodeConfFromJson*(jsonStr: string): Result[WakuNodeConf, string] =
## Parse a flat JSON config whose keys are WakuNodeConf field names or their CLI
## `name:` pragma equivalents.
var jsonNode: JsonNode
try:
jsonNode = parseJson(jsonStr)
except CatchableError as e:
return err("Failed to parse config JSON: " & e.msg)
let jsonFields = ?collectJsonFields(jsonNode)
return assembleFullConf(jsonFields)