mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-07-09 17:59:27 +00:00
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:
parent
0a1700e239
commit
90fa5fa91f
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
4
logos_delivery/api/kernel_conf.nim
Normal file
4
logos_delivery/api/kernel_conf.nim
Normal file
@ -0,0 +1,4 @@
|
||||
import tools/confutils/cli_args
|
||||
export cli_args
|
||||
|
||||
type KernelConf* = WakuNodeConf ## Alias of the CLI-facing `WakuNodeConf`.
|
||||
31
logos_delivery/api/logos_delivery_conf.nim
Normal file
31
logos_delivery/api/logos_delivery_conf.nim
Normal 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.}
|
||||
86
logos_delivery/api/logos_delivery_conf_json.nim
Normal file
86
logos_delivery/api/logos_delivery_conf_json.nim
Normal 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.}
|
||||
95
logos_delivery/api/messaging_conf.nim
Normal file
95
logos_delivery/api/messaging_conf.nim
Normal 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)))
|
||||
@ -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(
|
||||
|
||||
@ -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`,
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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),
|
||||
)
|
||||
|
||||
@ -155,8 +155,6 @@ type WakuConf* {.requiresInit.} = ref object
|
||||
# TODO: use proper type
|
||||
relayServiceRatio*: string
|
||||
|
||||
p2pReliability*: bool
|
||||
|
||||
localStoragePath*: string
|
||||
|
||||
proc logConf*(conf: WakuConf) =
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import
|
||||
./test_entry_nodes,
|
||||
./test_node_conf,
|
||||
./test_conf,
|
||||
./test_api_send,
|
||||
./test_api_subscription,
|
||||
./test_api_receive,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
261
tests/api/test_conf.nim
Normal 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
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = @[
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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.}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user