feat: improve config v3 (#4015)

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

View File

@ -50,12 +50,6 @@ struct ConfigNode
int relay;
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();

View File

@ -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);

View File

@ -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)

View File

@ -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
}
}
"""

View File

@ -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);
};

View File

@ -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) {

View File

@ -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)

View File

@ -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
}
}
)";

View File

@ -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 {

View File

@ -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:

View File

@ -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");

View File

@ -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": "<network preset>" (e.g. "twn")
// "messagingOverrides": { ... } (per-field messaging config overrides)
// "channelsOverrides": { ... } (per-field reliable-channel overrides)
// Override keys accept the config field name or its CLI switch name (e.g.
// "clusterId" or "cluster-id"). Unknown keys are rejected.
// Example: {"mode":"Core","messagingOverrides":{"cluster-id":42,"log-level":"INFO"}}
void *logosdelivery_create_node(
const char *configJson,
FFICallBack callback,

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import logos_delivery/channels/reliable_channel_manager
import logos_delivery/channels/reliable_channel
import logos_delivery/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(

View File

@ -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`,

View File

@ -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()

View File

@ -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(

View File

@ -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),
)

View File

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

View File

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

View File

@ -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)

View File

@ -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

View File

@ -8,6 +8,7 @@ import ../waku_archive/archive_utils
import logos_delivery, logos_delivery/waku/[waku_node, waku_core, waku_relay/protocol]
import logos_delivery/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

View File

@ -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 =

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

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

View File

@ -1,196 +1,10 @@
{.used.}
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()

View File

@ -13,6 +13,7 @@ import logos_delivery/waku/factory/waku_conf
import logos_delivery/api/events/messaging_client_events as waku_message_events
import logos_delivery/api/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()

View File

@ -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

View File

@ -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 = @[

View File

@ -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()

View File

@ -1,6 +1,9 @@
import std/[json, macros, strutils, tables]
import std/[json, macros, options, strutils, tables]
import confutils, confutils/defs, confutils/std/net, results
import ./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.}