From bc8acf7611a955bea4ce416d486e4299b11d69a7 Mon Sep 17 00:00:00 2001 From: fryorcraken <110212804+fryorcraken@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:31:34 +1000 Subject: [PATCH] feat: Waku API create node (#3580) * introduce createNode # Conflicts: # apps/wakunode2/cli_args.nim * remove confutils dependency on the library * test: remove websocket in default test config * update to latest specs * test: cli_args * align to spec changes (sovereign, message conf, entrypoints * accept enr, entree and multiaddr as entry points * post rebase * format * change from "sovereign" to "core" * add example * get example to continue running * nitpicks * idiomatic constructors * fix enum naming * replace procs with consts * remove messageConfirmation * use pure enum * rename example file --- .../diagnose_connections.nim | 2 +- .../liteprotocoltester/liteprotocoltester.nim | 2 +- .../service_peer_management.nim | 2 +- apps/liteprotocoltester/tester_config.nim | 11 +- apps/liteprotocoltester/v3_publisher.nim | 2 +- apps/wakunode2/wakunode2.nim | 4 +- examples/README.md | 16 +- examples/waku_example.nim | 38 +++ examples/wakustealthcommitments/node_spec.nim | 3 +- .../requests/node_lifecycle_request.nim | 2 +- .../requests/protocols/relay_request.nim | 3 +- tests/all_tests_waku.nim | 8 + tests/api/test_all.nim | 3 + tests/api/test_entry_nodes.nim | 264 +++++++++++++++++ tests/api/test_node_conf.nim | 277 ++++++++++++++++++ tests/common/test_all.nim | 2 - tests/factory/test_all.nim | 2 +- tests/factory/test_node_factory.nim | 8 +- tests/test_waku.nim | 91 ++++++ tests/test_waku_netconfig.nim | 41 ++- tests/testlib/wakunode.nim | 2 - tests/tools/test_all.nim | 3 + .../test_confutils_envvar.nim | 4 +- .../test_confutils_envvar_serialization.nim} | 2 +- tests/waku_discv5/test_waku_discv5.nim | 2 - tests/wakunode2/test_all.nim | 2 +- .../test_cli_args.nim} | 7 +- tests/wakunode2/test_validators.nim | 10 +- .../confutils/cli_args.nim | 23 +- .../defs.nim => tools/confutils/envvar.nim | 2 +- .../net.nim => tools/confutils/envvar_net.nim | 2 +- .../confutils}/envvar_serialization.nim | 0 .../envvar_serialization/reader.nim | 0 .../confutils}/envvar_serialization/utils.nim | 0 .../envvar_serialization/writer.nim | 0 waku.nim | 10 + waku.nimble | 1 + waku/api/api.nim | 17 ++ waku/api/api_conf.nim | 203 +++++++++++++ waku/api/entry_nodes.nim | 77 +++++ .../conf_builder/waku_conf_builder.nim | 2 +- waku/factory/internal_config.nim | 2 +- waku/factory/waku.nim | 1 - waku/node/waku_node.nim | 1 - waku/waku_filter_v2/protocol.nim | 2 +- 45 files changed, 1086 insertions(+), 70 deletions(-) create mode 100644 examples/waku_example.nim create mode 100644 tests/api/test_all.nim create mode 100644 tests/api/test_entry_nodes.nim create mode 100644 tests/api/test_node_conf.nim create mode 100644 tests/test_waku.nim create mode 100644 tests/tools/test_all.nim rename tests/{common => tools}/test_confutils_envvar.nim (93%) rename tests/{common/test_envvar_serialization.nim => tools/test_confutils_envvar_serialization.nim} (84%) rename tests/{factory/test_external_config.nim => wakunode2/test_cli_args.nim} (99%) rename waku/factory/external_config.nim => tools/confutils/cli_args.nim (98%) rename waku/common/confutils/envvar/defs.nim => tools/confutils/envvar.nim (91%) rename waku/common/confutils/envvar/std/net.nim => tools/confutils/envvar_net.nim (94%) rename {waku/common => tools/confutils}/envvar_serialization.nim (100%) rename {waku/common => tools/confutils}/envvar_serialization/reader.nim (100%) rename {waku/common => tools/confutils}/envvar_serialization/utils.nim (100%) rename {waku/common => tools/confutils}/envvar_serialization/writer.nim (100%) create mode 100644 waku.nim create mode 100644 waku/api/api.nim create mode 100644 waku/api/api_conf.nim create mode 100644 waku/api/entry_nodes.nim diff --git a/apps/liteprotocoltester/diagnose_connections.nim b/apps/liteprotocoltester/diagnose_connections.nim index a38de4b7b..f595b4e03 100644 --- a/apps/liteprotocoltester/diagnose_connections.nim +++ b/apps/liteprotocoltester/diagnose_connections.nim @@ -14,8 +14,8 @@ import libp2p/wire import + ../../tools/confutils/cli_args, waku/[ - factory/external_config, node/peer_manager, waku_lightpush/common, waku_relay, diff --git a/apps/liteprotocoltester/liteprotocoltester.nim b/apps/liteprotocoltester/liteprotocoltester.nim index d528efa90..2db9bf5ed 100644 --- a/apps/liteprotocoltester/liteprotocoltester.nim +++ b/apps/liteprotocoltester/liteprotocoltester.nim @@ -11,11 +11,11 @@ import confutils import + ../../tools/confutils/cli_args, waku/[ common/enr, common/logging, factory/waku as waku_factory, - factory/external_config, waku_node, node/waku_metrics, node/peer_manager, diff --git a/apps/liteprotocoltester/service_peer_management.nim b/apps/liteprotocoltester/service_peer_management.nim index 7d79e0f36..a72daa2b6 100644 --- a/apps/liteprotocoltester/service_peer_management.nim +++ b/apps/liteprotocoltester/service_peer_management.nim @@ -11,8 +11,8 @@ import libp2p/wire import + ../wakunode2/cli_args, waku/[ - factory/external_config, common/enr, waku_node, node/peer_manager, diff --git a/apps/liteprotocoltester/tester_config.nim b/apps/liteprotocoltester/tester_config.nim index c06a970b1..dee918b8c 100644 --- a/apps/liteprotocoltester/tester_config.nim +++ b/apps/liteprotocoltester/tester_config.nim @@ -12,14 +12,9 @@ import secp256k1 import - waku/[ - common/confutils/envvar/defs as confEnvvarDefs, - common/confutils/envvar/std/net as confEnvvarNet, - common/logging, - factory/external_config, - waku_core, - waku_core/topics/pubsub_topic, - ] + ../../tools/confutils/ + [cli_args, envvar as confEnvvarDefs, envvar_net as confEnvvarNet], + waku/[common/logging, waku_core, waku_core/topics/pubsub_topic] export confTomlDefs, confTomlNet, confEnvvarDefs, confEnvvarNet diff --git a/apps/liteprotocoltester/v3_publisher.nim b/apps/liteprotocoltester/v3_publisher.nim index 339e13da0..c8353b5a3 100644 --- a/apps/liteprotocoltester/v3_publisher.nim +++ b/apps/liteprotocoltester/v3_publisher.nim @@ -1,5 +1,5 @@ import results, options, chronos -import waku/[waku_node, waku_core, waku_lightpush] +import waku/[waku_node, waku_core, waku_lightpush, waku_lightpush/common] import publisher_base type V3Publisher* = ref object of PublisherBase diff --git a/apps/wakunode2/wakunode2.nim b/apps/wakunode2/wakunode2.nim index aabf13fa4..ac6b38a22 100644 --- a/apps/wakunode2/wakunode2.nim +++ b/apps/wakunode2/wakunode2.nim @@ -9,13 +9,13 @@ import system/ansi_c, libp2p/crypto/crypto import - ../../tools/rln_keystore_generator/rln_keystore_generator, + ../../tools/[rln_keystore_generator/rln_keystore_generator, confutils/cli_args], waku/[ common/logging, - factory/external_config, factory/waku, node/health_monitor, waku_api/rest/builder as rest_server_builder, + waku_core/message/default_values, ] logScope: diff --git a/examples/README.md b/examples/README.md index 995e3f3af..7055cf6f2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,9 +7,21 @@ Make all examples. make example2 ``` -## basic2 +## Waku API -TODO +Uses the simplified Waku API to create and start a node, +you need an RPC endpoint for Linea Sepolia for RLN: + +```console +./build/waku_api --ethRpcEndpoint=https://linea-sepolia.infura.io/v3/ +``` + +If you can't be bothered but still want to see some action, +just run the binary and it will use a non-RLN network: + +```console +./build/waku_api +``` ## publisher/subscriber diff --git a/examples/waku_example.nim b/examples/waku_example.nim new file mode 100644 index 000000000..03964ca7c --- /dev/null +++ b/examples/waku_example.nim @@ -0,0 +1,38 @@ +import std/options +import chronos, results, confutils, confutils/defs +import waku + +type CliArgs = object + ethRpcEndpoint* {. + defaultValue: "", desc: "ETH RPC Endpoint, if passed, RLN is enabled" + .}: string + +when isMainModule: + let args = CliArgs.load() + + echo "Starting Waku node..." + + let config = + if (args.ethRpcEndpoint == ""): + # Create a basic configuration for the Waku node + # No RLN as we don't have an ETH RPC Endpoint + NodeConfig.init(wakuConfig = WakuConfig.init(entryNodes = @[], clusterId = 42)) + else: + # Connect to TWN, use ETH RPC Endpoint for RLN + NodeConfig.init(ethRpcEndpoints = @[args.ethRpcEndpoint]) + + # Create the node using the library API's createNode function + let node = (waitFor createNode(config)).valueOr: + echo "Failed to create node: ", error + quit(QuitFailure) + + echo("Waku node created successfully!") + + # Start the node + (waitFor startWaku(addr node)).isOkOr: + echo "Failed to start node: ", error + quit(QuitFailure) + + echo "Node started successfully!" + + runForever() diff --git a/examples/wakustealthcommitments/node_spec.nim b/examples/wakustealthcommitments/node_spec.nim index 1af949314..bf61c282f 100644 --- a/examples/wakustealthcommitments/node_spec.nim +++ b/examples/wakustealthcommitments/node_spec.nim @@ -1,6 +1,7 @@ {.push raises: [].} -import waku/[common/logging, factory/[waku, networks_config, external_config]] +import ../../apps/wakunode2/cli_args +import waku/[common/logging, factory/[waku, networks_config]] import std/[options, strutils, os, sequtils], chronicles, diff --git a/library/waku_thread_requests/requests/node_lifecycle_request.nim b/library/waku_thread_requests/requests/node_lifecycle_request.nim index 6bc613406..270bdf1ce 100644 --- a/library/waku_thread_requests/requests/node_lifecycle_request.nim +++ b/library/waku_thread_requests/requests/node_lifecycle_request.nim @@ -3,7 +3,7 @@ import chronos, chronicles, results, confutils, confutils/std/net import ../../../waku/node/peer_manager/peer_manager, - ../../../waku/factory/external_config, + ../../../tools/confutils/cli_args, ../../../waku/factory/waku, ../../../waku/factory/node_factory, ../../../waku/factory/networks_config, diff --git a/library/waku_thread_requests/requests/protocols/relay_request.nim b/library/waku_thread_requests/requests/protocols/relay_request.nim index 279a1efb4..5c0732768 100644 --- a/library/waku_thread_requests/requests/protocols/relay_request.nim +++ b/library/waku_thread_requests/requests/protocols/relay_request.nim @@ -2,7 +2,8 @@ import std/[net, sequtils, strutils] import chronicles, chronos, stew/byteutils, results import ../../../../waku/waku_core/message/message, - ../../../../waku/factory/[external_config, validator_signed, waku], + ../../../../waku/factory/[validator_signed, waku], + ../../../../tools/confutils/cli_args, ../../../../waku/waku_node, ../../../../waku/waku_core/message, ../../../../waku/waku_core/time, # Timestamp diff --git a/tests/all_tests_waku.nim b/tests/all_tests_waku.nim index aac92863a..3d22cd9c2 100644 --- a/tests/all_tests_waku.nim +++ b/tests/all_tests_waku.nim @@ -1,5 +1,7 @@ ## Waku v2 +import ./test_waku + # Waku core test suite import ./waku_core/test_namespaced_topics, @@ -96,3 +98,9 @@ import ./waku_rln_relay/test_all # Node Factory import ./factory/test_all + +# Waku API tests +import ./api/test_all + +# Waku tools tests +import ./tools/test_all diff --git a/tests/api/test_all.nim b/tests/api/test_all.nim new file mode 100644 index 000000000..99c1b3b4c --- /dev/null +++ b/tests/api/test_all.nim @@ -0,0 +1,3 @@ +{.used.} + +import ./test_entry_nodes, ./test_node_conf diff --git a/tests/api/test_entry_nodes.nim b/tests/api/test_entry_nodes.nim new file mode 100644 index 000000000..136a49b2b --- /dev/null +++ b/tests/api/test_entry_nodes.nim @@ -0,0 +1,264 @@ +{.used.} + +import std/options, results, testutils/unittests + +import waku/api/entry_nodes + +# Since classifyEntryNode is internal, we test it indirectly through processEntryNodes behavior +# The enum is exported so we can test against it + +suite "Entry Nodes Classification": + test "Process ENRTree - standard format": + let result = processEntryNodes( + @[ + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im" + ] + ) + check: + result.isOk() + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 1 + bootstrapEnrs.len == 0 + staticNodes.len == 0 + + test "Process ENRTree - case insensitive": + let result = processEntryNodes( + @[ + "ENRTREE://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im" + ] + ) + check: + result.isOk() + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 1 + bootstrapEnrs.len == 0 + staticNodes.len == 0 + + test "Process ENR - standard format": + let result = processEntryNodes( + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g" + ] + ) + check: + result.isOk() + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 1 + staticNodes.len == 0 + + test "Process ENR - case insensitive": + let result = processEntryNodes( + @[ + "ENR:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g" + ] + ) + check: + result.isOk() + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 1 + staticNodes.len == 0 + + test "Process Multiaddress - IPv4": + let result = processEntryNodes( + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" + ] + ) + check: + result.isOk() + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 0 + staticNodes.len == 1 + + test "Process Multiaddress - IPv6": + let result = processEntryNodes( + @["/ip6/::1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc"] + ) + check: + result.isOk() + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 0 + staticNodes.len == 1 + + test "Process Multiaddress - DNS": + let result = processEntryNodes( + @[ + "/dns4/example.com/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" + ] + ) + check: + result.isOk() + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 0 + staticNodes.len == 1 + + test "Process empty string": + let result = processEntryNodes(@[""]) + check: + result.isErr() + result.error == "Entry node error: Empty entry node address" + + test "Process invalid format - HTTP URL": + let result = processEntryNodes(@["http://example.com"]) + check: + result.isErr() + result.error == + "Entry node error: Unrecognized entry node format. Must start with 'enrtree:', 'enr:', or '/'" + + test "Process invalid format - some string": + let result = processEntryNodes(@["some-string-here"]) + check: + result.isErr() + result.error == + "Entry node error: Unrecognized entry node format. Must start with 'enrtree:', 'enr:', or '/'" + +suite "Entry Nodes Processing": + test "Process mixed entry nodes": + let entryNodes = + @[ + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im", + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g", + ] + + let result = processEntryNodes(entryNodes) + check: + result.isOk() + + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 1 # enrtree + bootstrapEnrs.len == 1 # enr + staticNodes.len >= 1 # at least the multiaddr + enrTreeUrls[0] == entryNodes[0] # enrtree unchanged + bootstrapEnrs[0] == entryNodes[2] # enr unchanged + staticNodes[0] == entryNodes[1] # multiaddr added to static + + test "Process only ENRTree nodes": + let entryNodes = + @[ + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im", + "enrtree://ANOTHER_TREE@example.com", + ] + + let result = processEntryNodes(entryNodes) + check: + result.isOk() + + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 2 + bootstrapEnrs.len == 0 + staticNodes.len == 0 + enrTreeUrls == entryNodes + + test "Process only multiaddresses": + let entryNodes = + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + "/ip4/192.168.1.1/tcp/60001/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYd", + ] + + let result = processEntryNodes(entryNodes) + check: + result.isOk() + + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 0 + staticNodes.len == 2 + staticNodes == entryNodes + + test "Process only ENR nodes": + let entryNodes = + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g", + "enr:-QEkuECnZ3IbVAgkOzv-QLnKC4dRKAPRY80m1-R7G8jZ7yfT3ipEfBrhKN7ARcQgQ-vg-h40AQzyvAkPYlHPaFKk6u9MBgmlkgnY0iXNlY3AyNTZrMaEDk49D8JjMSns4p1XVNBvJquOUzT4PENSJknkROspfAFGg3RjcIJ2X4N1ZHCCd2g", + ] + + let result = processEntryNodes(entryNodes) + check: + result.isOk() + + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 2 + staticNodes.len == 0 + bootstrapEnrs == entryNodes + # Note: staticNodes may or may not be populated depending on ENR parsing + + test "Process empty list": + let entryNodes: seq[string] = @[] + + let result = processEntryNodes(entryNodes) + check: + result.isOk() + + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 0 + staticNodes.len == 0 + + test "Process with invalid entry": + let entryNodes = @["enrtree://VALID@example.com", "invalid://notvalid"] + + let result = processEntryNodes(entryNodes) + check: + result.isErr() + result.error == + "Entry node error: Unrecognized entry node format. Must start with 'enrtree:', 'enr:', or '/'" + + test "Process different multiaddr formats": + let entryNodes = + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + "/ip6/::1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYd", + "/dns4/example.com/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYe", + "/dns/node.example.org/tcp/443/wss/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYf", + ] + + let result = processEntryNodes(entryNodes) + check: + result.isOk() + + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + enrTreeUrls.len == 0 + bootstrapEnrs.len == 0 + staticNodes.len == 4 + staticNodes == entryNodes + + test "Process with duplicate entries": + let entryNodes = + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im", + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im", + ] + + let result = processEntryNodes(entryNodes) + check: + result.isOk() + + let (enrTreeUrls, bootstrapEnrs, staticNodes) = result.get() + check: + # Duplicates are not filtered out (by design - let downstream handle it) + enrTreeUrls.len == 2 + bootstrapEnrs.len == 0 + staticNodes.len == 2 diff --git a/tests/api/test_node_conf.nim b/tests/api/test_node_conf.nim new file mode 100644 index 000000000..c9b256d42 --- /dev/null +++ b/tests/api/test_node_conf.nim @@ -0,0 +1,277 @@ +{.used.} + +import std/options, results, stint, testutils/unittests +import waku/api/api_conf, waku/factory/waku_conf, waku/factory/networks_config + +suite "LibWaku Conf - toWakuConf": + test "Minimal configuration": + ## Given + let nodeConfig = NodeConfig.init(ethRpcEndpoints = @["http://someaddress"]) + + ## When + let wakuConfRes = toWakuConf(nodeConfig) + + ## Then + let wakuConf = wakuConfRes.valueOr: + raiseAssert error + wakuConf.validate().isOkOr: + raiseAssert error + check: + wakuConf.clusterId == 1 + wakuConf.shardingConf.numShardsInCluster == 8 + wakuConf.staticNodes.len == 0 + + test "Core mode configuration": + ## Given + let wakuConfig = WakuConfig.init(entryNodes = @[], clusterId = 1) + + let nodeConfig = NodeConfig.init(mode = Core, wakuConfig = wakuConfig) + + ## When + let wakuConfRes = toWakuConf(nodeConfig) + + ## Then + require wakuConfRes.isOk() + let wakuConf = wakuConfRes.get() + require wakuConf.validate().isOk() + check: + wakuConf.relay == true + wakuConf.lightPush == true + wakuConf.peerExchangeService == true + wakuConf.clusterId == 1 + + test "Auto-sharding configuration": + ## Given + let nodeConfig = NodeConfig.init( + mode = Core, + wakuConfig = WakuConfig.init( + entryNodes = @[], + staticStoreNodes = @[], + clusterId = 42, + autoShardingConfig = AutoShardingConfig(numShardsInCluster: 16), + ), + ) + + ## When + let wakuConfRes = toWakuConf(nodeConfig) + + ## Then + require wakuConfRes.isOk() + let wakuConf = wakuConfRes.get() + require wakuConf.validate().isOk() + check: + wakuConf.clusterId == 42 + wakuConf.shardingConf.numShardsInCluster == 16 + + test "Bootstrap nodes configuration": + ## Given + let entryNodes = + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g", + "enr:-QEkuECnZ3IbVAgkOzv-QLnKC4dRKAPRY80m1-R7G8jZ7yfT3ipEfBrhKN7ARcQgQ-vg-h40AQzyvAkPYlHPaFKk6u9MBgmlkgnY0iXNlY3AyNTZrMaEDk49D8JjMSns4p1XVNBvJquOUzT4PENSJknkROspfAFGg3RjcIJ2X4N1ZHCCd2g", + ] + let libConf = NodeConfig.init( + mode = Core, + wakuConfig = + WakuConfig.init(entryNodes = entryNodes, staticStoreNodes = @[], clusterId = 1), + ) + + ## When + let wakuConfRes = toWakuConf(libConf) + + ## Then + require wakuConfRes.isOk() + let wakuConf = wakuConfRes.get() + require wakuConf.validate().isOk() + require wakuConf.discv5Conf.isSome() + check: + wakuConf.discv5Conf.get().bootstrapNodes == entryNodes + + test "Static store nodes configuration": + ## Given + let staticStoreNodes = + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + "/ip4/192.168.1.1/tcp/60001/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYd", + ] + let nodeConf = NodeConfig.init( + wakuConfig = WakuConfig.init( + entryNodes = @[], staticStoreNodes = staticStoreNodes, clusterId = 1 + ) + ) + + ## When + let wakuConfRes = toWakuConf(nodeConf) + + ## Then + require wakuConfRes.isOk() + let wakuConf = wakuConfRes.get() + require wakuConf.validate().isOk() + check: + wakuConf.staticNodes == staticStoreNodes + + test "Message validation with max message size": + ## Given + let nodeConfig = NodeConfig.init( + wakuConfig = WakuConfig.init( + entryNodes = @[], + staticStoreNodes = @[], + clusterId = 1, + messageValidation = + MessageValidation(maxMessageSize: "100KiB", rlnConfig: none(RlnConfig)), + ) + ) + + ## When + let wakuConfRes = toWakuConf(nodeConfig) + + ## Then + require wakuConfRes.isOk() + let wakuConf = wakuConfRes.get() + require wakuConf.validate().isOk() + check: + wakuConf.maxMessageSizeBytes == 100'u64 * 1024'u64 + + test "Message validation with RLN config": + ## Given + let nodeConfig = NodeConfig.init( + wakuConfig = WakuConfig.init( + entryNodes = @[], + clusterId = 1, + messageValidation = MessageValidation( + maxMessageSize: "150 KiB", + rlnConfig: some( + RlnConfig( + contractAddress: "0x1234567890123456789012345678901234567890", + chainId: 1'u, + epochSizeSec: 600'u64, + ) + ), + ), + ), + ethRpcEndpoints = @["http://127.0.0.1:1111"], + ) + + ## When + let wakuConf = toWakuConf(nodeConfig).valueOr: + raiseAssert error + + wakuConf.validate().isOkOr: + raiseAssert error + + check: + wakuConf.maxMessageSizeBytes == 150'u64 * 1024'u64 + + require wakuConf.rlnRelayConf.isSome() + let rlnConf = wakuConf.rlnRelayConf.get() + check: + rlnConf.dynamic == true + rlnConf.ethContractAddress == "0x1234567890123456789012345678901234567890" + rlnConf.chainId == 1'u256 + rlnConf.epochSizeSec == 600'u64 + + test "Full Core mode configuration with all fields": + ## Given + let nodeConfig = NodeConfig.init( + mode = Core, + wakuConfig = WakuConfig.init( + entryNodes = + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g" + ], + staticStoreNodes = + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" + ], + clusterId = 99, + autoShardingConfig = AutoShardingConfig(numShardsInCluster: 12), + messageValidation = MessageValidation( + maxMessageSize: "512KiB", + rlnConfig: some( + RlnConfig( + contractAddress: "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + chainId: 5'u, # Goerli + epochSizeSec: 300'u64, + ) + ), + ), + ), + ethRpcEndpoints = @["https://127.0.0.1:8333"], + ) + + ## When + let wakuConfRes = toWakuConf(nodeConfig) + + ## Then + let wakuConf = wakuConfRes.valueOr: + raiseAssert error + wakuConf.validate().isOkOr: + raiseAssert error + + # Check basic settings + check: + wakuConf.relay == true + wakuConf.lightPush == true + wakuConf.peerExchangeService == true + wakuConf.rendezvous == true + wakuConf.clusterId == 99 + + # Check sharding + check: + wakuConf.shardingConf.numShardsInCluster == 12 + + # Check bootstrap nodes + require wakuConf.discv5Conf.isSome() + check: + wakuConf.discv5Conf.get().bootstrapNodes.len == 1 + + # Check static nodes + check: + wakuConf.staticNodes.len == 1 + wakuConf.staticNodes[0] == + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" + + # Check message validation + check: + wakuConf.maxMessageSizeBytes == 512'u64 * 1024'u64 + + # Check RLN config + require wakuConf.rlnRelayConf.isSome() + let rlnConf = wakuConf.rlnRelayConf.get() + check: + rlnConf.dynamic == true + rlnConf.ethContractAddress == "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + rlnConf.chainId == 5'u256 + rlnConf.epochSizeSec == 300'u64 + + test "NodeConfig with mixed entry nodes (integration test)": + ## Given + let entryNodes = + @[ + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im", + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + ] + + let nodeConfig = NodeConfig.init( + mode = Core, + wakuConfig = + WakuConfig.init(entryNodes = entryNodes, staticStoreNodes = @[], clusterId = 1), + ) + + ## When + let wakuConfRes = toWakuConf(nodeConfig) + + ## Then + require wakuConfRes.isOk() + let wakuConf = wakuConfRes.get() + require wakuConf.validate().isOk() + + # Check that ENRTree went to DNS discovery + require wakuConf.dnsDiscoveryConf.isSome() + check: + wakuConf.dnsDiscoveryConf.get().enrTreeUrl == entryNodes[0] + + # Check that multiaddr went to static nodes + check: + wakuConf.staticNodes.len == 1 + wakuConf.staticNodes[0] == entryNodes[1] diff --git a/tests/common/test_all.nim b/tests/common/test_all.nim index ae37337cd..5b4515093 100644 --- a/tests/common/test_all.nim +++ b/tests/common/test_all.nim @@ -2,9 +2,7 @@ import ./test_base64_codec, - ./test_confutils_envvar, ./test_enr_builder, - ./test_envvar_serialization, ./test_protobuf_validation, ./test_sqlite_migrations, ./test_parse_size, diff --git a/tests/factory/test_all.nim b/tests/factory/test_all.nim index 683bc3b10..916610340 100644 --- a/tests/factory/test_all.nim +++ b/tests/factory/test_all.nim @@ -1,3 +1,3 @@ {.used.} -import ./test_external_config, ./test_node_factory, ./test_waku_conf +import ./test_waku_conf, ./test_node_factory diff --git a/tests/factory/test_node_factory.nim b/tests/factory/test_node_factory.nim index 08149db08..f30e079b5 100644 --- a/tests/factory/test_node_factory.nim +++ b/tests/factory/test_node_factory.nim @@ -4,9 +4,10 @@ import testutils/unittests, chronos, libp2p/protocols/connectivity/relay/relay import ../testlib/wakunode, - waku/factory/node_factory, waku/waku_node, - waku/factory/conf_builder/conf_builder + waku/factory/node_factory, + waku/factory/conf_builder/conf_builder, + waku/factory/conf_builder/web_socket_conf_builder suite "Node Factory": asynctest "Set up a node based on default configurations": @@ -48,8 +49,9 @@ asynctest "Set up a node with Filter enabled": check: not node.isNil() not node.wakuFilter.isNil() + echo "TEST END" -asynctest "Start a node based on default configurations": +asynctest "Start a node based on default test configuration": let conf = defaultTestWakuConf() let node = (await setupNode(conf, relay = Relay.new())).valueOr: diff --git a/tests/test_waku.nim b/tests/test_waku.nim new file mode 100644 index 000000000..defec1222 --- /dev/null +++ b/tests/test_waku.nim @@ -0,0 +1,91 @@ +{.used.} + +import chronos, testutils/unittests, std/options + +import waku + +suite "Waku API - Create node": + asyncTest "Create node with minimal configuration": + ## Given + let nodeConfig = + NodeConfig.init(wakuConfig = WakuConfig.init(entryNodes = @[], clusterId = 1)) + + # This is the actual minimal config but as the node auto-start, it is not suitable for tests + # NodeConfig.init(ethRpcEndpoints = @["http://someaddress"]) + + ## When + let node = (await createNode(nodeConfig)).valueOr: + raiseAssert error + + ## Then + check: + not node.isNil() + node.conf.clusterId == 1 + node.conf.relay == true + + asyncTest "Create node with full configuration": + ## Given + let nodeConfig = NodeConfig.init( + mode = Core, + wakuConfig = WakuConfig.init( + entryNodes = + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g" + ], + staticStoreNodes = + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" + ], + clusterId = 99, + autoShardingConfig = AutoShardingConfig(numShardsInCluster: 16), + messageValidation = + MessageValidation(maxMessageSize: "1024 KiB", rlnConfig: none(RlnConfig)), + ), + ) + + ## When + let node = (await createNode(nodeConfig)).valueOr: + raiseAssert error + + ## Then + check: + not node.isNil() + node.conf.clusterId == 99 + node.conf.shardingConf.numShardsInCluster == 16 + node.conf.maxMessageSizeBytes == 1024'u64 * 1024'u64 + node.conf.staticNodes.len == 1 + node.conf.relay == true + node.conf.lightPush == true + node.conf.peerExchangeService == true + node.conf.rendezvous == true + + asyncTest "Create node with mixed entry nodes (enrtree, multiaddr)": + ## Given + let nodeConfig = NodeConfig.init( + mode = Core, + wakuConfig = WakuConfig.init( + entryNodes = + @[ + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im", + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc", + ], + clusterId = 42, + ), + ) + + ## When + let node = (await createNode(nodeConfig)).valueOr: + raiseAssert error + + ## Then + check: + not node.isNil() + node.conf.clusterId == 42 + # ENRTree should go to DNS discovery + node.conf.dnsDiscoveryConf.isSome() + node.conf.dnsDiscoveryConf.get().enrTreeUrl == + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im" + # Multiaddr should go to static nodes + node.conf.staticNodes.len == 1 + node.conf.staticNodes[0] == + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" diff --git a/tests/test_waku_netconfig.nim b/tests/test_waku_netconfig.nim index 712fa4736..5f9ff4b46 100644 --- a/tests/test_waku_netconfig.nim +++ b/tests/test_waku_netconfig.nim @@ -4,7 +4,10 @@ import chronos, confutils/toml/std/net, libp2p/multiaddress, testutils/unittests import ./testlib/wakunode, waku/waku_enr/capabilities -include waku/node/net_config +include + waku/node/net_config, + waku/factory/conf_builder/web_socket_conf_builder, + waku/factory/conf_builder/conf_builder proc defaultTestWakuFlags(): CapabilitiesBitfield = CapabilitiesBitfield.init( @@ -150,9 +153,15 @@ suite "Waku NetConfig": netConfig.announcedAddresses[0] == dns4TcpEndPoint(dns4DomainName, extPort) asyncTest "AnnouncedAddresses includes WebSocket addresses when enabled": - var - conf = defaultTestWakuConf() - wssEnabled = false + var confBuilder = defaultTestWakuConfBuilder() + + confBuilder.webSocketConf.withEnabled(true) + confBuilder.webSocketConf.withWebSocketPort(Port(8000)) + + let conf = confBuilder.build().valueOr: + raiseAssert error + + var wssEnabled = false var netConfigRes = NetConfig.init( bindIp = conf.endpointConf.p2pListenAddress, @@ -197,8 +206,14 @@ suite "Waku NetConfig": ) asyncTest "Announced WebSocket address contains external IP if provided": + var confBuilder = defaultTestWakuConfBuilder() + confBuilder.webSocketConf.withEnabled(true) + confBuilder.webSocketConf.withWebSocketPort(Port(8000)) + + let conf = confBuilder.build().valueOr: + raiseAssert error + let - conf = defaultTestWakuConf() extIp = parseIpAddress("1.2.3.4") extPort = Port(1234) wssEnabled = false @@ -222,8 +237,14 @@ suite "Waku NetConfig": (ip4TcpEndPoint(extIp, conf.websocketConf.get().port) & wsFlag(wssEnabled)) asyncTest "Announced WebSocket address contains dns4DomainName if provided": + var confBuilder = defaultTestWakuConfBuilder() + confBuilder.webSocketConf.withEnabled(true) + confBuilder.webSocketConf.withWebSocketPort(Port(8000)) + + let conf = confBuilder.build().valueOr: + raiseAssert error + let - conf = defaultTestWakuConf() dns4DomainName = "example.com" extPort = Port(1234) wssEnabled = false @@ -249,8 +270,14 @@ suite "Waku NetConfig": ) asyncTest "Announced WebSocket address contains dns4DomainName if provided alongside extIp": + var confBuilder = defaultTestWakuConfBuilder() + confBuilder.webSocketConf.withEnabled(true) + confBuilder.webSocketConf.withWebSocketPort(Port(8000)) + + let conf = confBuilder.build().valueOr: + raiseAssert error + let - conf = defaultTestWakuConf() dns4DomainName = "example.com" extIp = parseIpAddress("1.2.3.4") extPort = Port(1234) diff --git a/tests/testlib/wakunode.nim b/tests/testlib/wakunode.nim index fe040534e..105dc4fd2 100644 --- a/tests/testlib/wakunode.nim +++ b/tests/testlib/wakunode.nim @@ -42,8 +42,6 @@ proc defaultTestWakuConfBuilder*(): WakuConfBuilder = builder.withRendezvous(true) builder.storeServiceConf.withDbMigration(false) builder.storeServiceConf.withSupportV2(false) - builder.webSocketConf.withWebSocketPort(Port(8000)) - builder.webSocketConf.withEnabled(true) return builder proc defaultTestWakuConf*(): WakuConf = diff --git a/tests/tools/test_all.nim b/tests/tools/test_all.nim new file mode 100644 index 000000000..e5ace3dfa --- /dev/null +++ b/tests/tools/test_all.nim @@ -0,0 +1,3 @@ +{.used.} + +import ./test_confutils_envvar, ./test_confutils_envvar_serialization.nim diff --git a/tests/common/test_confutils_envvar.nim b/tests/tools/test_confutils_envvar.nim similarity index 93% rename from tests/common/test_confutils_envvar.nim rename to tests/tools/test_confutils_envvar.nim index fca11cca6..ed559ad0b 100644 --- a/tests/common/test_confutils_envvar.nim +++ b/tests/tools/test_confutils_envvar.nim @@ -7,9 +7,7 @@ import confutils, confutils/defs, confutils/std/net -import - waku/common/confutils/envvar/defs as confEnvvarDefs, - waku/common/confutils/envvar/std/net as confEnvvarNet +import ../../tools/confutils/[envvar as confEnvvarDefs, envvar_net as confEnvvarNet] type ConfResult[T] = Result[T, string] diff --git a/tests/common/test_envvar_serialization.nim b/tests/tools/test_confutils_envvar_serialization.nim similarity index 84% rename from tests/common/test_envvar_serialization.nim rename to tests/tools/test_confutils_envvar_serialization.nim index 3a46ac537..c9639e284 100644 --- a/tests/common/test_envvar_serialization.nim +++ b/tests/tools/test_confutils_envvar_serialization.nim @@ -1,7 +1,7 @@ {.used.} import testutils/unittests -import waku/common/envvar_serialization/utils +import ../../tools/confutils/envvar_serialization/utils suite "nim-envvar-serialization - utils": test "construct env var key": diff --git a/tests/waku_discv5/test_waku_discv5.nim b/tests/waku_discv5/test_waku_discv5.nim index 325e7a21d..abdf09626 100644 --- a/tests/waku_discv5/test_waku_discv5.nim +++ b/tests/waku_discv5/test_waku_discv5.nim @@ -439,7 +439,6 @@ suite "Waku Discovery v5": confBuilder.discv5Conf.withEnabled(true) confBuilder.discv5Conf.withUdpPort(9001.Port) confBuilder.withP2pTcpPort(60001.Port) - confBuilder.websocketConf.withEnabled(false) let conf1 = confBuilder.build().valueOr: raiseAssert error @@ -456,7 +455,6 @@ suite "Waku Discovery v5": confBuilder.withP2pTcpPort(60003.Port) confBuilder.discv5Conf.withUdpPort(9003.Port) confBuilder.withNodeKey(crypto.PrivateKey.random(Secp256k1, myRng[])[]) - confBuilder.websocketConf.withEnabled(false) let conf2 = confBuilder.build().valueOr: raiseAssert error diff --git a/tests/wakunode2/test_all.nim b/tests/wakunode2/test_all.nim index 76bc9aaad..7e658d4ca 100644 --- a/tests/wakunode2/test_all.nim +++ b/tests/wakunode2/test_all.nim @@ -1,3 +1,3 @@ {.used.} -import ./test_app, ./test_validators +import ./test_app, ./test_validators, ./test_cli_args diff --git a/tests/factory/test_external_config.nim b/tests/wakunode2/test_cli_args.nim similarity index 99% rename from tests/factory/test_external_config.nim rename to tests/wakunode2/test_cli_args.nim index 16270bdbd..dabc78083 100644 --- a/tests/factory/test_external_config.nim +++ b/tests/wakunode2/test_cli_args.nim @@ -10,12 +10,15 @@ import secp256k1, confutils, stint + +import tools/confutils/cli_args + import - ../../waku/factory/external_config, ../../waku/factory/networks_config, ../../waku/factory/waku_conf, ../../waku/common/logging, - ../../waku/common/utils/parse_size_units + ../../waku/common/utils/parse_size_units, + ../../waku/waku_core/message/default_values suite "Waku external config - default values": test "Default sharding value": diff --git a/tests/wakunode2/test_validators.nim b/tests/wakunode2/test_validators.nim index b0a8dd8fb..83928e4df 100644 --- a/tests/wakunode2/test_validators.nim +++ b/tests/wakunode2/test_validators.nim @@ -12,14 +12,8 @@ import libp2p/multihash, secp256k1 import - waku/[ - waku_core, - node/peer_manager, - waku_node, - waku_relay, - factory/external_config, - factory/validator_signed, - ], + waku/[waku_core, node/peer_manager, waku_node, factory/validator_signed], + tools/confutils/cli_args, ../testlib/wakucore, ../testlib/wakunode diff --git a/waku/factory/external_config.nim b/tools/confutils/cli_args.nim similarity index 98% rename from waku/factory/external_config.nim rename to tools/confutils/cli_args.nim index b4f8e9b0b..c4bd66af0 100644 --- a/waku/factory/external_config.nim +++ b/tools/confutils/cli_args.nim @@ -19,23 +19,22 @@ import json import - ./waku_conf, - ./conf_builder/conf_builder, - ./networks_config, - ../common/confutils/envvar/defs as confEnvvarDefs, - ../common/confutils/envvar/std/net as confEnvvarNet, - ../common/logging, - ../waku_enr, - ../node/peer_manager, - ../waku_core/topics/pubsub_topic, + waku/factory/[waku_conf, conf_builder/conf_builder, networks_config], + waku/common/[logging], + waku/[ + waku_enr, + node/peer_manager, + waku_core/topics/pubsub_topic, + waku_core/message/default_values, + ], ../../tools/rln_keystore_generator/rln_keystore_generator -include ../waku_core/message/default_values +import ./envvar as confEnvvarDefs, ./envvar_net as confEnvvarNet export confTomlDefs, confTomlNet, confEnvvarDefs, confEnvvarNet, ProtectedShard logScope: - topics = "waku external config" + topics = "waku cli args" # Git version in git describe format (defined at compile time) const git_version* {.strdefine.} = "n/a" @@ -161,7 +160,7 @@ type WakuNodeConf* = object .}: uint16 agentString* {. - defaultValue: "nwaku-" & external_config.git_version, + defaultValue: "nwaku-" & cli_args.git_version, desc: "Node agent string which is used as identifier in network", name: "agent-string" .}: string diff --git a/waku/common/confutils/envvar/defs.nim b/tools/confutils/envvar.nim similarity index 91% rename from waku/common/confutils/envvar/defs.nim rename to tools/confutils/envvar.nim index 4f71bd88f..ee73594aa 100644 --- a/waku/common/confutils/envvar/defs.nim +++ b/tools/confutils/envvar.nim @@ -1,7 +1,7 @@ {.push raises: [].} import confutils/defs as confutilsDefs -import ../../envvar_serialization +import ./envvar_serialization export envvar_serialization, confutilsDefs diff --git a/waku/common/confutils/envvar/std/net.nim b/tools/confutils/envvar_net.nim similarity index 94% rename from waku/common/confutils/envvar/std/net.nim rename to tools/confutils/envvar_net.nim index 4264373a6..0c689f0e2 100644 --- a/waku/common/confutils/envvar/std/net.nim +++ b/tools/confutils/envvar_net.nim @@ -1,7 +1,7 @@ {.push raises: [].} import std/[strutils, net] -import ../../../envvar_serialization +import ./envvar_serialization export net, envvar_serialization diff --git a/waku/common/envvar_serialization.nim b/tools/confutils/envvar_serialization.nim similarity index 100% rename from waku/common/envvar_serialization.nim rename to tools/confutils/envvar_serialization.nim diff --git a/waku/common/envvar_serialization/reader.nim b/tools/confutils/envvar_serialization/reader.nim similarity index 100% rename from waku/common/envvar_serialization/reader.nim rename to tools/confutils/envvar_serialization/reader.nim diff --git a/waku/common/envvar_serialization/utils.nim b/tools/confutils/envvar_serialization/utils.nim similarity index 100% rename from waku/common/envvar_serialization/utils.nim rename to tools/confutils/envvar_serialization/utils.nim diff --git a/waku/common/envvar_serialization/writer.nim b/tools/confutils/envvar_serialization/writer.nim similarity index 100% rename from waku/common/envvar_serialization/writer.nim rename to tools/confutils/envvar_serialization/writer.nim diff --git a/waku.nim b/waku.nim new file mode 100644 index 000000000..18d52741e --- /dev/null +++ b/waku.nim @@ -0,0 +1,10 @@ +## Main module for using nwaku as a Nimble library +## +## This module re-exports the public API for creating and managing Waku nodes +## when using nwaku as a library dependency. + +import waku/api/[api, api_conf] +export api, api_conf + +import waku/factory/waku +export waku diff --git a/waku.nimble b/waku.nimble index e426a9f7a..6de73ad96 100644 --- a/waku.nimble +++ b/waku.nimble @@ -140,6 +140,7 @@ task testwakunode2, "Build & run wakunode2 app tests": test "all_tests_wakunode2" task example2, "Build Waku examples": + buildBinary "waku_example", "examples/" buildBinary "publisher", "examples/" buildBinary "subscriber", "examples/" buildBinary "filter_subscriber", "examples/" diff --git a/waku/api/api.nim b/waku/api/api.nim new file mode 100644 index 000000000..5bab06188 --- /dev/null +++ b/waku/api/api.nim @@ -0,0 +1,17 @@ +import chronicles, chronos, results + +import waku/factory/waku + +import ./api_conf + +# TODO: Specs says it should return a `WakuNode`. As `send` and other APIs are defined, we can align. +proc createNode*(config: NodeConfig): Future[Result[Waku, string]] {.async.} = + let wakuConf = toWakuConf(config).valueOr: + return err("Failed to handle the configuration: " & error) + + ## We are not defining app callbacks at node creation + let wakuRes = (await Waku.new(wakuConf)).valueOr: + error "waku initialization failed", error = error + return err("Failed setting up Waku: " & $error) + + return ok(wakuRes) diff --git a/waku/api/api_conf.nim b/waku/api/api_conf.nim new file mode 100644 index 000000000..360c397af --- /dev/null +++ b/waku/api/api_conf.nim @@ -0,0 +1,203 @@ +import std/[net, options] + +import results + +import + waku/common/utils/parse_size_units, + waku/factory/waku_conf, + waku/factory/conf_builder/conf_builder, + waku/factory/networks_config, + ./entry_nodes + +type AutoShardingConfig* {.requiresInit.} = object + numShardsInCluster*: uint16 + +type RlnConfig* {.requiresInit.} = object + contractAddress*: string + chainId*: uint + epochSizeSec*: uint64 + +type NetworkingConfig* {.requiresInit.} = object + listenIpv4*: string + p2pTcpPort*: uint16 + discv5UdpPort*: uint16 + +type MessageValidation* {.requiresInit.} = object + maxMessageSize*: string # Accepts formats like "150 KiB", "1500 B" + rlnConfig*: Option[RlnConfig] + +type WakuConfig* {.requiresInit.} = 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[WakuConfig], + 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* = WakuConfig( + 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.} = object + mode: WakuMode + wakuConfig: WakuConfig + networkingConfig: NetworkingConfig + ethRpcEndpoints: seq[string] + +proc init*( + T: typedesc[NodeConfig], + mode: WakuMode = WakuMode.Core, + wakuConfig: WakuConfig = TheWakuNetworkPreset, + networkingConfig: NetworkingConfig = DefaultNetworkingConfig, + ethRpcEndpoints: seq[string] = @[], +): T = + return T( + mode: mode, + wakuConfig: wakuConfig, + networkingConfig: networkingConfig, + ethRpcEndpoints: ethRpcEndpoints, + ) + +proc toWakuConf*(nodeConfig: NodeConfig): Result[WakuConf, string] = + var b = WakuConfBuilder.init() + + # 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: + return err("Edge mode is not implemented") + + ## Network Conf + let wakuConfig = nodeConfig.wakuConfig + + # Set cluster ID + b.withClusterId(wakuConfig.clusterId) + + # Set sharding configuration + b.withShardingConf(ShardingConfKind.AutoSharding) + let autoShardingConfig = wakuConfig.autoShardingConfig + b.withNumShardsInCluster(autoShardingConfig.numShardsInCluster) + + # Process entry nodes - supports enrtree:, enr:, and multiaddress formats + if wakuConfig.entryNodes.len > 0: + let (enrTreeUrls, bootstrapEnrs, staticNodesFromEntry) = processEntryNodes( + wakuConfig.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 wakuConfig.staticStoreNodes.len > 0: + b.withStaticNodes(wakuConfig.staticStoreNodes) + + # Set message validation + let msgValidation = wakuConfig.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") + + 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) diff --git a/waku/api/entry_nodes.nim b/waku/api/entry_nodes.nim new file mode 100644 index 000000000..2dad853b8 --- /dev/null +++ b/waku/api/entry_nodes.nim @@ -0,0 +1,77 @@ +import std/strutils + +import results, eth/p2p/discoveryv5/enr + +import waku/waku_core/peers + +type EntryNodeType {.pure.} = enum + EnrTree + Enr + Multiaddr + +proc classifyEntryNode(address: string): Result[EntryNodeType, string] = + ## Classifies an entry node address by its type + ## Returns the type as EntryNodeType enum + if address.len == 0: + return err("Empty entry node address") + + let lowerAddress = address.toLowerAscii() + if lowerAddress.startsWith("enrtree:"): + return ok(EnrTree) + elif lowerAddress.startsWith("enr:"): + return ok(Enr) + elif address[0] == '/': + return ok(Multiaddr) + else: + return + err("Unrecognized entry node format. Must start with 'enrtree:', 'enr:', or '/'") + +proc parseEnrToMultiaddrs(enrStr: string): Result[seq[string], string] = + ## Parses an ENR string and extracts multiaddresses from it + let enrRec = enr.Record.fromURI(enrStr).valueOr: + return err("Invalid ENR record") + + let remotePeerInfo = toRemotePeerInfo(enrRec).valueOr: + return err("Failed to convert ENR to peer info: " & $error) + + # Convert RemotePeerInfo addresses to multiaddr strings + var multiaddrs: seq[string] + for addr in remotePeerInfo.addrs: + multiaddrs.add($addr & "/p2p/" & $remotePeerInfo.peerId) + + if multiaddrs.len == 0: + return err("No valid addresses found in ENR") + + return ok(multiaddrs) + +proc processEntryNodes*( + entryNodes: seq[string] +): Result[(seq[string], seq[string], seq[string]), string] = + ## Processes entry nodes and returns (enrTreeUrls, bootstrapEnrs, staticNodes) + ## ENRTree URLs for DNS discovery, ENR records for bootstrap, multiaddrs for static nodes + var enrTreeUrls: seq[string] + var bootstrapEnrs: seq[string] + var staticNodes: seq[string] + + for node in entryNodes: + let nodeType = classifyEntryNode(node).valueOr: + return err("Entry node error: " & error) + + case nodeType + of EnrTree: + # ENRTree URLs go to DNS discovery configuration + enrTreeUrls.add(node) + of Enr: + # ENR records go to bootstrap nodes for discv5 + bootstrapEnrs.add(node) + # Additionally, extract multiaddrs for static connections + let multiaddrsRes = parseEnrToMultiaddrs(node) + if multiaddrsRes.isOk(): + for maddr in multiaddrsRes.get(): + staticNodes.add(maddr) + # If we can't extract multiaddrs, just use it as bootstrap (already added above) + of Multiaddr: + # Multiaddresses go to static nodes + staticNodes.add(node) + + return ok((enrTreeUrls, bootstrapEnrs, staticNodes)) diff --git a/waku/factory/conf_builder/waku_conf_builder.nim b/waku/factory/conf_builder/waku_conf_builder.nim index 84348c248..645869247 100644 --- a/waku/factory/conf_builder/waku_conf_builder.nim +++ b/waku/factory/conf_builder/waku_conf_builder.nim @@ -1,7 +1,7 @@ import libp2p/crypto/crypto, libp2p/multiaddress, - std/[net, options, sequtils, strutils], + std/[net, options, sequtils], stint, chronicles, chronos, diff --git a/waku/factory/internal_config.nim b/waku/factory/internal_config.nim index 9c593d235..5a8e219c7 100644 --- a/waku/factory/internal_config.nim +++ b/waku/factory/internal_config.nim @@ -5,7 +5,7 @@ import libp2p/crypto/curve25519, libp2p/multiaddress, libp2p/nameresolving/dnsresolver, - std/[options, sequtils, net, strutils], + std/[options, sequtils, net], results import ../common/utils/nat, ../node/net_config, ../waku_enr, ../waku_core, ./waku_conf diff --git a/waku/factory/waku.nim b/waku/factory/waku.nim index c6196337f..7c2c430b6 100644 --- a/waku/factory/waku.nim +++ b/waku/factory/waku.nim @@ -42,7 +42,6 @@ import ../waku_filter_v2, ../factory/node_factory, ../factory/internal_config, - ../factory/external_config, ../factory/app_callbacks, ../waku_enr/multiaddr, ./waku_conf diff --git a/waku/node/waku_node.nim b/waku/node/waku_node.nim index bbda3f5ea..d47ac31dc 100644 --- a/waku/node/waku_node.nim +++ b/waku/node/waku_node.nim @@ -57,7 +57,6 @@ import ../common/rate_limit/setting, ../common/callbacks, ../common/nimchronos, - ../waku_enr/mix, ../waku_mix declarePublicCounter waku_node_messages, "number of messages received", ["type"] diff --git a/waku/waku_filter_v2/protocol.nim b/waku/waku_filter_v2/protocol.nim index 80f60fdd3..8b419c300 100644 --- a/waku/waku_filter_v2/protocol.nim +++ b/waku/waku_filter_v2/protocol.nim @@ -3,7 +3,7 @@ {.push raises: [].} import - std/[options, sequtils, sets, strutils, tables], + std/[options, sequtils, sets, tables], stew/byteutils, chronicles, chronos,