diff --git a/examples/cbindings/waku_example.c b/examples/cbindings/waku_example.c index 55feb225d..614d54d69 100644 --- a/examples/cbindings/waku_example.c +++ b/examples/cbindings/waku_example.c @@ -50,12 +50,6 @@ struct ConfigNode int relay; char peers[2048]; int store; - char storeNode[2048]; - char storeRetentionPolicy[64]; - char storeDbUrl[256]; - int storeVacuum; - int storeDbMigration; - int storeMaxNumDbConnections; }; // libwaku Context @@ -290,42 +284,24 @@ int main(int argc, char **argv) cfgNode.relay = 1; 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); char jsonConfig[5000]; snprintf(jsonConfig, 5000, "{ \ - \"clusterId\": 16, \ - \"shards\": [ 1, 32, 64, 128, 256 ], \ - \"numShardsInNetwork\": 257, \ - \"listenAddress\": \"%s\", \ - \"tcpPort\": %d, \ - \"relay\": %s, \ - \"store\": %s, \ - \"storeMessageDbUrl\": \"%s\", \ - \"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\"] \ + \"mode\": \"Core\", \ + \"preset\": \"status.prod\", \ + \"messagingOverrides\": { \ + \"listen-address\": \"%s\", \ + \"tcp-port\": %d, \ + \"store\": %s, \ + \"log-level\": \"DEBUG\", \ + \"discv5-udp-port\": 9999 \ + } \ }", cfgNode.host, cfgNode.port, - cfgNode.relay ? "true" : "false", - cfgNode.store ? "true" : "false", - cfgNode.storeDbUrl, - cfgNode.storeRetentionPolicy, - cfgNode.storeMaxNumDbConnections); + cfgNode.store ? "true" : "false"); ctx = logosdelivery_create_node(jsonConfig, event_handler, userData); waitForCallback(); diff --git a/examples/cpp/waku.cpp b/examples/cpp/waku.cpp index 7b2a9b5f5..d2808537f 100644 --- a/examples/cpp/waku.cpp +++ b/examples/cpp/waku.cpp @@ -240,18 +240,14 @@ int main(int argc, char **argv) char jsonConfig[2048]; snprintf(jsonConfig, 2048, "{ \ - \"host\": \"%s\", \ - \"port\": %d, \ - \"relay\": true, \ - \"clusterId\": 16, \ - \"shards\": [ 1, 32, 64, 128, 256 ], \ - \"logLevel\": \"FATAL\", \ - \"discv5Discovery\": true, \ - \"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\"] \ + \"mode\": \"Core\", \ + \"preset\": \"status.prod\", \ + \"messagingOverrides\": { \ + \"listen-address\": \"%s\", \ + \"tcp-port\": %d, \ + \"log-level\": \"FATAL\", \ + \"discv5-udp-port\": 9999 \ + } \ }", cfgNode.host, cfgNode.port); diff --git a/examples/golang/waku.go b/examples/golang/waku.go index 1f99003bd..1d09bfb5e 100644 --- a/examples/golang/waku.go +++ b/examples/golang/waku.go @@ -293,12 +293,16 @@ type WakuMessageHash = string type WakuPubsubTopic = 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 { - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - NodeKey string `json:"key,omitempty"` - EnableRelay bool `json:"relay"` - LogLevel string `json:"logLevel"` + Mode string `json:"mode"` + MessagingOverrides MessagingOverrides `json:"messagingOverrides"` } type WakuNode struct { @@ -563,11 +567,13 @@ func (self *WakuNode) WakuGetMyENR() (string, error) { func main() { config := WakuConfig{ - Host: "0.0.0.0", - Port: 30304, - NodeKey: "11d0dcea28e86f81937a3bd1163473c7fbc0a0db54fd72914849bc47bdf78710", - EnableRelay: true, - LogLevel: "DEBUG", + Mode: "Core", + MessagingOverrides: MessagingOverrides{ + ListenAddress: "0.0.0.0", + Port: 30304, + NodeKey: "11d0dcea28e86f81937a3bd1163473c7fbc0a0db54fd72914849bc47bdf78710", + LogLevel: "DEBUG", + }, } node, err := WakuNew(config) diff --git a/examples/ios/WakuExample/WakuNode.swift b/examples/ios/WakuExample/WakuNode.swift index 9cc252546..611f50c50 100644 --- a/examples/ios/WakuExample/WakuNode.swift +++ b/examples/ios/WakuExample/WakuNode.swift @@ -300,22 +300,17 @@ actor WakuActor { private func initializeNode() async -> Bool { let config = """ { - "tcpPort": 60000, - "clusterId": 1, - "shards": [0], - "relay": false, - "lightpush": true, - "filter": true, - "logLevel": "DEBUG", - "discv5Discovery": true, - "discv5BootstrapNodes": [ - "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", - "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"] + "mode": "Edge", + "messagingOverrides": { + "tcp-port": 60000, + "cluster-id": 1, + "log-level": "DEBUG", + "entry-node": [ + "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", + "enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw" + ], + "discv5-udp-port": 9999 + } } """ diff --git a/examples/mobile/App.tsx b/examples/mobile/App.tsx index bf01de3a5..ff77fea5e 100644 --- a/examples/mobile/App.tsx +++ b/examples/mobile/App.tsx @@ -162,10 +162,13 @@ function App(): React.JSX.Element { const onClickNew = async () => { const config = { - host: '0.0.0.0', - port: 42342, - key: '1122334455667788990011223344556677889900112233445566778899000022', - relay: true, + mode: 'Core', + messagingOverrides: { + 'listen-address': '0.0.0.0', + 'tcp-port': 42342, + nodekey: + '1122334455667788990011223344556677889900112233445566778899000022', + }, }; waku = await WakuFactory.createInstance(config); }; diff --git a/examples/nodejs/waku.js b/examples/nodejs/waku.js index 258e34849..13005dc62 100644 --- a/examples/nodejs/waku.js +++ b/examples/nodejs/waku.js @@ -17,11 +17,13 @@ function create_random_string(length) { var wakuMod = require('bindings')('waku'); var cfg = `{ - "host": "0.0.0.0", - "port": 60001, - "key": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9", - "relay": true - "logLevel": "DEBUG" + "mode": "Core", + "messagingOverrides": { + "listen-address": "0.0.0.0", + "tcp-port": 60001, + "nodekey": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9", + "log-level": "DEBUG" + } }` function event_handler(event) { diff --git a/examples/python/waku.py b/examples/python/waku.py index 5df72695d..b61d5ff36 100644 --- a/examples/python/waku.py +++ b/examples/python/waku.py @@ -55,15 +55,16 @@ args = parser.parse_args() # The next 'json_config' is the item passed to the 'logosdelivery_create_node'. json_config = "{ \ - \"host\": \"%s\", \ - \"port\": %d, \ - \"key\": \"%s\", \ - \"relay\": %s ,\ - \"logLevel\": \"DEBUG\" \ + \"mode\": \"Core\", \ + \"messagingOverrides\": { \ + \"listen-address\": \"%s\", \ + \"tcp-port\": %d, \ + \"nodekey\": \"%s\", \ + \"log-level\": \"DEBUG\" \ + } \ }" % (args.host, int(args.port), - args.key, - "true" if args.relay else "false") + args.key) callback_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p, ctypes.c_size_t) diff --git a/examples/qt/main_qt.cpp b/examples/qt/main_qt.cpp index f16660c17..db922f062 100644 --- a/examples/qt/main_qt.cpp +++ b/examples/qt/main_qt.cpp @@ -17,18 +17,16 @@ int main(int argc, char *argv[]) { QString jsonConfig = R"( { - "tcpPort": 60000, - "relay": true, - "logLevel": "TRACE", - "discv5Discovery": true, - "discv5BootstrapNodes": [ - "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", - "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"] + "mode": "Core", + "messagingOverrides": { + "tcp-port": 60000, + "log-level": "TRACE", + "entry-node": [ + "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", + "enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw" + ], + "discv5-udp-port": 9999 + } } )"; diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 16f31bb63..bf142bd1b 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -50,11 +50,13 @@ where fn main() { let config_json = "\ { \ - \"host\": \"127.0.0.1\",\ - \"port\": 60000, \ - \"key\": \"0d714a1fada214dead6dc9c7274581ec20ff292451866e7d6d677dc818e8ccd2\", \ - \"relay\": true ,\ - \"logLevel\": \"DEBUG\" + \"mode\": \"Core\",\ + \"messagingOverrides\": { \ + \"listen-address\": \"127.0.0.1\",\ + \"tcp-port\": 60000, \ + \"nodekey\": \"0d714a1fada214dead6dc9c7274581ec20ff292451866e7d6d677dc818e8ccd2\", \ + \"log-level\": \"DEBUG\" + } }"; unsafe { diff --git a/library/README.md b/library/README.md index e6ddcd4d2..53f174759 100644 --- a/library/README.md +++ b/library/README.md @@ -33,15 +33,22 @@ void *logosdelivery_create_node( { "mode": "Core", "preset": "logos.dev", - "listenAddress": "0.0.0.0", - "tcpPort": 60000, - "discv5UdpPort": 9000 + "messagingOverrides": { + "listen-address": "0.0.0.0", + "tcp-port": 60000, + "discv5-udp-port": 9000 + } } ``` -Configuration uses flat field names matching `WakuNodeConf` in `tools/confutils/cli_args.nim`. -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. +The configuration object has four optional top-level keys: `mode` (`"Core"` or +`"Edge"`, defaults to `"Core"`), `preset`, `messagingOverrides` (per-field node +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: diff --git a/library/examples/logosdelivery_example.c b/library/examples/logosdelivery_example.c index 729f7f0dc..bccdc7d25 100644 --- a/library/examples/logosdelivery_example.c +++ b/library/examples/logosdelivery_example.c @@ -129,12 +129,14 @@ void simple_callback(int ret, const char *msg, size_t len, void *userData) { int main() { printf("=== Logos Messaging API (LMAPI) Example ===\n\n"); - // Configuration JSON using WakuNodeConf field names (flat structure). - // Field names match Nim identifiers from WakuNodeConf in tools/confutils/cli_args.nim. + // Layered messaging config: {mode, preset, messagingOverrides, channelsOverrides}. + // Override keys are MessagingClientConf field or CLI switch names. const char *config = "{" - "\"logLevel\": \"INFO\"," "\"mode\": \"Core\"," - "\"preset\": \"logos.dev\"" + "\"preset\": \"logos.dev\"," + "\"messagingOverrides\": {" + "\"log-level\": \"INFO\"" + "}" "}"; printf("1. Creating node...\n"); diff --git a/library/liblogosdelivery.h b/library/liblogosdelivery.h index 7b4bbd5ab..21d8890d2 100644 --- a/library/liblogosdelivery.h +++ b/library/liblogosdelivery.h @@ -22,9 +22,14 @@ extern "C" // 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. - // Configuration should be in JSON format using WakuNodeConf field names. - // Field names match Nim identifiers from WakuNodeConf (camelCase). - // Example: {"mode": "Core", "clusterId": 42, "relay": true} + // The configuration is a JSON object with these optional keys: + // "mode": "Core" | "Edge" (messaging role; defaults to "Core") + // "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( const char *configJson, FFICallBack callback, diff --git a/library/logos_delivery_api/node_api.nim b/library/logos_delivery_api/node_api.nim index 1130ab181..bed900683 100644 --- a/library/logos_delivery_api/node_api.nim +++ b/library/logos_delivery_api/node_api.nim @@ -7,7 +7,7 @@ import logos_delivery/api/types, logos_delivery/waku/api/events/health_events, logos_delivery/waku/api/events/peer_events, - tools/confutils/conf_from_json, + logos_delivery/api/logos_delivery_conf_json, ../declare_lib, ../json_event @@ -17,10 +17,10 @@ proc `%`*(id: RequestId): JsonNode = registerReqFFI(CreateNodeRequest, ctx: ptr FFIContext[LogosDelivery]): proc(configJson: cstring): Future[Result[string, string]] {.async.} = - let conf = parseNodeConfFromJson($configJson).valueOr: - error "Failed to assemble WakuNodeConf from JSON", + let conf = parseLogosDeliveryConf($configJson).valueOr: + error "Failed to parse Logos Delivery configuration JSON", error = error, configJson = $configJson - return err("failed parseNodeConfFromJson " & error) + return err("failed parseLogosDeliveryConf " & error) ctx.myLib[] = (await LogosDelivery.new(conf)).valueOr: let errMsg = $error diff --git a/logos_delivery/api/api_conf.nim b/logos_delivery/api/api_conf.nim deleted file mode 100644 index 5810b1eb2..000000000 --- a/logos_delivery/api/api_conf.nim +++ /dev/null @@ -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 diff --git a/logos_delivery/api/kernel_conf.nim b/logos_delivery/api/kernel_conf.nim new file mode 100644 index 000000000..2c66e2d51 --- /dev/null +++ b/logos_delivery/api/kernel_conf.nim @@ -0,0 +1,4 @@ +import tools/confutils/cli_args +export cli_args + +type KernelConf* = WakuNodeConf ## Alias of the CLI-facing `WakuNodeConf`. diff --git a/logos_delivery/api/logos_delivery_conf.nim b/logos_delivery/api/logos_delivery_conf.nim new file mode 100644 index 000000000..124a8c5fb --- /dev/null +++ b/logos_delivery/api/logos_delivery_conf.nim @@ -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.} diff --git a/logos_delivery/api/logos_delivery_conf_json.nim b/logos_delivery/api/logos_delivery_conf_json.nim new file mode 100644 index 000000000..2416104bd --- /dev/null +++ b/logos_delivery/api/logos_delivery_conf_json.nim @@ -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.} diff --git a/logos_delivery/api/messaging_conf.nim b/logos_delivery/api/messaging_conf.nim new file mode 100644 index 000000000..9ba642cd8 --- /dev/null +++ b/logos_delivery/api/messaging_conf.nim @@ -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))) diff --git a/logos_delivery/channels/api/channel_lifecycle.nim b/logos_delivery/channels/api/channel_lifecycle.nim index 4f891c61f..d110ceb72 100644 --- a/logos_delivery/channels/api/channel_lifecycle.nim +++ b/logos_delivery/channels/api/channel_lifecycle.nim @@ -8,7 +8,7 @@ import logos_delivery/channels/reliable_channel_manager import logos_delivery/channels/reliable_channel import logos_delivery/waku/persistency/sds_persistency -# ReliableChannel, SendHandler, config and wire-version markers. +# ReliableChannel, config and wire-version markers. export reliable_channel const SdsJobId = "sds" @@ -33,22 +33,32 @@ proc createReliableChannel*( contentTopic: ContentTopic, senderId: SdsParticipantID, ): Result[ChannelId, string] = + ## Encryption and egress providers must be installed (or `setNoopEncryption()`) + ## before traffic flows on the channel. if self.channels.hasKey(channelId): return err("channel already exists: " & channelId) + let cc = self.conf let segConfig = SegmentationConfig( - segmentSizeBytes: DefaultSegmentSizeBytes, - enableReedSolomon: false, + segmentSizeBytes: cc.segmentationSegmentSizeBytes.get(DefaultSegmentSizeBytes), + enableReedSolomon: cc.segmentationEnableReedSolomon.get(false), persistence: nil, ) let sdsConfig = SdsConfig( - acknowledgementTimeoutMs: DefaultAcknowledgementTimeoutMs, - maxRetransmissions: DefaultMaxRetransmissions, - causalHistorySize: DefaultCausalHistorySize, + acknowledgementTimeoutMs: + cc.sdsAcknowledgementTimeoutMs.get(DefaultAcknowledgementTimeoutMs), + maxRetransmissions: cc.sdsMaxRetransmissions.get(DefaultMaxRetransmissions), + causalHistorySize: cc.sdsCausalHistorySize.get(DefaultCausalHistorySize), persistence: sdsPersistence(), ) 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( diff --git a/logos_delivery/channels/reliable_channel_manager.nim b/logos_delivery/channels/reliable_channel_manager.nim index 8ff176e9a..46e3c3912 100644 --- a/logos_delivery/channels/reliable_channel_manager.nim +++ b/logos_delivery/channels/reliable_channel_manager.nim @@ -13,8 +13,6 @@ import stew/byteutils 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/reliable_channel_manager_api @@ -24,12 +22,22 @@ export reliable_channel type ReliableChannelManagerConf* = object - ## Per-layer config object for the reliable - ## channel API. Placeholder for now (segmentation / SDS / rate-limit defaults - ## will move here in a follow-up PR); kept so each layer owns its own config. + ## All-`Option` partial; unset fields fall back to `createReliableChannel` defaults. + segmentationEnableReedSolomon*: Option[bool] + ## 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`. channels*: Table[ChannelId, ReliableChannel] ## read by `channels/api.nim` + conf*: ReliableChannelManagerConf brokerCtx*: BrokerContext proc new*( @@ -37,7 +45,13 @@ proc new*( conf: ReliableChannelManagerConf, brokerCtx: BrokerContext = globalBrokerContext(), ): 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] = ## Placeholder: per-channel listeners are installed in `ReliableChannel.new`, diff --git a/logos_delivery/logos_delivery.nim b/logos_delivery/logos_delivery.nim index 6a6587ab0..1e34b6a6a 100644 --- a/logos_delivery/logos_delivery.nim +++ b/logos_delivery/logos_delivery.nim @@ -9,6 +9,7 @@ {.push raises: [].} +import std/options import results, chronos, chronicles # 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 import logos_delivery/api/events/messaging_client_events export messaging_client_events +import logos_delivery/api/messaging_conf +export messaging_conf # Reliable Channel layer 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 tools/confutils/cli_args import logos_delivery/waku/node/health_monitor/online_monitor +import logos_delivery/api/logos_delivery_conf +export logos_delivery_conf logScope: topics = "logosdelivery" -type - LogosDeliveryConf* = object - ## Aggregates the per-layer config objects. For now - ## the sub-configs are derived from `WakuConf`; richer per-layer configuration - ## (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(), - ) +type LogosDelivery* = ref object ## Entry point. Holds one instance of each API layer. + waku*: Waku + messagingClient*: MessagingClient + reliableChannelManager*: ReliableChannelManager proc new*( - T: type LogosDelivery, conf: WakuNodeConf, appCallbacks: AppCallbacks = nil + T: type LogosDelivery, conf: LogosDeliveryConf, appCallbacks: AppCallbacks = nil ): Future[Result[LogosDelivery, string]] {.async.} = - ## Single entry point, from the CLI configuration type. Derives the aggregated - ## per-layer config, then creates the full stack bottom-up so each layer can - ## chain onto the one below. - let wakuConf = conf.toWakuConf().valueOr: + ## Builds the stack bottom-up from a resolved per-layer config. + let wakuConf = conf.kernelConf.toWakuConf().valueOr: return err("failed to handle the configuration: " & error) - let layerConf = LogosDeliveryConf.init(wakuConf) - - let waku = (await Waku.new(layerConf.waku, appCallbacks)).valueOr: + let waku = (await Waku.new(wakuConf, appCallbacks)).valueOr: 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) let reliableChannelManager = ReliableChannelManager.new( - layerConf.reliableChannel, waku.brokerCtx + conf.channelsConf, waku.brokerCtx ).valueOr: return err("failed to create ReliableChannelManager: " & error) return ok( - T( + LogosDelivery( waku: waku, messagingClient: messagingClient, 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.} = ## Starts each layer bottom-up: transport first, then messaging, then channels. 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.} = ## Stops in reverse order so higher layers drain before their dependencies. - await self.reliableChannelManager.stop() - await self.messagingClient.stop() - - (await self.waku.stop()).isOkOr: - return err("failed to stop Waku: " & error) + if not self.reliableChannelManager.isNil(): + await self.reliableChannelManager.stop() + if not self.messagingClient.isNil(): + await self.messagingClient.stop() + if not self.waku.isNil(): + (await self.waku.stop()).isOkOr: + return err("failed to stop Waku: " & error) return ok() diff --git a/logos_delivery/messaging/messaging_client.nim b/logos_delivery/messaging/messaging_client.nim index 32f5439e5..ef69c2d43 100644 --- a/logos_delivery/messaging/messaging_client.nim +++ b/logos_delivery/messaging/messaging_client.nim @@ -1,11 +1,17 @@ ## Messaging layer core: the `MessagingClient` type plus its construction and ## lifecycle. The public operations (subscribe / unsubscribe / send) live in ## `messaging/api.nim`. +import std/[options, net] 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 logos_delivery/api/messaging_client_api, logos_delivery/waku/waku, + logos_delivery/waku/factory/conf_builder/waku_conf_builder, logos_delivery/messaging/delivery_service/[recv_service, send_service] # Surfaces the messaging API interface (and its Message* events) to consumers. @@ -13,10 +19,45 @@ export messaging_client_api type MessagingClientConf* = object - ## Per-layer config object for the messaging API. - ## Kept intentionally minimal for now; the full config surface lands in a - ## follow-up PR. Today it only carries the p2p reliability toggle. - useP2PReliability*: bool + clusterId* {.name: "cluster-id".}: Option[uint16] ## Network cluster id. + numShardsInCluster* {.name: "num-shards-in-network".}: Option[uint16] + ## Number of shards in the cluster. + 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 brokerCtx*: BrokerContext @@ -30,7 +71,8 @@ proc new*( ): Result[T, string] = ## The messaging layer chains onto Waku: it drives the underlying Waku kernel ## 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) return ok( T( diff --git a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim index ffffceb5f..d312f620e 100644 --- a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim @@ -170,7 +170,6 @@ type WakuConfBuilder* = object relayShardedPeerManagement: Option[bool] relayServiceRatio: Option[string] circuitRelayClient: Option[bool] - p2pReliability: Option[bool] localStoragePath: Option[string] @@ -310,9 +309,6 @@ proc withRelayShardedPeerManagement*( ) = b.relayShardedPeerManagement = some(relayShardedPeerManagement) -proc withP2pReliability*(b: var WakuConfBuilder, p2pReliability: bool) = - b.p2pReliability = some(p2pReliability) - proc withLocalStoragePath*(b: var WakuConfBuilder, localStoragePath: string) = b.localStoragePath = some(localStoragePath) @@ -462,10 +458,6 @@ proc applyNetworkPresetConf(builder: var WakuConfBuilder) = checkSetPresetValueToField( builder.mix, networkPresetConf.mix, "Mix was provided alongside a network conf" ) - checkSetPresetValueToField( - builder.p2pReliability, networkPresetConf.p2pReliability, - "P2P Reliability was provided alongside a network conf", - ) # Process entry nodes from network config - classify and distribute if networkPresetConf.entryNodes.len > 0: @@ -841,7 +833,6 @@ proc build*( circuitRelayClient: builder.circuitRelayClient.get(DefaultCircuitRelayClient), staticNodes: builder.staticNodes, relayShardedPeerManagement: relayShardedPeerManagement, - p2pReliability: builder.p2pReliability.get(DefaultP2pReliability), wakuFlags: wakuFlags, localStoragePath: builder.localStoragePath.get(DefaultStoragePath), ) diff --git a/logos_delivery/waku/factory/waku_conf.nim b/logos_delivery/waku/factory/waku_conf.nim index 1ceb9ed09..db1a8aa28 100644 --- a/logos_delivery/waku/factory/waku_conf.nim +++ b/logos_delivery/waku/factory/waku_conf.nim @@ -155,8 +155,6 @@ type WakuConf* {.requiresInit.} = ref object # TODO: use proper type relayServiceRatio*: string - p2pReliability*: bool - localStoragePath*: string proc logConf*(conf: WakuConf) = diff --git a/tests/api/test_all.nim b/tests/api/test_all.nim index 56be19c27..108551bc2 100644 --- a/tests/api/test_all.nim +++ b/tests/api/test_all.nim @@ -3,6 +3,7 @@ import ./test_entry_nodes, ./test_node_conf, + ./test_conf, ./test_api_send, ./test_api_subscription, ./test_api_receive, diff --git a/tests/api/test_api_health.nim b/tests/api/test_api_health.nim index a972d5db6..e0d344958 100644 --- a/tests/api/test_api_health.nim +++ b/tests/api/test_api_health.nim @@ -91,9 +91,8 @@ suite "LM API health checking": serviceNode.wakuRelay.subscribe(DefaultShard, dummyHandler) lockNewGlobalBrokerContext: - var conf = defaultWakuNodeConf().valueOr: - raiseAssert error - conf.mode = Core + var conf = MessagingClientConf().toKernelConf(Core).valueOr: + raiseAssert error conf.listenAddress = parseIpAddress("0.0.0.0") conf.tcpPort = Port(0) conf.discv5UdpPort = Port(0) @@ -272,9 +271,8 @@ suite "LM API health checking": var edgeWaku: LogosDelivery lockNewGlobalBrokerContext: - var edgeConf = defaultWakuNodeConf().valueOr: - raiseAssert error - edgeConf.mode = Edge + var edgeConf = MessagingClientConf().toKernelConf(Edge).valueOr: + raiseAssert error edgeConf.listenAddress = parseIpAddress("0.0.0.0") edgeConf.tcpPort = Port(0) edgeConf.discv5UdpPort = Port(0) diff --git a/tests/api/test_api_receive.nim b/tests/api/test_api_receive.nim index 807f36014..51c2438a8 100644 --- a/tests/api/test_api_receive.nim +++ b/tests/api/test_api_receive.nim @@ -21,6 +21,7 @@ import ] import logos_delivery/waku/factory/waku_conf import tools/confutils/cli_args +import logos_delivery/api/messaging_conf const TestTimeout = chronos.seconds(60) @@ -82,15 +83,13 @@ proc waitForConnectionStatus( await EventConnectionStatusChange.dropListener(brokerCtx, handle) proc createApiNodeConf(numShards: uint16 = 1): WakuNodeConf = - var conf = defaultWakuNodeConf().valueOr: - raiseAssert error - conf.mode = cli_args.WakuMode.Core + var conf = MessagingClientConf().toKernelConf(messaging_conf.WakuMode.Core).valueOr: + raiseAssert error conf.listenAddress = parseIpAddress("0.0.0.0") conf.tcpPort = Port(0) conf.discv5UdpPort = Port(0) conf.clusterId = some(3'u16) conf.numShardsInNetwork = numShards - conf.reliabilityEnabled = some(true) conf.rest = false result = conf diff --git a/tests/api/test_api_send.nim b/tests/api/test_api_send.nim index df1522564..29921e7aa 100644 --- a/tests/api/test_api_send.nim +++ b/tests/api/test_api_send.nim @@ -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/waku/factory/waku_conf import tools/confutils/cli_args +import logos_delivery/api/messaging_conf type SendEventOutcome {.pure.} = enum Sent @@ -119,16 +120,16 @@ proc validate( for requestId in manager.errorRequestIds: check requestId == expectedRequestId -proc createApiNodeConf(mode: cli_args.WakuMode = cli_args.WakuMode.Core): WakuNodeConf = - var conf = defaultWakuNodeConf().valueOr: - raiseAssert error - conf.mode = mode +proc createApiNodeConf( + mode: messaging_conf.WakuMode = messaging_conf.WakuMode.Core +): WakuNodeConf = + var conf = MessagingClientConf().toKernelConf(mode).valueOr: + raiseAssert error conf.listenAddress = parseIpAddress("0.0.0.0") conf.tcpPort = Port(0) conf.discv5UdpPort = Port(0) conf.clusterId = some(3'u16) conf.numShardsInNetwork = 1 - conf.reliabilityEnabled = some(true) conf.rest = false result = conf @@ -353,7 +354,7 @@ suite "Waku API - Send": ## connected to a lightpush-capable peer must deliver through lightpush. var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await LogosDelivery.new(createApiNodeConf(cli_args.WakuMode.Edge))).valueOr: + node = (await LogosDelivery.new(createApiNodeConf(messaging_conf.WakuMode.Edge))).valueOr: raiseAssert error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error @@ -388,7 +389,7 @@ suite "Waku API - Send": ## later retry must deliver the queued message. var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await LogosDelivery.new(createApiNodeConf(cli_args.WakuMode.Edge))).valueOr: + node = (await LogosDelivery.new(createApiNodeConf(messaging_conf.WakuMode.Edge))).valueOr: raiseAssert error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error @@ -479,7 +480,7 @@ suite "Waku API - Send": var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await LogosDelivery.new(createApiNodeConf(cli_args.WakuMode.Edge))).valueOr: + node = (await LogosDelivery.new(createApiNodeConf(messaging_conf.WakuMode.Edge))).valueOr: raiseAssert error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error diff --git a/tests/api/test_api_subscription.nim b/tests/api/test_api_subscription.nim index 359e55fbc..37566539a 100644 --- a/tests/api/test_api_subscription.nim +++ b/tests/api/test_api_subscription.nim @@ -18,6 +18,7 @@ import ] import logos_delivery/waku/factory/waku_conf import tools/confutils/cli_args +import logos_delivery/api/messaging_conf const TestTimeout = chronos.seconds(10) const NegativeTestTimeout = chronos.seconds(2) @@ -68,17 +69,15 @@ type TestNetwork = ref object publisherPeerInfo: RemotePeerInfo 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 = - var conf = defaultWakuNodeConf().valueOr: - raiseAssert error - conf.mode = mode + var conf = MessagingClientConf().toKernelConf(mode).valueOr: + raiseAssert error conf.listenAddress = parseIpAddress("0.0.0.0") conf.tcpPort = Port(0) conf.discv5UdpPort = Port(0) conf.clusterId = some(3'u16) conf.numShardsInNetwork = numShards - conf.reliabilityEnabled = some(true) conf.rest = false result = conf @@ -90,7 +89,7 @@ proc setupSubscriberNode(conf: WakuNodeConf): Future[LogosDelivery] {.async.} = return node 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.} = var net = TestNetwork() @@ -100,7 +99,7 @@ proc setupNetwork( "Failed to mount metadata" ) (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.mountLibp2pPing() await net.publisher.start() @@ -119,7 +118,7 @@ proc setupNetwork( "Failed to sub publisher" ) - if mode == cli_args.WakuMode.Edge: + if mode == messaging_conf.WakuMode.Edge: lockNewGlobalBrokerContext: net.meshBuddy = newTestWakuNode(generateSecp256k1Key()) net.meshBuddy.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( @@ -474,7 +473,7 @@ suite "Messaging API, SubscriptionManager": await verifyNetworkState(activeSubs) 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: await net.teardown() @@ -496,7 +495,7 @@ suite "Messaging API, SubscriptionManager": check eventManager.receivedMessages[0].contentTopic == testTopic 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: await net.teardown() @@ -518,7 +517,7 @@ suite "Messaging API, SubscriptionManager": check eventManager.receivedMessages.len == 0 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: await net.teardown() @@ -543,7 +542,7 @@ suite "Messaging API, SubscriptionManager": check eventManager.receivedMessages.len == 0 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: await net.teardown() @@ -572,7 +571,7 @@ suite "Messaging API, SubscriptionManager": check eventManager.receivedMessages[0].contentTopic == topicB 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: await net.teardown() @@ -657,7 +656,7 @@ suite "Messaging API, SubscriptionManager": await meshBuddy.connectToNodes(@[publisherPeerInfo]) - let conf = createApiNodeConf(cli_args.WakuMode.Edge, numShards) + let conf = createApiNodeConf(messaging_conf.WakuMode.Edge, numShards) var subscriber: LogosDelivery lockNewGlobalBrokerContext: subscriber = @@ -785,7 +784,7 @@ suite "Messaging API, SubscriptionManager": await meshBuddy.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 lockNewGlobalBrokerContext: subscriber = diff --git a/tests/api/test_conf.nim b/tests/api/test_conf.nim new file mode 100644 index 000000000..dd90477d4 --- /dev/null +++ b/tests/api/test_conf.nim @@ -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 diff --git a/tests/api/test_node_conf.nim b/tests/api/test_node_conf.nim index 2716ffed1..6048a5951 100644 --- a/tests/api/test_node_conf.nim +++ b/tests/api/test_node_conf.nim @@ -1,196 +1,10 @@ {.used.} -import std/[options, strutils], results, stint, testutils/unittests -import json_serialization, confutils, confutils/std/net +import std/options, results, testutils/unittests import tools/confutils/cli_args, - tools/confutils/conf_from_json, - logos_delivery/api/api_conf, logos_delivery/waku/factory/waku_conf, - logos_delivery/waku/factory/networks_config, - logos_delivery/waku/factory/conf_builder/conf_builder, - logos_delivery/waku/common/logging - -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() + logos_delivery/waku/factory/networks_config suite "WakuNodeConf - preset integration": test "TWN preset applies TheWakuNetworkConf": @@ -260,6 +74,25 @@ suite "WakuNodeConf - preset integration": wakuConf.shardingConf.numShardsInCluster == 1 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": ## Given var conf = defaultWakuNodeConf().valueOr: @@ -271,202 +104,3 @@ suite "WakuNodeConf - preset integration": ## Then 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() diff --git a/tests/channels/test_reliable_channel_send_receive.nim b/tests/channels/test_reliable_channel_send_receive.nim index 70a8e2702..a56cb0522 100644 --- a/tests/channels/test_reliable_channel_send_receive.nim +++ b/tests/channels/test_reliable_channel_send_receive.nim @@ -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/messaging_client_api import tools/confutils/cli_args +import logos_delivery/api/messaging_conf import logos_delivery/channels/reliable_channel_manager import logos_delivery/channels/encryption/noop_encryption @@ -27,15 +28,13 @@ import snapshot_codec const TestTimeout = chronos.seconds(15) proc createApiNodeConf(): WakuNodeConf = - var conf = defaultWakuNodeConf().valueOr: - raiseAssert error - conf.mode = cli_args.WakuMode.Core + var conf = MessagingClientConf().toKernelConf(messaging_conf.WakuMode.Core).valueOr: + raiseAssert error conf.listenAddress = parseIpAddress("0.0.0.0") conf.tcpPort = Port(0) conf.discv5UdpPort = Port(0) conf.clusterId = some(3'u16) conf.numShardsInNetwork = 1 - conf.reliabilityEnabled = some(true) conf.rest = false return conf @@ -166,7 +165,7 @@ suite "Reliable Channel - ingress": suite "Reliable Channel - send state machine": asyncTest "MessageSentEvent finalises the channelReqId as Sent": ## 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. ## Emitting the delivery-layer `MessageSentEvent` must drive the ## 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 ## one segment under the current segmentation skeleton ## (`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`, ## finalising the second as a failure emits `ChannelMessageErrorEvent` ## for the other. @@ -329,7 +328,7 @@ suite "Reliable Channel - send state machine": ## `channelReqId` cannot be produced through the real pipeline. ## Implement once segmentation does real chunking: send a payload ## 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. skip() diff --git a/tests/node/test_wakunode_health_monitor.nim b/tests/node/test_wakunode_health_monitor.nim index c6f9d34e6..af90a9be0 100644 --- a/tests/node/test_wakunode_health_monitor.nim +++ b/tests/node/test_wakunode_health_monitor.nim @@ -238,7 +238,7 @@ suite "Health Monitor - events": node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA) ) let ds = MessagingClient - .new(MessagingClientConf(useP2PReliability: false), waku) + .new(MessagingClientConf(reliabilityEnabled: some(false)), waku) .expect("Failed to create MessagingClient") ds.start().expect("Failed to start MessagingClient") @@ -349,7 +349,7 @@ suite "Health Monitor - events": node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA) ) let ds = MessagingClient - .new(MessagingClientConf(useP2PReliability: false), waku) + .new(MessagingClientConf(reliabilityEnabled: some(false)), waku) .expect("Failed to create MessagingClient") ds.start().expect("Failed to start MessagingClient") let subMgr = nodeA.subscriptionManager diff --git a/tests/test_waku.nim b/tests/test_waku.nim index faf1575eb..772387674 100644 --- a/tests/test_waku.nim +++ b/tests/test_waku.nim @@ -12,9 +12,8 @@ import logos_delivery/waku/factory/conf_builder/conf_builder suite "LogosDelivery API - Create node": asyncTest "Create node with minimal configuration": ## Given - var nodeConf = defaultWakuNodeConf().valueOr: - raiseAssert "defaultWakuNodeConf failed: " & error - nodeConf.mode = Core + var nodeConf = MessagingClientConf().toKernelConf(Core).valueOr: + raiseAssert "toKernelConf failed: " & error nodeConf.clusterId = some(3'u16) nodeConf.rest = false @@ -32,9 +31,8 @@ suite "LogosDelivery API - Create node": asyncTest "Create node with full configuration": ## Given - var nodeConf = defaultWakuNodeConf().valueOr: - raiseAssert "defaultWakuNodeConf failed: " & error - nodeConf.mode = Core + var nodeConf = MessagingClientConf().toKernelConf(Core).valueOr: + raiseAssert "toKernelConf failed: " & error nodeConf.clusterId = some(99'u16) nodeConf.rest = false nodeConf.numShardsInNetwork = 16 @@ -64,9 +62,8 @@ suite "LogosDelivery API - Create node": asyncTest "Create node with mixed entry nodes (enrtree, multiaddr)": ## Given - var nodeConf = defaultWakuNodeConf().valueOr: - raiseAssert "defaultWakuNodeConf failed: " & error - nodeConf.mode = Core + var nodeConf = MessagingClientConf().toKernelConf(Core).valueOr: + raiseAssert "toKernelConf failed: " & error nodeConf.clusterId = some(42'u16) nodeConf.rest = false nodeConf.entryNodes = @[ diff --git a/tools/confutils/cli_args.nim b/tools/confutils/cli_args.nim index f781133af..aa7b6cb1b 100644 --- a/tools/confutils/cli_args.nim +++ b/tools/confutils/cli_args.nim @@ -60,11 +60,6 @@ type StartUpCommand* = enum noCommand # default, runs waku generateRlnKeystore # generates a new RLN keystore -type WakuMode* {.pure.} = enum - noMode # default - use explicit CLI flags as-is - Core # full service node - Edge # client-only node - type WakuNodeConf* = object configFile* {. desc: "Loads configuration from a TOML file (cmd-line parameters take precedence)", @@ -166,13 +161,6 @@ type WakuNodeConf* = object .}: seq[ProtectedShard] ## General node config - mode* {. - desc: - "Node operation mode. 'Core' enables relay+service protocols. 'Edge' enables client-only protocols. Default: explicit CLI flags used.", - defaultValue: WakuMode.noMode, - name: "mode" - .}: WakuMode - preset* {. desc: "Network preset to use. 'twn' is The RLN-protected Waku Network (cluster 1). 'logos.dev' is the Logos Dev Network (cluster 2). 'logos.test' is the Logos Test Network (cluster 2). 'status.prod' is the Status Production Network (cluster 16, RLN off, auto-sharding with 1 shard). Overrides other values.", @@ -442,7 +430,7 @@ hence would have reachability issues.""", ## Filter config filter* {. - desc: "Enable filter protocol: true|false", defaultValue: false, name: "filter" + desc: "Enable filter protocol: true|false", defaultValue: true, name: "filter" .}: bool filternode* {. @@ -474,7 +462,7 @@ hence would have reachability issues.""", ## Lightpush config lightpush* {. desc: "Enable lightpush protocol: true|false", - defaultValue: false, + defaultValue: true, name: "lightpush" .}: bool @@ -484,16 +472,6 @@ hence would have reachability issues.""", name: "lightpushnode" .}: string - ## Reliability config - # Option-typed; desc states the default since the CLI can't auto-show it for none(). - reliabilityEnabled* {. - desc: - """Adds an extra effort in the delivery/reception of messages by leveraging store-v3 requests, with the drawback of consuming some more bandwidth. Default is """ & - $DefaultP2pReliability & ".", - defaultValue: none(bool), - name: "reliability" - .}: Option[bool] - ## REST HTTP config rest* {. desc: "Enable Waku REST HTTP server: true|false", @@ -577,12 +555,9 @@ hence would have reachability issues.""", .}: string ## Discovery v5 config - # Option-typed; desc states the default since the CLI can't auto-show it for none(). discv5Discovery* {. - desc: - "Enable discovering nodes via Node Discovery v5. Default is " & - $DefaultDiscv5Enabled & ".", - defaultValue: none(bool), + desc: "Enable discovering nodes via Node Discovery v5. Default is true.", + defaultValue: some(true), name: "discv5-discovery" .}: Option[bool] @@ -977,7 +952,7 @@ proc toKeystoreGeneratorConf*(n: WakuNodeConf): RlnKeystoreGeneratorConf = credPassword: n.rlnRelayCredPassword, ) -proc toNetworkPresetConf( +proc toNetworkPresetConf*( preset: string, clusterId: Option[uint16] ): ConfResult[Option[NetworkPresetConf]] = var lcPreset = toLowerAscii(preset) @@ -1146,8 +1121,6 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] = b.filterServiceConf.withMaxCriteria(n.filterMaxCriteria) b.withLightPush(n.lightpush) - if n.reliabilityEnabled.isSome(): - b.withP2pReliability(n.reliabilityEnabled.get()) b.restServerConf.withEnabled(n.rest) b.restServerConf.withListenAddress(n.restAddress) @@ -1190,6 +1163,12 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] = if n.rateLimits.len > 0: b.rateLimitConf.withRateLimits(n.rateLimits) + else: + # The default protocol posture serves filter/lightpush, so default service + # rate limits must apply unless the operator sets their own. + b.rateLimitConf.withRateLimitsIfNotAssigned( + @["filter:100/1s", "lightpush:5/1s", "px:5/1s"] + ) b.withLocalStoragePath(n.localStoragePath) @@ -1206,25 +1185,4 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] = 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() diff --git a/tools/confutils/conf_from_json.nim b/tools/confutils/conf_from_json.nim index 69cca0ce7..9afb145f9 100644 --- a/tools/confutils/conf_from_json.nim +++ b/tools/confutils/conf_from_json.nim @@ -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 ./cli_args + +# The shared JSON walker is `raises: []` so the messaging FFI parser (also +# `raises: []`) can build on it. +{.push raises: [].} proc collectJsonFields*( jsonNode: JsonNode @@ -12,7 +15,7 @@ proc collectJsonFields*( for key, value in jsonNode: let lowerKey = key.toLowerAscii() if jsonFields.hasKey(lowerKey): - let firstKey = jsonFields[lowerKey][0] + let firstKey = jsonFields.getOrDefault(lowerKey)[0] return err( "Duplicate configuration option (case-insensitive): '" & firstKey & "' and '" & key & "'" @@ -27,7 +30,7 @@ proc unknownKeysError( var keys = newSeq[string]() for _, (jsonKey, _) in pairs(jsonFields): keys.add(jsonKey) - return prefix & ": " & $keys + return prefix & ": " & keys.join(", ") proc jsonScalarToString(node: JsonNode): Result[string, string] = ## Convert a scalar JSON value to its string form. @@ -40,20 +43,55 @@ proc jsonScalarToString(node: JsonNode): Result[string, string] = return ok($node.getFloat()) of JBool: return ok($node.getBool()) - of JNull: - return ok("") else: return err("expected scalar JSON value, got " & $node.kind) -proc applyJsonFieldsToConf( - conf: var WakuNodeConf, +proc parseScalarInto[U]( + 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)], parseErrPrefix: string, unknownErrPrefix: string, ): Result[void, string] = - ## Walk `conf`'s fields and write each one matched (case-insensitive) by - ## `jsonFields`. seq fields take a JArray (full replace); scalar fields - ## take any scalar JSON kind. Errors on leftover unknown keys. + ## Write each conf field matched (case-insensitive) by name or CLI `name:` pragma. + ## JSON `null` leaves the field unset; unknown or non-`parseCmdArg` keys error. for confField, confValue in fieldPairs(conf): # Match a field by its name or by its CLI name: pragma; case-insensitive. var matchKey = "" @@ -66,71 +104,52 @@ proc applyJsonFieldsToConf( if matchKey != "": # field-name form already present: set twice return err( "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 if matchKey != "": - let (jsonKey, jsonValue) = jsonFields[matchKey] - when confValue is seq: - if jsonValue.kind != JArray: - return err( - parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & - "' must be a JSON array" - ) - var newSeq: typeof(confValue) = @[] - for item in jsonValue: - let formattedItem = jsonScalarToString(item).valueOr: - return err( - parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & - error - ) - try: - type ElemType = typeof(confValue[0]) - newSeq.add(parseCmdArg(ElemType, formattedItem)) - except CatchableError as e: - return err( - parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & - e.msg & ". Value: " & formattedItem - ) - confValue = newSeq + let (jsonKey, jsonValue) = jsonFields.getOrDefault(matchKey) + if jsonValue.kind == JNull: + # JSON null leaves the field unset; it keeps its default. + jsonFields.del(matchKey) else: - let formattedString = jsonScalarToString(jsonValue).valueOr: - return err( - parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & - error - ) - try: - confValue = parseCmdArg(typeof(confValue), formattedString) - except CatchableError as e: - return err( - parseErrPrefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & - e.msg & ". Value: " & formattedString - ) - jsonFields.del(matchKey) + when confValue is Option: + type Inner = typeof(confValue.get()) + when Inner is seq: + type Elem = typeof(confValue.get()[0]) + when compiles(parseCmdArg(Elem, "")): + confValue = + some(?parseSeqInto[Elem](jsonValue, confField, jsonKey, parseErrPrefix)) + jsonFields.del(matchKey) + else: + return err("config option '" & jsonKey & "' cannot be set via JSON") + else: + when compiles(parseCmdArg(Inner, "")): + 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: return err(unknownKeysError(jsonFields, unknownErrPrefix)) - return ok() + ok() -proc assembleFullConf*( - jsonFields: Table[string, (string, JsonNode)] -): Result[WakuNodeConf, string] = - ## Build a WakuNodeConf from a flat JSON object whose keys are WakuNodeConf field - ## names 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) +{.pop.}