diff --git a/library/libwaku.nim b/library/libwaku.nim index ad3afa1340..ff25e5cdc2 100644 --- a/library/libwaku.nim +++ b/library/libwaku.nim @@ -98,7 +98,7 @@ proc initializeLibrary() {.exported.} = ################################################################################ ################################################################################ -### Exported procs +### FFI Exported procs proc waku_new( configJson: cstring, callback: WakuCallback, userData: pointer @@ -849,5 +849,5 @@ proc waku_is_online( userData, ) -### End of exported procs +### End of FFI exported procs ################################################################################ diff --git a/library/libwaku_api.nim b/library/libwaku_api.nim new file mode 100644 index 0000000000..a13f68d14c --- /dev/null +++ b/library/libwaku_api.nim @@ -0,0 +1,16 @@ +import chronicles, chronos, results + +import waku/factory/waku + +import ./libwaku_conf + +proc createNode*(config: LibWakuConfig): 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/library/libwaku_conf.nim b/library/libwaku_conf.nim new file mode 100644 index 0000000000..8eb84d33ad --- /dev/null +++ b/library/libwaku_conf.nim @@ -0,0 +1,149 @@ +import + std/options, + results, + waku/factory/waku_conf, + waku/factory/conf_builder/conf_builder, + waku/factory/networks_config + +type ShardingMode* = enum + AutoSharding = "auto" + StaticSharding = "static" + +type AutoShardingConfig* = object + numShardsInCluster*: uint16 + +type RlnConfig* = object + contractAddress*: string + chainId*: uint + epochSizeSec*: uint64 + +type MessageValidation* = object + maxMessageSizeBytes*: uint64 + rlnConfig*: Option[RlnConfig] + +type NetworkConfig* = object + bootstrapNodes*: seq[string] + staticStoreNodes*: seq[string] + clusterId*: uint16 + shardingMode*: Option[ShardingMode] + autoShardingConfig*: Option[AutoShardingConfig] + messageValidation*: Option[MessageValidation] + +type WakuMode* = enum + Edge = "edge" + Relay = "relay" + +type LibWakuConfig* = object + mode*: WakuMode + networkConfig*: Option[NetworkConfig] + storeConfirmation*: bool + ethRpcEndpoints*: seq[string] + +proc DefaultShardingMode(): ShardingMode = + return ShardingMode.Autosharding + +proc DefaultAutoShardingConfig(): AutoShardingConfig = + return AutoShardingConfig(numShardsInCluster: 1) + +proc DefaultNetworkConfig(): NetworkConfig = + return NetworkConfig( + bootstrapNodes: + @[ + "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im" + ], + staticStoreNodes: @[], # TODO + clusterId: 1, + shardingMode: some(ShardingMode.AutoSharding), + autoShardingConfig: some(AutoShardingConfig(numShardsInCluster: 8)), + messageValidation: some( + MessageValidation( + maxMessageSizeBytes: 153600, + rlnConfig: some ( + RlnConfig( + contractAddress: "0xB9cd878C90E49F797B4431fBF4fb333108CB90e6", + chain_id: 59141, + epoch_size_sec: 600, # 10 minutes + ) + ), + ) + ), + ) + +proc DefaultMessageValidation(): MessageValidation = + return MessageValidation(maxMessageSizeBytes: 153600, rlnConfig: none(RlnConfig)) + +proc toWakuConf*(config: LibWakuConfig): Result[WakuConf, string] = + var b = WakuConfBuilder.init() + + case config.mode + of Relay: + b.withRelay(true) + + b.filterServiceConf.withEnabled(true) + b.filterServiceConf.withMaxPeersToServe(20) + + b.withLightPush(true) + + b.discv5Conf.withEnabled(true) + b.withPeerExchange(true) + + b.rateLimitConf.withRateLimits(@["filter:100/1s", "lightpush:5/1s", "px:5/1s"]) + of Edge: + return err("Edge mode is not implemented") + + #TODO: store confirmation + + ## Network Conf + let networkConfig = config.networkConfig.get(DefaultNetworkConfig()) + + # Set cluster ID + b.withClusterId(networkConfig.clusterId) + + # Set sharding configuration + case networkConfig.shardingMode.get(DefaultShardingMode()) + of AutoSharding: + b.withShardingConf(ShardingConfKind.AutoSharding) + let autoShardingConfig = + networkConfig.autoShardingConfig.get(DefaultAutoShardingConfig()) + b.withNumShardsInCluster(autoShardingConfig.numShardsInCluster) + of StaticSharding: + b.withShardingConf(ShardingConfKind.StaticSharding) + + # Set bootstrap nodes + if networkConfig.bootstrapNodes.len > 0: + b.discv5Conf.withBootstrapNodes(networkConfig.bootstrapNodes) + + # TODO: verify behaviour + # Set static store nodes + if networkConfig.staticStoreNodes.len > 0: + b.withStaticNodes(networkConfig.staticStoreNodes) + + # Set message validation + + let msgValidation = networkConfig.messageValidation.get(DefaultMessageValidation()) + b.withMaxMessageSize(msgValidation.maxMessageSizeBytes) + + # 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(config.ethRpcEndpoints) + + # TODO: we should get rid of those two + b.rlnRelayconf.withUserMessageLimit(100) + b.rlnRelayConf.withTreePath("./rln_tree") + + ## 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/tests/all_tests_waku.nim b/tests/all_tests_waku.nim index aac92863af..2f9c31f39f 100644 --- a/tests/all_tests_waku.nim +++ b/tests/all_tests_waku.nim @@ -96,3 +96,6 @@ import ./waku_rln_relay/test_all # Node Factory import ./factory/test_all + +# Library tests +import ./library/test_all diff --git a/tests/library/test_all.nim b/tests/library/test_all.nim new file mode 100644 index 0000000000..30797e8e08 --- /dev/null +++ b/tests/library/test_all.nim @@ -0,0 +1,3 @@ +{.used.} + +import ./test_libwaku_conf, ./test_libwaku diff --git a/tests/library/test_libwaku.nim b/tests/library/test_libwaku.nim new file mode 100644 index 0000000000..a34ab3fc24 --- /dev/null +++ b/tests/library/test_libwaku.nim @@ -0,0 +1,102 @@ +{.used.} + +import std/options, results, chronos, testutils/unittests +import library/libwaku_api, library/libwaku_conf, waku/factory/waku + +suite "LibWaku - createNode": + asyncTest "Create node with minimal Relay configuration": + ## Given + let libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: @[], + staticStoreNodes: @[], + clusterId: 1, + shardingMode: some(StaticSharding), + autoShardingConfig: none(AutoShardingConfig), + messageValidation: none(MessageValidation), + ) + ), + storeConfirmation: false, + ) + + ## When + let node = (await createNode(libConf)).valueOr: + raiseAssert error + + ## Then + check: + not node.isNil() + node.conf.clusterId == 1 + node.conf.relay == true + + asyncTest "Create node with auto-sharding configuration": + ## Given + let libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: @[], + staticStoreNodes: @[], + clusterId: 42, + shardingMode: some(AutoSharding), + autoShardingConfig: some(AutoShardingConfig(numShardsInCluster: 8)), + messageValidation: none(MessageValidation), + ) + ), + storeConfirmation: false, + ) + + ## When + let node = (await createNode(libConf)).valueOr: + raiseAssert error + + ## Then + check: + not node.isNil() + node.conf.clusterId == 42 + node.conf.shardingConf.numShardsInCluster == 8 + + asyncTest "Create node with full configuration": + ## Given + let libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g" + ], + staticStoreNodes: + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" + ], + clusterId: 99, + shardingMode: some(AutoSharding), + autoShardingConfig: some(AutoShardingConfig(numShardsInCluster: 16)), + messageValidation: some( + MessageValidation( + maxMessageSizeBytes: 1024'u64 * 1024'u64, # 1MB + rlnConfig: none(RlnConfig), + ) + ), + ) + ), + storeConfirmation: true, + ) + + ## When + let node = (await createNode(libConf)).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 diff --git a/tests/library/test_libwaku_conf.nim b/tests/library/test_libwaku_conf.nim new file mode 100644 index 0000000000..d2a86aedbd --- /dev/null +++ b/tests/library/test_libwaku_conf.nim @@ -0,0 +1,307 @@ +{.used.} + +import std/options, results, stint, testutils/unittests +import library/libwaku_conf, waku/factory/waku_conf, waku/factory/networks_config + +suite "LibWaku Conf - toWakuConf": + test "Relay mode configuration": + ## Given + let libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: @[], + staticStoreNodes: @[], + clusterId: 1, + shardingMode: some(ShardingMode.StaticSharding), + autoShardingConfig: none(AutoShardingConfig), + messageValidation: none(MessageValidation), + ) + ), + storeConfirmation: false, + ) + + ## When + let wakuConfRes = toWakuConf(libConf) + + ## 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 + wakuConf.shardingConf.kind == ShardingConfKind.StaticSharding + + test "Auto-sharding configuration": + ## Given + let libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: @[], + staticStoreNodes: @[], + clusterId: 42, + shardingMode: some(ShardingMode.AutoSharding), + autoShardingConfig: some(AutoShardingConfig(numShardsInCluster: 16)), + messageValidation: none(MessageValidation), + ) + ), + storeConfirmation: false, + ) + + ## When + let wakuConfRes = toWakuConf(libConf) + + ## Then + require wakuConfRes.isOk() + let wakuConf = wakuConfRes.get() + require wakuConf.validate().isOk() + check: + wakuConf.clusterId == 42 + wakuConf.shardingConf.kind == ShardingConfKind.AutoSharding + wakuConf.shardingConf.numShardsInCluster == 16 + + test "Bootstrap nodes configuration": + ## Given + let bootstrapNodes = + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g", + "enr:-QEkuECnZ3IbVAgkOzv-QLnKC4dRKAPRY80m1-R7G8jZ7yfT3ipEfBrhKN7ARcQgQ-vg-h40AQzyvAkPYlHPaFKk6u9MBgmlkgnY0iXNlY3AyNTZrMaEDk49D8JjMSns4p1XVNBvJquOUzT4PENSJknkROspfAFGg3RjcIJ2X4N1ZHCCd2g", + ] + let libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: bootstrapNodes, + staticStoreNodes: @[], + clusterId: 1, + shardingMode: some(ShardingMode.StaticSharding), + autoShardingConfig: none(AutoShardingConfig), + messageValidation: none(MessageValidation), + ) + ), + storeConfirmation: false, + ) + + ## 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 == bootstrapNodes + + 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 libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: @[], + staticStoreNodes: staticStoreNodes, + clusterId: 1, + shardingMode: some(ShardingMode.StaticSharding), + autoShardingConfig: none(AutoShardingConfig), + messageValidation: none(MessageValidation), + ) + ), + storeConfirmation: false, + ) + + ## When + let wakuConfRes = toWakuConf(libConf) + + ## 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 libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: @[], + staticStoreNodes: @[], + clusterId: 1, + shardingMode: some(ShardingMode.StaticSharding), + autoShardingConfig: none(AutoShardingConfig), + messageValidation: some( + MessageValidation( + maxMessageSizeBytes: 100'u64 * 1024'u64, # 100kB + rlnConfig: none(RlnConfig), + ) + ), + ) + ), + storeConfirmation: false, + ) + + ## When + let wakuConfRes = toWakuConf(libConf) + + ## 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 libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: @[], + staticStoreNodes: @[], + clusterId: 1, + shardingMode: some(ShardingMode.StaticSharding), + autoShardingConfig: none(AutoShardingConfig), + messageValidation: some( + MessageValidation( + maxMessageSizeBytes: 150'u64 * 1024'u64, # 150KB + rlnConfig: some( + RlnConfig( + contractAddress: "0x1234567890123456789012345678901234567890", + chainId: 1'u, + epochSizeSec: 600'u64, + ) + ), + ) + ), + ) + ), + storeConfirmation: false, + ethRpcEndpoints: @["http://127.0.0.1:1111"], + ) + + ## When + let wakuConf = toWakuConf(libConf).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 Relay mode configuration with all fields": + ## Given + let libConf = LibWakuConfig( + mode: Relay, + networkConfig: some( + libwaku_conf.NetworkConfig( + bootstrapNodes: + @[ + "enr:-QESuEC1p_s3xJzAC_XlOuuNrhVUETmfhbm1wxRGis0f7DlqGSw2FM-p2Vn7gmfkTTnAe8Ys2cgGBN8ufJnvzKQFZqFMBgmlkgnY0iXNlY3AyNTZrMaEDS8-D878DrdbNwcuY-3p1qdDp5MOoCurhdsNPJTXZ3c5g3RjcIJ2X4N1ZHCCd2g" + ], + staticStoreNodes: + @[ + "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" + ], + clusterId: 99, + shardingMode: some(ShardingMode.AutoSharding), + autoShardingConfig: some(AutoShardingConfig(numShardsInCluster: 8)), + messageValidation: some( + MessageValidation( + maxMessageSizeBytes: 512'u64 * 1024'u64, # 512KB + rlnConfig: some( + RlnConfig( + contractAddress: "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + chainId: 5'u, # Goerli + epochSizeSec: 300'u64, + ) + ), + ) + ), + ) + ), + storeConfirmation: true, + ethRpcEndpoints: @["https://127.0.0.1:8333"], + ) + + ## When + let wakuConfRes = toWakuConf(libConf) + + ## 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.clusterId == 99 + + # Check sharding + check: + wakuConf.shardingConf.kind == ShardingConfKind.AutoSharding + wakuConf.shardingConf.numShardsInCluster == 8 + + # 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 "Minimal configuration": + ## Given + let libConf = LibWakuConfig(mode: Relay, ethRpcEndpoints: @["http://someaddress"]) + + ## When + let wakuConfRes = toWakuConf(libConf) + + ## Then + let wakuConf = wakuConfRes.valueOr: + raiseAssert error + wakuConf.validate().isOkOr: + raiseAssert error + check: + wakuConf.clusterId == 1 + wakuConf.shardingConf.kind == ShardingConfKind.AutoSharding + wakuConf.shardingConf.numShardsInCluster == 8 + wakuConf.staticNodes.len == 0 diff --git a/waku.nim b/waku.nim new file mode 100644 index 0000000000..35fa56ed04 --- /dev/null +++ b/waku.nim @@ -0,0 +1,15 @@ +## 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. + +# Re-export the main types and functions +export libwaku_api, libwaku_conf + +# Import the modules to make them available +import library/libwaku_api +import library/libwaku_conf + +# Re-export essential types from waku factory +import waku/factory/waku +export Waku diff --git a/waku.nimble b/waku.nimble index 8014621f54..b9472bb264 100644 --- a/waku.nimble +++ b/waku.nimble @@ -27,8 +27,7 @@ requires "nim >= 2.2.4", "regex", "results", "db_connector", - "minilru", - "quic" + "minilru" ### Helper functions proc buildModule(filePath, params = "", lang = "c"): bool = diff --git a/waku/node/peer_manager/peer_manager.nim b/waku/node/peer_manager/peer_manager.nim index e4ac119db6..ff49ec77da 100644 --- a/waku/node/peer_manager/peer_manager.nim +++ b/waku/node/peer_manager/peer_manager.nim @@ -1,7 +1,7 @@ {.push raises: [].} import - std/[options, sets, sequtils, times, strformat, strutils, math, random], + std/[options, sets, sequtils, tables, times, strformat, strutils, math, random], chronos, chronicles, metrics,