diff --git a/.github/ISSUE_TEMPLATE/deploy_release.md b/.github/ISSUE_TEMPLATE/deploy_release.md deleted file mode 100644 index 0c584cc81..000000000 --- a/.github/ISSUE_TEMPLATE/deploy_release.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Deploy Release -about: Execute tasks for deploying a new version in a fleet -title: 'Deploy release vX.X.X in logos.test and/or status.prod fleet' -labels: deploy-release -assignees: '' - ---- - - - -### Link to the Release PR - - - -### Items to complete, in order - - - -- [ ] Receive sign-off from DST. - - [ ] Inform DST team about what are the expectations for this release. For example, if we expect higher, same or lower bandwidth consumption. Or a new protocol appears, etc. - - [ ] Ask DST to add a comment approving this deployment and add a link to the analysis report. - -- [ ] Deploy to logos.test - - [ ] Coordinate with Infra Team about possible changes in CI behavior - - [ ] Update logos.test with the corresponding deployment job in [Jenkins](https://ci.infra.status.im/) (ask the Infra Team if you don't have access or can't find the job). - -- [ ] Deploy to status.prod - - [ ] Coordinate with Infra Team about possible changes in CI behavior - - [ ] Ask Status admin to add a comment approving that this deployment to happen now. - - [ ] Update status.prod with [this deployment job](https://ci.infra.status.im/job/nim-waku/job/deploy-status-prod/). - -- [ ] Update infra config - - [ ] Submit PRs into infra repos to adjust deprecated or changed arguments (review CHANGELOG.md for that release). And confirm the fleet can run after that. This requires coordination with infra team. - -### Reference Links - -- [Release process](https://github.com/logos-messaging/logos-delivery/blob/master/docs/contributors/release-process.md) -- [Release notes](https://github.com/logos-messaging/logos-delivery/blob/master/CHANGELOG.md) -- [Infra-role-logos-node](https://github.com/status-im/infra-role-logos-node) -- [Infra-logos](https://github.com/status-im/infra-logos) -- [Infra-Status](https://github.com/status-im/infra-status) -- [Jenkins](https://ci.infra.status.im/) -- [Fleets](https://fleets.logos.co/) -- [Harbor](https://harbor.status.im/harbor/projects/32/repositories/logos-node/artifacts-tab) -- [Kibana](https://kibana.infra.status.im/app/) diff --git a/.github/ISSUE_TEMPLATE/prepare_release.md b/.github/ISSUE_TEMPLATE/prepare_release.md index 1ad9c95b3..86ce6a099 100644 --- a/.github/ISSUE_TEMPLATE/prepare_release.md +++ b/.github/ISSUE_TEMPLATE/prepare_release.md @@ -1,6 +1,6 @@ --- name: Prepare Release -about: Execute tasks for the creation and publishing of a new full release +about: Execute tasks for the creation, validation, deployment and publishing of a new full release title: 'Prepare release 0.0.0' labels: full-release assignees: '' @@ -8,64 +8,81 @@ assignees: '' --- -### Items to complete +### Items to complete, in order -All items below are to be completed by the owner of the given release. +#### Initial validation + - [ ] Confirm waku.test has been running with new commits on each merge into master (see Grafana boards.) + - [ ] Search [Kibana logs](https://kibana.infra.status.im/app/discover) since the last release for crashes or errors in waku.test that we didn't detect in CI. + - Most relevant search queries: `(fleet: "waku.test" AND message: "SIGSEGV")`, `(fleet: "waku.test" AND message: "exception")`, `(fleet: "waku.test" AND message: "error")`. + - Detect unexpected reboots. + - Document any crashes or errors found and fix them before proceeding. -- [ ] Create release branch with major and minor only ( e.g. release/v0.X ) if it doesn't exist. -- [ ] Update the `version` field in `logos_delivery.nimble` to match the release version (e.g. `version = "0.X.0"`) **and merge it before assigning any tag** - the `release-assets` workflow gates artifact build/upload. +### Create the release candidate + +- [ ] Create release branch with major and minor only ( e.g. release/v0.X ). +- [ ] In release branch, update the `version` field in `logos_delivery.nimble` to match the release version (e.g. `version = "0.X.0"`). - [ ] Assign release candidate tag to the release branch HEAD (e.g. `v0.X.0-rc.0`, `v0.X.0-rc.1`, ... `v0.X.0-rc.N`). -- [ ] Generate and edit release notes in CHANGELOG.md. -- [ ] **Validation of release candidate** +#### Validate the release candidate - - [ ] **Automated testing** - - [ ] Ensure all the unit tests (specifically logos-messaging-js tests) are green against the release candidate. + - [ ] 1. Essential test + - [ ] Ensure all unit tests are green + - [ ] Get interop tests results from QA - - [ ] **QA testing** - - [ ] Ask QA to run their available tests against the release candidate. - - - [ ] **Logos fleet testing** - - [ ] Deploy the release candidate to `logos.dev` fleet. + - [ ] 2. Fleet validation (pre-requisite: 1. Can be done in parallel with 3.) + - [ ] Deploy the release candidate to the waku.test fleet. - Start the deployment job in [Jenkins](https://ci.infra.status.im/) and wait for it to finish (Jenkins access required; ask the infra team if you don't have it). - - After completion, disable fleet so that daily CI does not override your release candidate. - - Verify at https://fleets.logos.co/ that the fleet is locked to the release candidate image. - Confirm the container image exists on [Harbor](https://harbor.status.im/harbor/projects/32/repositories/logos-node/artifacts-tab). - - [ ] Search [Kibana logs](https://kibana.infra.status.im/app/discover) from the previous month (since the last release was deployed) for possible crashes or errors in `logos.dev`. - - Set time range to "Last 30 days" (or since last release). - - Most relevant search query: `(fleet: "logos.dev" AND message: "SIGSEGV")`, `(fleet: "logos.dev" AND message: "exception")`, `(fleet: "logos.dev" AND message: "error")`. - - Document any crashes or errors found. - - [ ] Ask QA to perform tests against `logos.dev`, if any. Then, after that, review Kibana for possible issues or unexpected restart. - - [ ] Enable the `logos.dev` fleet again to resume auto-deployment of the latest `master` commit. + - After completion, disable the fleet so that daily CI does not override your release candidate. + - Verify at https://fleets.logos.co/ that the fleet is locked to the release candidate image. + + - [ ] Ask QA to run tests against waku.test and attach a screenshot as evidence. + - [ ] Re-enable the waku.test fleet to resume auto-deployment of the latest master commit. + - - [ ] **Status testing** - - [ ] Get QA approval to deploy a new version in `status.staging`. - - [ ] Deploy release candidate to `status.staging`. - - [ ] Perform [sanity check](https://www.notion.so/How-to-test-Nwaku-on-Status-12c6e4b9bf06420ca868bd199129b425) and log results as comments in this issue. - - [ ] Connect 2 instances to `status.staging` fleet, one in relay mode, the other one in light client mode. - - 1:1 Chats with each other. - - Send and receive messages in a community. - - Close one instance, send messages with second instance, reopen first instance and confirm messages sent while offline are retrieved from store. - - [ ] Perform checks based on _end user impact_ - - [ ] Inform other (Waku and Status) CCs to point their instances to `status.staging` for a few days. Ping Status colleagues on their Discord server or in the [Status community](https://status.app/c/G3kAAMSQtb05kog3aGbr3kiaxN4tF5xy4BAGEkkLwILk2z3GcoYlm5hSJXGn7J3laft-tnTwDWmYJ18dP_3bgX96dqr_8E3qKAvxDf3NrrCMUBp4R9EYkQez9XSM4486mXoC3mIln2zc-TNdvjdfL9eHVZ-mGgs=#zQ3shZeEJqTC1xhGUjxuS4rtHSrhJ8vUYp64v6qWkLpvdy9L9) (this is not a blocking point.) - - [ ] Ask QA to perform sanity checks (as described above) and checks based on _end user impact_; specify the version being tested - - [ ] Ask QA or infra to run the automated Status e2e tests against `status.staging` - - [ ] Get other CCs' sign-off: they should comment on this PR, e.g., "Used the app for a week, no problem." If problems are reported, resolve them and create a new RC. + - [ ] 3. DST sign-off (pre-requisite: 1) + - [ ] Inform the DST team about the expectations for this release. For example, if we expect higher, same or lower bandwidth consumption, or a new protocol appears, etc. + - [ ] Ask DST to add a comment approving this release and add a summary analysis report. -- [ ] **Proceed with release** + - [ ] 4. Status testing (pre-requisite: 1, 2, 3) + - [ ] Bump logos-delivery dependency in [logos-delivery-go-bindings](https://github.com/logos-messaging/logos-delivery-go-bindings) and make sure all tests work. + - [ ] Submit a PR on [status-go](https://github.com/status-im/status-go/blob/1f9061064587e1167e32d965d5a6f2b745324d5e/tests-functional/docker-compose.waku.yml#L3) bumping logos-delivery to this release candidate. + - [ ] Submit a PR on [status-app](https://github.com/status-im/status-app/blob/3639e28374ca3c2158ac2dac6af35dbce439b10a/docker-compose.waku.yml#L3) bumping logos-delivery to this release candidate. + - [ ] Both PRs must be merged before the release is considered created. + - [ ] Deploy the release candidate in status.staging. That may alert about needed infra changes. - - [ ] Assign a final release tag (`v0.X.0`) to the same commit that contains the validated release-candidate tag (e.g. `git tag -as v0.X.0 -m "final release."`). + - [ ] 5. Submit a PR against release/v0.X with CHANGELOG.md updates (pre-requisite: all previous.) + +#### Complete the release + - [ ] Assign a final release tag (`v0.X.0`) to the same commit that contains the latest release-candidate tag (e.g. `git tag -as v0.X.0 -m "final release."`). - [ ] Update [logos-delivery-compose](https://github.com/logos-messaging/logos-delivery-compose) and [logos-delivery-simulator](https://github.com/logos-messaging/logos-delivery-simulator) according to the new release. - - [ ] Bump logos-delivery dependency in [logos-delivery-rust-bindings](https://github.com/logos-messaging/logos-delivery-rust-bindings) and make sure all examples and tests work. - - [ ] Bump logos-delivery dependency in [logos-delivery-go-bindings](https://github.com/logos-messaging/logos-delivery-go-bindings) and make sure all tests work. - [ ] Create GitHub release (https://github.com/logos-messaging/logos-delivery/releases). - - [ ] Submit a PR to merge the release branch back to `master`. Make sure you use the option "Merge pull request (Create a merge commit)" to perform the merge. Ping repo admin if this option is not available. - - [ ] Create a deployment issue with the recently created release. + - [ ] Merge release branch into master + - [ ] Create a temporary branch from the release branch. This is needed in case we need to rebase that branch without impacting the release branch. Notice that the release branch should live forever. + - [ ] Submit a PR from temporary branch to master. Make sure you use the option "Merge pull request (Create a merge commit)" to perform the merge. Ping repo admin if this option is not available. + - [ ] Adjust the status-go and status-app `docker-compose.waku.yml` files (the ones bumped above) so they use the final tag instead of the release candidate one. + +#### Deployment + - [ ] Deploy to waku.sandbox + - [ ] Confirm the release candidate ran properly in waku.test with the current infra config. + - [ ] If it did NOT (e.g. CLI params changed): submit PRs to the infra repos adjusting the deprecated or changed arguments (review CHANGELOG.md for that release), add links to them, and wait until they are merged. Infra changes are deployed by the infra team, so this requires coordination with them. + - [ ] If it ran fine with the current config, no infra PR is needed. + - [ ] Deploy: + - [ ] Coordinate with the Infra Team about the deployment timing. + - [ ] Update waku.sandbox with [this deployment job](https://ci.infra.status.im/job/nim-waku/job/deploy-waku-sandbox/). + - [ ] Confirm the fleet runs properly after the deployment. + - [ ] Deploy to status.prod + - [ ] Confirm the release candidate ran properly in status.staging with the current infra config. + - [ ] If it did NOT (e.g. CLI params changed): submit PRs to the infra repos adjusting the deprecated or changed arguments (review CHANGELOG.md for that release), add links to them, and wait until they are merged. Infra changes are deployed by the infra team, so this requires coordination with them. + - [ ] If it ran fine with the current config, no infra PR is needed. + - [ ] Deploy: + - [ ] Ask the Status admin to add a comment approving that this deployment happen now. + - [ ] Update status.prod with [this deployment job](https://ci.infra.status.im/job/nim-waku/job/deploy-status-prod/). + - [ ] Confirm the fleet runs properly after the deployment. + ### Links @@ -73,6 +90,7 @@ All items below are to be completed by the owner of the given release. - [Release notes](https://github.com/logos-messaging/logos-delivery/blob/master/CHANGELOG.md) - [Fleet ownership](https://www.notion.so/Fleet-Ownership-7532aad8896d46599abac3c274189741?pvs=4#d2d2f0fe4b3c429fbd860a1d64f89a64) - [Infra-logos](https://github.com/status-im/infra-logos) +- [Infra-Status](https://github.com/status-im/infra-status) - [Jenkins](https://ci.infra.status.im/) - [Fleets](https://fleets.logos.co/) - [Harbor](https://harbor.status.im/harbor/projects/32/repositories/logos-node/artifacts-tab) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 278f37a05..af7f256fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,25 @@ jobs: touch nimbledeps/.nimble-setup - name: Build binaries - run: make V=1 all + # -j1: `all` builds wakunode2 and liblogosdelivery, each via `nimble + # `. Under -j2 the two nimble invocations re-resolve git deps + # concurrently and clobber each other in the shared ~/.nimble/pkgcache + # (e.g. "destination already exists", "could not lock config file"), + # failing the build. Serializing the targets removes the race; Nim's + # own --parallelBuild still parallelizes compilation. Mirrors the test + # job, which already forces -j1. + run: make V=1 -j1 all + + # Publish the freshly built shared library so the interop-tests workflow + # can run the wrapper suite against it without rebuilding. Only the Linux + # build is uploaded since the interop runners are ubuntu-latest. + - name: Upload liblogosdelivery.so + if: matrix.os == 'ubuntu-22.04' + uses: actions/upload-artifact@v4 + with: + name: liblogosdelivery + path: build/liblogosdelivery.so + retention-days: 1 build-windows: needs: changes @@ -118,7 +136,7 @@ jobs: matrix: os: [ubuntu-22.04, macos-15] runs-on: ${{ matrix.os }} - timeout-minutes: 45 + timeout-minutes: 60 name: test-${{ matrix.os }} steps: @@ -177,14 +195,24 @@ jobs: uses: ./.github/workflows/container-image.yml secrets: inherit + # Docker-based interop smoke tests. Gated only on the docker image so they + # still run on docker-only PRs (no liblogosdelivery build required). nwaku-nwaku-interop-tests: needs: build-docker-image - uses: logos-messaging/logos-delivery-interop-tests/.github/workflows/nim_waku_PR.yml@SMOKE_TEST_STABLE + uses: logos-messaging/logos-delivery-interop-tests/.github/workflows/nim_waku_PR.yml@SMOKE_TEST_2026.06.25 with: node_nwaku: ${{ needs.build-docker-image.outputs.image }} secrets: inherit + # Send API E2E tests run the wrapper suite against the liblogosdelivery.so + # built by the `build` job (downloaded as the `liblogosdelivery` artifact). + # Gated on `build`, so it is skipped on docker-only PRs where no lib is built. + send-api-e2e-tests: + needs: build + uses: logos-messaging/logos-delivery-interop-tests/.github/workflows/send_api_e2e_PR.yml@SMOKE_TEST_2026.06.25 + secrets: inherit + lint: name: "Lint" runs-on: ubuntu-22.04 diff --git a/README.md b/README.md index f227ea483..b94f5d491 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ private communications. - Examples. - Various tests of above. -For more details see the [source code](waku/README.md) +For more details see the [source code](logos_delivery/waku/README.md) ## How to Build & Run ( Linux, MacOS & WSL ) @@ -48,11 +48,14 @@ make wakunode2 NIMFLAGS="-d:chronicles_colors:none -d:disableMarchNative" # Run with DNS bootstrapping ./build/wakunode2 --dns-discovery --dns-discovery-url=DNS_BOOTSTRAP_NODE_URL +# Run with the QUIC transport enabled +./build/wakunode2 --quic-support=true + # See available command line options ./build/wakunode2 --help ``` To join the network, you need to know the address of at least one bootstrap node. -Please refer to the [Waku README](https://github.com/logos-messaging/logos-delivery/blob/master/waku/README.md) for more information. +Please refer to the [Waku README](https://github.com/logos-messaging/logos-delivery/blob/master/logos_delivery/waku/README.md) for more information. For more on how to run `wakunode2`, refer to: - [Run using binaries](https://docs.waku.org/run-node/build-source) diff --git a/apps/benchmarks/benchmarks.nim b/apps/benchmarks/benchmarks.nim index 705beeacf..eb1efe98d 100644 --- a/apps/benchmarks/benchmarks.nim +++ b/apps/benchmarks/benchmarks.nim @@ -3,11 +3,11 @@ import import logos_delivery/waku/[ - waku_rln_relay/protocol_types, - waku_rln_relay/rln, - waku_rln_relay, - waku_rln_relay/conversion_utils, - waku_rln_relay/group_manager/on_chain/group_manager, + rln/protocol_types, + rln/bindings, + rln, + rln/conversion_utils, + rln/group_manager/on_chain/group_manager, ], tests/waku_rln_relay/utils_onchain diff --git a/apps/chat2/chat2.nim b/apps/chat2/chat2.nim index 968d4582a..647667b0c 100644 --- a/apps/chat2/chat2.nim +++ b/apps/chat2/chat2.nim @@ -48,7 +48,7 @@ import ./config_chat2 import libp2p/protocols/pubsub/rpc/messages, libp2p/protocols/pubsub/pubsub -import ../../logos_delivery/waku/waku_rln_relay +import ../../logos_delivery/waku/rln const Help = """ Commands: /[?|help|connect|nick|exit] @@ -189,11 +189,11 @@ proc publish(c: Chat, line: string) = timestamp: getNanosecondTime(time), ) - if not isNil(c.node.wakuRlnRelay): + if not isNil(c.node.rln): # for future version when we support more than one rln protected content topic, # we should check the message content topic as well let proofRes = - waitFor c.node.wakuRlnRelay.generateRLNProof(message.toRLNSignal(), float64(time)) + waitFor c.node.rln.generateRLNProof(message.toRLNSignal(), float64(time)) if proofRes.isErr(): info "could not append rate limit proof to the message" else: @@ -204,13 +204,13 @@ proc publish(c: Chat, line: string) = return # TODO move it to log after dogfooding let msgEpoch = fromEpoch(proof.epoch) - if fromEpoch(c.node.wakuRlnRelay.lastEpoch) == msgEpoch: + if fromEpoch(c.node.rln.lastEpoch) == msgEpoch: echo "--rln epoch: ", msgEpoch, " ⚠️ message rate violation! you are spamming the network!" else: echo "--rln epoch: ", msgEpoch # update the last epoch - c.node.wakuRlnRelay.lastEpoch = proof.epoch + c.node.rln.lastEpoch = proof.epoch try: if not c.node.wakuLegacyLightPush.isNil(): @@ -521,7 +521,7 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} = topic = DefaultPubsubTopic, error = error if conf.rlnRelay: - info "WakuRLNRelay is enabled" + info "Rln is enabled" proc spamHandler(wakuMessage: WakuMessage) {.gcsafe, closure.} = info "spam handler is called" @@ -538,24 +538,22 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} = chainId: UInt256.fromBytesBE(conf.rlnRelayChainId.toBytesBE()), ethClientUrls: conf.ethClientUrls.mapIt(string(it)), creds: some( - RlnRelayCreds( - path: conf.rlnRelayCredPath, password: conf.rlnRelayCredPassword - ) + RlnCreds(path: conf.rlnRelayCredPath, password: conf.rlnRelayCredPassword) ), userMessageLimit: conf.rlnRelayUserMessageLimit, epochSizeSec: conf.rlnEpochSizeSec, ) - waitFor node.mountRlnRelay(rlnConf, spamHandler = some(spamHandler)) + waitFor node.setRlnValidator(rlnConf, spamHandler = some(spamHandler)) - let membershipIndex = node.wakuRlnRelay.groupManager.membershipIndex.get() - let identityCredential = node.wakuRlnRelay.groupManager.idCredentials.get() + let membershipIndex = node.rln.groupManager.membershipIndex.get() + let identityCredential = node.rln.groupManager.idCredentials.get() echo "your membership index is: ", membershipIndex echo "your rln identity commitment key is: ", identityCredential.idCommitment.inHex() else: - info "WakuRLNRelay is disabled" - echo "WakuRLNRelay is disabled, please enable it by passing in the --rln-relay flag" + info "Rln is disabled" + echo "Rln is disabled, please enable it by passing in the --rln-relay flag" if conf.metricsLogging: startMetricsLog() diff --git a/apps/chat2mix/chat2mix.nim b/apps/chat2mix/chat2mix.nim index 155f1e855..f5d864a8f 100644 --- a/apps/chat2mix/chat2mix.nim +++ b/apps/chat2mix/chat2mix.nim @@ -56,7 +56,7 @@ import ./config_chat2mix import libp2p/protocols/pubsub/rpc/messages, libp2p/protocols/pubsub/pubsub -import ../../logos_delivery/waku/waku_rln_relay +import ../../logos_delivery/waku/rln logScope: topics = "chat2 mix" diff --git a/apps/liteprotocoltester/liteprotocoltester.nim b/apps/liteprotocoltester/liteprotocoltester.nim index dd3097e49..124d09173 100644 --- a/apps/liteprotocoltester/liteprotocoltester.nim +++ b/apps/liteprotocoltester/liteprotocoltester.nim @@ -14,7 +14,7 @@ import logos_delivery/waku/[ common/enr, common/logging, - factory/waku as waku_factory, + waku as waku_factory, waku_node, node/waku_metrics, node/peer_manager, diff --git a/apps/networkmonitor/networkmonitor.nim b/apps/networkmonitor/networkmonitor.nim index 4c2d047ae..a23c778ef 100644 --- a/apps/networkmonitor/networkmonitor.nim +++ b/apps/networkmonitor/networkmonitor.nim @@ -25,7 +25,7 @@ import discovery/waku_discv5, discovery/waku_dnsdisc, waku_relay, - waku_rln_relay, + rln, factory/builder, factory/networks_config, ], @@ -614,12 +614,12 @@ when isMainModule: ethContractAddress: conf.rlnRelayEthContractAddress, ethClientUrls: conf.ethClientUrls.mapIt(string(it)), epochSizeSec: conf.rlnEpochSizeSec, - creds: none(RlnRelayCreds), + creds: none(RlnCreds), onFatalErrorAction: onFatalErrorAction, ) try: - waitFor node.mountRlnRelay(rlnConf) + waitFor node.setRlnValidator(rlnConf) except CatchableError: error "failed to setup RLN", error = getCurrentExceptionMsg() quit(QuitFailure) diff --git a/apps/wakunode2/wakunode2.nim b/apps/wakunode2/wakunode2.nim index e61b76e9c..a75b472ec 100644 --- a/apps/wakunode2/wakunode2.nim +++ b/apps/wakunode2/wakunode2.nim @@ -11,7 +11,7 @@ import ../../tools/[rln_keystore_generator/rln_keystore_generator, confutils/cli_args], logos_delivery/waku/[ common/logging, - factory/waku, + waku, node/health_monitor, rest_api/endpoint/builder as rest_server_builder, waku_core/message/default_values, diff --git a/config.nims b/config.nims index efff8a552..aaba3854e 100644 --- a/config.nims +++ b/config.nims @@ -63,6 +63,27 @@ else: switch("passC", "-mno-avx512f") switch("passL", "-mno-avx512f") +when defined(macosx): + # TEMPORARY (added 2026-06-19) — remove once nim-chronos is fixed/bumped upstream. + # + # macOS / Apple Clang 16+ promotes -Wincompatible-function-pointer-types to a + # DEFAULT error (not gated behind -Werror). It fires on a dependency↔dependency + # call in nim-chronos — none of our code is involved: + # chronos/streams/tlsstream.nim:718 ctx.setdest(itemAppend, ...) + # chronos declares the PEM callback `itemAppend(ctx, pbytes: pointer, nbytes)` + # while bearssl's `br_pem_decoder_setdest` expects `pbytes: const void*`. The + # callback only READS the buffer (copyMem from pbytes), so the missing `const` + # is benign — we demote the diagnostic back to a warning instead of failing. + # + # Why it appeared now (not a change on our side): the `const` was introduced in + # nim-bearssl v0.2.9, published 2026-06-19 11:22 UTC. CI does not honor the + # locked bearssl (nimble.lock pins 0.2.8) and fresh-resolves to latest, so any + # run after 11:22 UTC picks up 0.2.9 and hits this. Earlier-running PRs that + # resolved 0.2.8 are green only by timing; re-running them now reproduces it. + # + # Proper fix: chronos should declare `itemAppend`'s `pbytes` as `const pointer`. + switch("passC", "-Wno-error=incompatible-function-pointer-types") + --threads: on --opt: diff --git a/docs/operators/how-to/configure-quic.md b/docs/operators/how-to/configure-quic.md new file mode 100644 index 000000000..188433f8b --- /dev/null +++ b/docs/operators/how-to/configure-quic.md @@ -0,0 +1,20 @@ +# Configure QUIC transport + +QUIC is a UDP-based transport. Enabling it allows peers to connect to your node over QUIC, in addition to the default TCP transport. + +To enable QUIC, use the `--quic-support` option. +Note, the default port for QUIC is 60000. + +```shell +wakunode2 --quic-support=true +``` + +To listen on a different UDP port, use `--quic-port`: + +```shell +wakunode2 --quic-support=true --quic-port= +``` + +QUIC runs alongside the existing TCP transport. The node keeps listening on TCP and announces a `/udp//quic-v1` address in its ENR, so peers that support QUIC can connect over it while others continue to use TCP. + +If you restrict the node's announced addresses with `--ext-multiaddr-only`, the QUIC address is no longer announced automatically. In that case, include the QUIC multiaddr in `--ext-multiaddr` yourself, for example `/ip4//udp//quic-v1`. diff --git a/docs/operators/how-to/configure.md b/docs/operators/how-to/configure.md index f052b22be..b7ef2a57b 100644 --- a/docs/operators/how-to/configure.md +++ b/docs/operators/how-to/configure.md @@ -132,5 +132,6 @@ This is an index of tutorials explaining how to configure your nwaku node for di 4. [Configure store protocol and message store](./configure-store.md) 5. [Generate and configure a node key](./configure-key.md) 6. [Configure websocket transport](./configure-websocket.md) -7. [Run nwaku with rate limiting enabled](./run-with-rln.md) -8. [Configure a REST API node](./configure-rest-api.md) +7. [Configure QUIC transport](./configure-quic.md) +8. [Run nwaku with rate limiting enabled](./run-with-rln.md) +9. [Configure a REST API node](./configure-rest-api.md) diff --git a/docs/operators/how-to/run.md b/docs/operators/how-to/run.md index bc9039479..b6801f311 100644 --- a/docs/operators/how-to/run.md +++ b/docs/operators/how-to/run.md @@ -56,6 +56,12 @@ and websocket address /ip4/0.0.0.0/tcp/8000/ws/p2p/16Uiu2HAkzjwwgEAXfeGNMKFPSpc6vGBRqCdTLG5q3Gmk2v4pQw7H ``` +If your node is running with QUIC enabled, the log also includes a QUIC address + +``` +/ip4/0.0.0.0/udp/60000/quic-v1/p2p/16Uiu2HAkzjwwgEAXfeGNMKFPSpc6vGBRqCdTLG5q3Gmk2v4pQw7H +``` + You can also query a running node for its listening addresses using the REST API. diff --git a/examples/api_example/api_example.nim b/examples/api_example/api_example.nim index 9e96858db..8c8759191 100644 --- a/examples/api_example/api_example.nim +++ b/examples/api_example/api_example.nim @@ -7,7 +7,7 @@ type CliArgs = object defaultValue: "", desc: "ETH RPC Endpoint, if passed, RLN is enabled" .}: string -proc periodicSender(w: Waku): Future[void] {.async.} = +proc periodicSender(logos: LogosDelivery): Future[void] {.async.} = let sentListener = MessageSentEvent.listen( proc(event: MessageSentEvent) {.async: (raises: []).} = echo "Message sent with request ID: ", @@ -45,7 +45,7 @@ proc periodicSender(w: Waku): Future[void] {.async.} = payload = "Hello Waku! Message number: " & $counter, ) - let sendRequestId = (await w.send(envelope)).valueOr: + let sendRequestId = (await logos.messagingClient.send(envelope)).valueOr: echo "Failed to send message: ", error quit(QuitFailure) @@ -75,16 +75,12 @@ when isMainModule: conf.preset = "twn" conf.ethClientUrls = @[EthRpcUrl(args.ethRpcEndpoint)] - # Create the node using the library API's createNode function - let node = (waitFor createNode(conf)).valueOr: + # Create the full Logos Messaging stack (Waku + messaging + channels) + let node = (waitFor LogosDelivery.new(conf)).valueOr: echo "Failed to create node: ", error quit(QuitFailure) - echo("Waku node created successfully!") - - node.mountMessagingClient().isOkOr: - echo "Failed to mount messaging: ", error - quit(QuitFailure) + echo("Logos Messaging node created successfully!") # Start the node (waitFor node.start()).isOkOr: diff --git a/examples/cbindings/waku_example.c b/examples/cbindings/waku_example.c index 4b7d32247..614d54d69 100644 --- a/examples/cbindings/waku_example.c +++ b/examples/cbindings/waku_example.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -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 @@ -64,51 +58,35 @@ void *ctx; // For the case of C language we don't need to store a particular userData void *userData = NULL; -// Arguments parsing -static char doc[] = "\nC example that shows how to use the waku library."; -static char args_doc[] = ""; - -static struct argp_option options[] = { - {"host", 'h', "HOST", 0, "IP to listen for for LibP2P traffic. (default: \"0.0.0.0\")"}, - {"port", 'p', "PORT", 0, "TCP listening port. (default: \"60000\")"}, - {"key", 'k', "KEY", 0, "P2P node private key as 64 char hex string."}, - {"relay", 'r', "RELAY", 0, "Enable relay protocol: 1 or 0. (default: 1)"}, - {"peers", 'a', "PEERS", 0, "Comma-separated list of peer-multiaddress to connect\ - to. (default: \"\") e.g. \"/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN\""}, - {0}}; - -static error_t parse_opt(int key, char *arg, struct argp_state *state) +// Arguments parsing. Uses POSIX getopt so the example builds on glibc and on +// macOS/BSD alike (argp is a GNU libc extension not available everywhere). +static void parse_args(int argc, char **argv, struct ConfigNode *cfgNode) { - - struct ConfigNode *cfgNode = state->input; - switch (key) + int opt; + while ((opt = getopt(argc, argv, "h:p:k:r:a:")) != -1) { - case 'h': - snprintf(cfgNode->host, 128, "%s", arg); - break; - case 'p': - cfgNode->port = atoi(arg); - break; - case 'k': - snprintf(cfgNode->key, 128, "%s", arg); - break; - case 'r': - cfgNode->relay = atoi(arg); - break; - case 'a': - snprintf(cfgNode->peers, 2048, "%s", arg); - break; - case ARGP_KEY_ARG: - if (state->arg_num >= 1) /* Too many arguments. */ - argp_usage(state); - break; - case ARGP_KEY_END: - break; - default: - return ARGP_ERR_UNKNOWN; + switch (opt) + { + case 'h': + snprintf(cfgNode->host, 128, "%s", optarg); + break; + case 'p': + cfgNode->port = atoi(optarg); + break; + case 'k': + snprintf(cfgNode->key, 128, "%s", optarg); + break; + case 'r': + cfgNode->relay = atoi(optarg); + break; + case 'a': + snprintf(cfgNode->peers, 2048, "%s", optarg); + break; + default: + printf("Wrong parameters\n"); + exit(1); + } } - - return 0; } void signal_cond() @@ -119,8 +97,6 @@ void signal_cond() pthread_mutex_unlock(&mutex); } -static struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; - void event_handler(int callerRet, const char *msg, size_t len, void *userData) { if (callerRet == RET_ERR) @@ -308,47 +284,26 @@ 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; - if (argp_parse(&argp, argc, argv, 0, 0, &cfgNode) == ARGP_ERR_UNKNOWN) - { - show_help_and_exit(); - } + 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 = waku_new(jsonConfig, event_handler, userData); + ctx = logosdelivery_create_node(jsonConfig, event_handler, userData); waitForCallback(); WAKU_CALL(waku_default_pubsub_topic(ctx, print_default_pubsub_topic, userData)); @@ -359,7 +314,7 @@ int main(int argc, char **argv) logosdelivery_set_event_callback(ctx, on_event_received, userData); - waku_start(ctx, event_handler, userData); + logosdelivery_start_node(ctx, event_handler, userData); waitForCallback(); WAKU_CALL(waku_listen_addresses(ctx, event_handler, userData)); diff --git a/examples/cpp/waku.cpp b/examples/cpp/waku.cpp index b5a36cd49..d2808537f 100644 --- a/examples/cpp/waku.cpp +++ b/examples/cpp/waku.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -60,51 +60,35 @@ struct ConfigNode char peers[2048]; }; -// Arguments parsing -static char doc[] = "\nC example that shows how to use the waku library."; -static char args_doc[] = ""; - -static struct argp_option options[] = { - {"host", 'h', "HOST", 0, "IP to listen for for LibP2P traffic. (default: \"0.0.0.0\")"}, - {"port", 'p', "PORT", 0, "TCP listening port. (default: \"60000\")"}, - {"key", 'k', "KEY", 0, "P2P node private key as 64 char hex string."}, - {"relay", 'r', "RELAY", 0, "Enable relay protocol: 1 or 0. (default: 1)"}, - {"peers", 'a', "PEERS", 0, "Comma-separated list of peer-multiaddress to connect\ - to. (default: \"\") e.g. \"/ip4/127.0.0.1/tcp/60001/p2p/16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN\""}, - {0}}; - -static error_t parse_opt(int key, char *arg, struct argp_state *state) +// Arguments parsing. Uses POSIX getopt so the example builds on glibc and on +// macOS/BSD alike (argp is a GNU libc extension not available everywhere). +static void parse_args(int argc, char **argv, struct ConfigNode *cfgNode) { - - struct ConfigNode *cfgNode = (ConfigNode *)state->input; - switch (key) + int opt; + while ((opt = getopt(argc, argv, "h:p:k:r:a:")) != -1) { - case 'h': - snprintf(cfgNode->host, 128, "%s", arg); - break; - case 'p': - cfgNode->port = atoi(arg); - break; - case 'k': - snprintf(cfgNode->key, 128, "%s", arg); - break; - case 'r': - cfgNode->relay = atoi(arg); - break; - case 'a': - snprintf(cfgNode->peers, 2048, "%s", arg); - break; - case ARGP_KEY_ARG: - if (state->arg_num >= 1) /* Too many arguments. */ - argp_usage(state); - break; - case ARGP_KEY_END: - break; - default: - return ARGP_ERR_UNKNOWN; + switch (opt) + { + case 'h': + snprintf(cfgNode->host, 128, "%s", optarg); + break; + case 'p': + cfgNode->port = atoi(optarg); + break; + case 'k': + snprintf(cfgNode->key, 128, "%s", optarg); + break; + case 'r': + cfgNode->relay = atoi(optarg); + break; + case 'a': + snprintf(cfgNode->peers, 2048, "%s", optarg); + break; + default: + printf("Wrong parameters\n"); + exit(1); + } } - - return 0; } void event_handler(const char *msg, size_t len) @@ -129,8 +113,6 @@ auto cify(F &&f) }; } -static struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; - // Beginning of UI program logic enum PROGRAM_STATE @@ -254,33 +236,26 @@ int main(int argc, char **argv) cfgNode.port = 60000; cfgNode.relay = 1; - if (argp_parse(&argp, argc, argv, 0, 0, &cfgNode) == ARGP_ERR_UNKNOWN) - { - show_help_and_exit(); - } + parse_args(argc, argv, &cfgNode); 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); void *ctx = - waku_new(jsonConfig, + logosdelivery_create_node(jsonConfig, cify([](const char *msg, size_t len) - { std::cout << "waku_new feedback: " << msg << std::endl; }), + { std::cout << "logosdelivery_create_node feedback: " << msg << std::endl; }), nullptr); waitForCallback(); @@ -317,7 +292,7 @@ int main(int argc, char **argv) { event_handler(msg, len); }), nullptr); - WAKU_CALL(waku_start(ctx, + WAKU_CALL(logosdelivery_start_node(ctx, cify([&](const char *msg, size_t len) { event_handler(msg, len); }), nullptr)); diff --git a/examples/golang/waku.go b/examples/golang/waku.go index 968151475..1d09bfb5e 100644 --- a/examples/golang/waku.go +++ b/examples/golang/waku.go @@ -71,20 +71,20 @@ package main static void* cGoWakuNew(const char* configJson, void* resp) { // We pass NULL because we are not interested in retrieving data from this callback - void* ret = waku_new(configJson, (FFICallBack) callback, resp); + void* ret = logosdelivery_create_node(configJson, (FFICallBack) callback, resp); return ret; } static void cGoWakuStart(void* wakuCtx, void* resp) { - WAKU_CALL(waku_start(wakuCtx, (FFICallBack) callback, resp)); + WAKU_CALL(logosdelivery_start_node(wakuCtx, (FFICallBack) callback, resp)); } static void cGoWakuStop(void* wakuCtx, void* resp) { - WAKU_CALL(waku_stop(wakuCtx, (FFICallBack) callback, resp)); + WAKU_CALL(logosdelivery_stop_node(wakuCtx, (FFICallBack) callback, resp)); } static void cGoWakuDestroy(void* wakuCtx, void* resp) { - WAKU_CALL(waku_destroy(wakuCtx, (FFICallBack) callback, resp)); + WAKU_CALL(logosdelivery_destroy(wakuCtx, (FFICallBack) callback, resp)); } static void cGoWakuStartDiscV5(void* wakuCtx, void* resp) { @@ -293,12 +293,16 @@ type WakuMessageHash = string type WakuPubsubTopic = string type WakuContentTopic = string +type MessagingOverrides struct { + ListenAddress string `json:"listen-address,omitempty"` + Port int `json:"tcp-port,omitempty"` + NodeKey string `json:"nodekey,omitempty"` + LogLevel string `json:"log-level,omitempty"` +} + type WakuConfig struct { - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - NodeKey string `json:"key,omitempty"` - EnableRelay bool `json:"relay"` - LogLevel string `json:"logLevel"` + Mode string `json:"mode"` + MessagingOverrides MessagingOverrides `json:"messagingOverrides"` } type WakuNode struct { @@ -563,11 +567,13 @@ func (self *WakuNode) WakuGetMyENR() (string, error) { func main() { config := WakuConfig{ - Host: "0.0.0.0", - Port: 30304, - NodeKey: "11d0dcea28e86f81937a3bd1163473c7fbc0a0db54fd72914849bc47bdf78710", - EnableRelay: true, - LogLevel: "DEBUG", + Mode: "Core", + MessagingOverrides: MessagingOverrides{ + ListenAddress: "0.0.0.0", + Port: 30304, + NodeKey: "11d0dcea28e86f81937a3bd1163473c7fbc0a0db54fd72914849bc47bdf78710", + LogLevel: "DEBUG", + }, } node, err := WakuNew(config) diff --git a/examples/ios/WakuExample/WakuNode.swift b/examples/ios/WakuExample/WakuNode.swift index fa4e02a3b..611f50c50 100644 --- a/examples/ios/WakuExample/WakuNode.swift +++ b/examples/ios/WakuExample/WakuNode.swift @@ -240,11 +240,11 @@ actor WakuActor { print("[WakuActor] Unsubscribed from filter") // Stop - _ = await self.callWakuSync { waku_stop(ctxToStop, WakuActor.syncCallback, $0) } + _ = await self.callWakuSync { logosdelivery_stop_node(ctxToStop, WakuActor.syncCallback, $0) } print("[WakuActor] Node stopped") // Destroy - _ = await self.callWakuSync { waku_destroy(ctxToStop, WakuActor.syncCallback, $0) } + _ = await self.callWakuSync { logosdelivery_destroy(ctxToStop, WakuActor.syncCallback, $0) } print("[WakuActor] Node destroyed") } } @@ -300,32 +300,27 @@ 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 + } } """ - // Create node - waku_new is special, it returns the context directly + // Create node - logosdelivery_create_node is special, it returns the context directly let createResult = await withCheckedContinuation { (continuation: CheckedContinuation<(ctx: UnsafeMutableRawPointer?, success: Bool, result: String?), Never>) in let callbackCtx = CallbackContext() let userDataPtr = Unmanaged.passRetained(callbackCtx).toOpaque() - // Set up a simple callback for waku_new - let newCtx = waku_new(config, { ret, msg, len, userData in + // Set up a simple callback for logosdelivery_create_node + let newCtx = logosdelivery_create_node(config, { ret, msg, len, userData in guard let userData = userData else { return } let context = Unmanaged.fromOpaque(userData).takeUnretainedValue() context.success = (ret == RET_OK) @@ -354,7 +349,7 @@ actor WakuActor { // Start node let startResult = await callWakuSync { userData in - waku_start(self.ctx, WakuActor.syncCallback, userData) + logosdelivery_start_node(self.ctx, WakuActor.syncCallback, userData) } guard startResult.success else { diff --git a/examples/mobile/App.tsx b/examples/mobile/App.tsx index bf01de3a5..ff77fea5e 100644 --- a/examples/mobile/App.tsx +++ b/examples/mobile/App.tsx @@ -162,10 +162,13 @@ function App(): React.JSX.Element { const onClickNew = async () => { const config = { - host: '0.0.0.0', - port: 42342, - key: '1122334455667788990011223344556677889900112233445566778899000022', - relay: true, + mode: 'Core', + messagingOverrides: { + 'listen-address': '0.0.0.0', + 'tcp-port': 42342, + nodekey: + '1122334455667788990011223344556677889900112233445566778899000022', + }, }; waku = await WakuFactory.createInstance(config); }; diff --git a/examples/mobile/android/app/src/main/java/com/mobile/WakuModule.kt b/examples/mobile/android/app/src/main/java/com/mobile/WakuModule.kt index 4c1c0209e..aff8a9ca8 100644 --- a/examples/mobile/android/app/src/main/java/com/mobile/WakuModule.kt +++ b/examples/mobile/android/app/src/main/java/com/mobile/WakuModule.kt @@ -123,7 +123,7 @@ class WakuModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo val configStr = stringifyReadableMap(config) val response = wakuNew(configStr) if (response.error) { - promise.reject("waku_new", response.errorMessage) + promise.reject("logosdelivery_create_node", response.errorMessage) } else { // With this we just indicate to waku_ffi that we have registered a // closure, for this wakuPtr. Later once a message is received the @@ -140,7 +140,7 @@ class WakuModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo val wakuPtr = BigInteger(ctx).toLong() val response = wakuStart(wakuPtr) if (response.error) { - promise.reject("waku_start", response.message) + promise.reject("logosdelivery_start_node", response.message) } else { promise.resolve(null) } @@ -162,7 +162,7 @@ class WakuModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo val wakuPtr = BigInteger(ctx).toLong() val response = wakuStop(wakuPtr) if (response.error) { - promise.reject("waku_stop", response.message) + promise.reject("logosdelivery_stop_node", response.message) } else { promise.resolve(null) } @@ -173,7 +173,7 @@ class WakuModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo val wakuPtr = BigInteger(ctx).toLong() val response = wakuDestroy(wakuPtr) if (response.error) { - promise.reject("waku_destroy", response.message) + promise.reject("logosdelivery_destroy", response.message) } else { promise.resolve(null) } diff --git a/examples/mobile/android/app/src/main/jni/waku_ffi.c b/examples/mobile/android/app/src/main/jni/waku_ffi.c index 9dfeadaee..6a15ed6d9 100644 --- a/examples/mobile/android/app/src/main/jni/waku_ffi.c +++ b/examples/mobile/android/app/src/main/jni/waku_ffi.c @@ -184,7 +184,7 @@ jobject Java_com_mobile_WakuModule_wakuNew(JNIEnv *env, jobject thiz, jstring configJson) { const char *config = (*env)->GetStringUTFChars(env, configJson, 0); cb_result *result = NULL; - void *wakuPtr = waku_new(config, on_response, (void *)&result); + void *wakuPtr = logosdelivery_create_node(config, on_response, (void *)&result); jobject response = to_jni_ptr(env, result, wakuPtr); (*env)->ReleaseStringUTFChars(env, configJson, config); free_cb_result(result); @@ -194,7 +194,7 @@ jobject Java_com_mobile_WakuModule_wakuNew(JNIEnv *env, jobject thiz, jobject Java_com_mobile_WakuModule_wakuStart(JNIEnv *env, jobject thiz, jlong wakuPtr) { cb_result *result = NULL; - waku_start((void *)wakuPtr, on_response, &result); + logosdelivery_start_node((void *)wakuPtr, on_response, &result); jobject response = to_jni_result(env, result); free_cb_result(result); return response; @@ -212,7 +212,7 @@ jobject Java_com_mobile_WakuModule_wakuVersion(JNIEnv *env, jobject thiz, jobject Java_com_mobile_WakuModule_wakuStop(JNIEnv *env, jobject thiz, jlong wakuPtr) { cb_result *result = NULL; - waku_stop((void *)wakuPtr, on_response, &result); + logosdelivery_stop_node((void *)wakuPtr, on_response, &result); jobject response = to_jni_result(env, result); free_cb_result(result); return response; @@ -221,7 +221,7 @@ jobject Java_com_mobile_WakuModule_wakuStop(JNIEnv *env, jobject thiz, jobject Java_com_mobile_WakuModule_wakuDestroy(JNIEnv *env, jobject thiz, jlong wakuPtr) { cb_result *result = NULL; - waku_destroy((void *)wakuPtr, on_response, &result); + logosdelivery_destroy((void *)wakuPtr, on_response, &result); jobject response = to_jni_result(env, result); free_cb_result(result); return response; diff --git a/examples/nodejs/waku.js b/examples/nodejs/waku.js index 258e34849..13005dc62 100644 --- a/examples/nodejs/waku.js +++ b/examples/nodejs/waku.js @@ -17,11 +17,13 @@ function create_random_string(length) { var wakuMod = require('bindings')('waku'); var cfg = `{ - "host": "0.0.0.0", - "port": 60001, - "key": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9", - "relay": true - "logLevel": "DEBUG" + "mode": "Core", + "messagingOverrides": { + "listen-address": "0.0.0.0", + "tcp-port": 60001, + "nodekey": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9", + "log-level": "DEBUG" + } }` function event_handler(event) { diff --git a/examples/nodejs/waku_addon.c b/examples/nodejs/waku_addon.c index 2fa312573..3bf570abe 100644 --- a/examples/nodejs/waku_addon.c +++ b/examples/nodejs/waku_addon.c @@ -200,7 +200,7 @@ static napi_value WakuNew(napi_env env, napi_callback_info info) { str_size = str_size + 1; napi_get_value_string_utf8(env, args[0], jsonConfig, str_size, &str_size_read); - ctx = waku_new(jsonConfig, event_handler, userData); + ctx = logosdelivery_create_node(jsonConfig, event_handler, userData); free(jsonConfig); @@ -290,7 +290,7 @@ static napi_value WakuSetEventCallback(napi_env env, napi_callback_info info) { } static napi_value WakuStart(napi_env env, napi_callback_info info) { - waku_start(ctx, event_handler, userData); + logosdelivery_start_node(ctx, event_handler, userData); return NULL; } diff --git a/examples/python/waku.py b/examples/python/waku.py index d038e4351..b61d5ff36 100644 --- a/examples/python/waku.py +++ b/examples/python/waku.py @@ -53,31 +53,32 @@ parser.add_argument('--peer', dest='peer', default="", args = parser.parse_args() -# The next 'json_config' is the item passed to the 'waku_new'. +# 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) # Node creation -libwaku.waku_new.restype = ctypes.c_void_p -libwaku.waku_new.argtypes = [ctypes.c_char_p, +libwaku.logosdelivery_create_node.restype = ctypes.c_void_p +libwaku.logosdelivery_create_node.argtypes = [ctypes.c_char_p, callback_type, ctypes.c_void_p] -ctx = libwaku.waku_new(bytes(json_config, 'utf-8'), +ctx = libwaku.logosdelivery_create_node(bytes(json_config, 'utf-8'), callback_type( #onErrCb lambda ret, msg, len: - print("Error calling waku_new: %s", + print("Error calling logosdelivery_create_node: %s", msg.decode('utf-8')) ), ctypes.c_void_p(0)) @@ -115,12 +116,12 @@ libwaku.logosdelivery_set_event_callback.argtypes = [callback_type, ctypes.c_voi libwaku.logosdelivery_set_event_callback(callback, ctypes.c_void_p(0)) # Start the node -libwaku.waku_start.argtypes = [ctypes.c_void_p, +libwaku.logosdelivery_start_node.argtypes = [ctypes.c_void_p, callback_type, ctypes.c_void_p] -libwaku.waku_start(ctx, +libwaku.logosdelivery_start_node(ctx, callback_type(lambda ret, msg, len: - print("Error in waku_start: %s" % + print("Error in logosdelivery_start_node: %s" % msg.decode('utf-8'))), ctypes.c_void_p(0)) diff --git a/examples/qt/main_qt.cpp b/examples/qt/main_qt.cpp index f16660c17..db922f062 100644 --- a/examples/qt/main_qt.cpp +++ b/examples/qt/main_qt.cpp @@ -17,18 +17,16 @@ int main(int argc, char *argv[]) { QString jsonConfig = R"( { - "tcpPort": 60000, - "relay": true, - "logLevel": "TRACE", - "discv5Discovery": true, - "discv5BootstrapNodes": [ - "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", - "enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw" - ], - "discv5UdpPort": 9999, - "dnsDiscovery": true, - "dnsDiscoveryUrl": "enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im", - "dnsDiscoveryNameServers": ["8.8.8.8", "1.0.0.1"] + "mode": "Core", + "messagingOverrides": { + "tcp-port": 60000, + "log-level": "TRACE", + "entry-node": [ + "enr:-QESuEB4Dchgjn7gfAvwB00CxTA-nGiyk-aALI-H4dYSZD3rUk7bZHmP8d2U6xDiQ2vZffpo45Jp7zKNdnwDUx6g4o6XAYJpZIJ2NIJpcIRA4VDAim11bHRpYWRkcnO4XAArNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwAtNiZub2RlLTAxLmRvLWFtczMud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQOvD3S3jUNICsrOILlmhENiWAMmMVlAl6-Q8wRB7hidY4N0Y3CCdl-DdWRwgiMohXdha3UyDw", + "enr:-QEkuEBIkb8q8_mrorHndoXH9t5N6ZfD-jehQCrYeoJDPHqT0l0wyaONa2-piRQsi3oVKAzDShDVeoQhy0uwN1xbZfPZAYJpZIJ2NIJpcIQiQlleim11bHRpYWRkcnO4bgA0Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQZ2XwA2Ni9ub2RlLTAxLmdjLXVzLWNlbnRyYWwxLWEud2FrdS5zYW5kYm94LnN0YXR1cy5pbQYfQN4DgnJzkwABCAAAAAEAAgADAAQABQAGAAeJc2VjcDI1NmsxoQKnGt-GSgqPSf3IAPM7bFgTlpczpMZZLF3geeoNNsxzSoN0Y3CCdl-DdWRwgiMohXdha3UyDw" + ], + "discv5-udp-port": 9999 + } } )"; diff --git a/examples/qt/waku_handler.h b/examples/qt/waku_handler.h index baee785a3..ea44416d5 100644 --- a/examples/qt/waku_handler.h +++ b/examples/qt/waku_handler.h @@ -25,7 +25,7 @@ public: WakuHandler() : QObject(), ctx(nullptr) {} void initialize(const QString& jsonConfig, WakuCallBack event_handler, void* userData) { - ctx = waku_new(jsonConfig.toUtf8().constData(), WakuCallBack(event_handler), userData); + ctx = logosdelivery_create_node(jsonConfig.toUtf8().constData(), WakuCallBack(event_handler), userData); logosdelivery_set_event_callback(ctx, on_event_received, userData); qDebug() << "Waku context initialized, ready to start."; @@ -33,7 +33,7 @@ public: Q_INVOKABLE void start() { if (ctx) { - waku_start(ctx, event_handler, nullptr); + logosdelivery_start_node(ctx, event_handler, nullptr); qDebug() << "Waku start called with event_handler and userData."; } else { qDebug() << "Context is not initialized in start."; @@ -42,7 +42,7 @@ public: Q_INVOKABLE void stop() { if (ctx) { - waku_stop(ctx, event_handler, nullptr); + logosdelivery_stop_node(ctx, event_handler, nullptr); qDebug() << "Waku stop called with event_handler and userData."; } else { qDebug() << "Context is not initialized in stop."; diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index d26e9627e..bf142bd1b 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -6,7 +6,7 @@ use std::{slice, thread, time}; pub type FFICallBack = unsafe extern "C" fn(c_int, *const c_char, usize, *const c_void); extern "C" { - pub fn waku_new( + pub fn logosdelivery_create_node( config_json: *const u8, cb: FFICallBack, user_data: *const c_void, @@ -14,7 +14,7 @@ extern "C" { pub fn waku_version(ctx: *const c_void, cb: FFICallBack, user_data: *const c_void) -> c_int; - pub fn waku_start(ctx: *const c_void, cb: FFICallBack, user_data: *const c_void) -> c_int; + pub fn logosdelivery_start_node(ctx: *const c_void, cb: FFICallBack, user_data: *const c_void) -> c_int; pub fn waku_default_pubsub_topic( ctx: *mut c_void, @@ -50,21 +50,23 @@ 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 { // Create the waku node let closure = |ret: i32, data: &str| { - println!("Ret {ret}. waku_new closure called {data}"); + println!("Ret {ret}. logosdelivery_create_node closure called {data}"); }; let cb = get_trampoline(&closure); let config_json_str = CString::new(config_json).unwrap(); - let ctx = waku_new( + let ctx = logosdelivery_create_node( config_json_str.as_ptr() as *const u8, cb, &closure as *const _ as *const c_void, @@ -99,10 +101,10 @@ fn main() { // Start the Waku node let closure = |ret: i32, data: &str| { - println!("Ret {ret}. waku_start closure called {data}"); + println!("Ret {ret}. logosdelivery_start_node closure called {data}"); }; let cb = get_trampoline(&closure); - let _ret = waku_start(ctx, cb, &closure as *const _ as *const c_void); + let _ret = logosdelivery_start_node(ctx, cb, &closure as *const _ as *const c_void); } loop { diff --git a/examples/wakustealthcommitments/node_spec.nim b/examples/wakustealthcommitments/node_spec.nim index 3f4deeb08..e8e0a2062 100644 --- a/examples/wakustealthcommitments/node_spec.nim +++ b/examples/wakustealthcommitments/node_spec.nim @@ -1,7 +1,7 @@ {.push raises: [].} import tools/confutils/cli_args -import logos_delivery/waku/[common/logging, factory/[waku, networks_config]] +import logos_delivery/waku/[common/logging, waku, factory/networks_config] import std/[options, strutils, os, sequtils], chronicles, diff --git a/examples/wakustealthcommitments/stealth_commitment_protocol.nim b/examples/wakustealthcommitments/stealth_commitment_protocol.nim index f7b34e26a..097994126 100644 --- a/examples/wakustealthcommitments/stealth_commitment_protocol.nim +++ b/examples/wakustealthcommitments/stealth_commitment_protocol.nim @@ -2,7 +2,7 @@ import results, - logos_delivery/waku/[common/logging, waku_node, waku_rln_relay], + logos_delivery/waku/[common/logging, waku_node, rln], ./erc_5564_interface as StealthCommitmentFFI, ./node_spec, ./wire_spec @@ -47,9 +47,7 @@ proc sendThruWaku*( ) message.proof = ( - await self.waku.node.wakuRlnRelay.generateRLNProof( - message.toRLNSignal(), float64(time) - ) + await self.waku.node.rln.generateRLNProof(message.toRLNSignal(), float64(time)) ).valueOr: return err("could not append rate limit proof to the message: " & error) diff --git a/library/README.md b/library/README.md index e8352c611..53f174759 100644 --- a/library/README.md +++ b/library/README.md @@ -33,15 +33,31 @@ 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"`) 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: + +| Preset | Cluster ID | RLN | Sharding | Network | +| --- | --- | --- | --- | --- | +| `twn` | 1 | on | auto (8 shards) | The Waku Network | +| `logos.dev` | 2 | off | auto (8 shards) | Logos Dev Network | +| `logos.test` | 2 | off | auto (8 shards) | Logos Test Network | +| `status.prod` | 16 | off | auto (1 shard) | Status Production Network | #### `logosdelivery_start_node` Starts the node. diff --git a/library/channels_api/channel_api.nim b/library/channels_api/channel_api.nim new file mode 100644 index 000000000..c9b96312f --- /dev/null +++ b/library/channels_api/channel_api.nim @@ -0,0 +1,85 @@ +import std/json +import chronos, results, ffi +import + logos_delivery/waku/common/base64, + logos_delivery, + logos_delivery/waku/waku_core/topics/content_topic, + logos_delivery/api/types, + ../declare_lib + +proc logosdelivery_channel_create( + ctx: ptr FFIContext[LogosDelivery], + callback: FFICallBack, + userData: pointer, + channelIdStr: cstring, + contentTopicStr: cstring, + senderIdStr: cstring, +) {.ffi.} = + requireInitializedNode(ctx, "ChannelCreate"): + return err(errMsg) + + requireChannels(ctx, "ChannelCreate"): + return err(errMsg) + + let id = ctx.myLib[].reliableChannelManager.createReliableChannel( + ChannelId($channelIdStr), + ContentTopic($contentTopicStr), + SdsParticipantID($senderIdStr), + ).valueOr: + return err("ChannelCreate failed: " & $error) + + return ok(string(id)) + +proc logosdelivery_channel_send( + ctx: ptr FFIContext[LogosDelivery], + callback: FFICallBack, + userData: pointer, + channelIdStr: cstring, + messageJson: cstring, +) {.ffi.} = + ## `messageJson` carries `{ "payload": , "ephemeral": }`. + requireInitializedNode(ctx, "ChannelSend"): + return err(errMsg) + + requireChannels(ctx, "ChannelSend"): + return err(errMsg) + + var jsonNode: JsonNode + try: + jsonNode = parseJson($messageJson) + except Exception as e: + return err("Failed to parse channel message JSON: " & e.msg) + + if not jsonNode.hasKey("payload"): + return err("Missing payload field") + + let payload = base64.decode(Base64String(jsonNode["payload"].getStr())).valueOr: + return err("invalid payload format: " & error) + + let ephemeral = jsonNode.getOrDefault("ephemeral").getBool(false) + + let requestId = ( + await ctx.myLib[].reliableChannelManager.send( + ChannelId($channelIdStr), payload, ephemeral + ) + ).valueOr: + return err("ChannelSend failed: " & $error) + + return ok($requestId) + +proc logosdelivery_channel_close( + ctx: ptr FFIContext[LogosDelivery], + callback: FFICallBack, + userData: pointer, + channelIdStr: cstring, +) {.ffi.} = + requireInitializedNode(ctx, "ChannelClose"): + return err(errMsg) + + requireChannels(ctx, "ChannelClose"): + return err(errMsg) + + (await ctx.myLib[].reliableChannelManager.closeChannel(ChannelId($channelIdStr))).isOkOr: + return err("ChannelClose failed: " & $error) + + return ok("") diff --git a/library/declare_lib.nim b/library/declare_lib.nim index 77e992eea..869c6f71e 100644 --- a/library/declare_lib.nim +++ b/library/declare_lib.nim @@ -1,6 +1,7 @@ import ffi import std/locks -import logos_delivery/waku/factory/waku +import results +import logos_delivery declareLibrary("logosdelivery") @@ -8,7 +9,7 @@ var eventCallbackLock: Lock initLock(eventCallbackLock) template requireInitializedNode*( - ctx: ptr FFIContext[Waku], opName: string, onError: untyped + ctx: ptr FFIContext[LogosDelivery], opName: string, onError: untyped ) = if isNil(ctx): let errMsg {.inject.} = opName & " failed: invalid context" @@ -17,8 +18,26 @@ template requireInitializedNode*( let errMsg {.inject.} = opName & " failed: node is not initialized" onError +template requireMessaging*( + ctx: ptr FFIContext[LogosDelivery], opName: string, onError: untyped +) = + ## Use after `requireInitializedNode`. Fails if the node has no messaging client + ## (a kernel-only / fleet node). + ctx.myLib[].ensureMessaging().isOkOr: + let errMsg {.inject.} = opName & " failed: " & error + onError + +template requireChannels*( + ctx: ptr FFIContext[LogosDelivery], opName: string, onError: untyped +) = + ## Use after `requireInitializedNode`. Fails if the node has no reliable channel + ## manager (a kernel-only / fleet node). + ctx.myLib[].ensureChannels().isOkOr: + let errMsg {.inject.} = opName & " failed: " & error + onError + proc logosdelivery_set_event_callback( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.dynlib, exportc, cdecl.} = if isNil(ctx): echo "error: invalid context in logosdelivery_set_event_callback" diff --git a/library/events/json_connection_status_change_event.nim b/library/events/json_connection_status_change_event.nim index 9b8a0f9e6..f5af78f24 100644 --- a/library/events/json_connection_status_change_event.nim +++ b/library/events/json_connection_status_change_event.nim @@ -2,7 +2,7 @@ import system, std/json import ./json_base_event -import ../../logos_delivery/waku/api/types +import ../../logos_delivery/api/types type JsonConnectionStatusChangeEvent* = ref object of JsonEvent status*: ConnectionStatus diff --git a/library/examples/logosdelivery_example.c b/library/examples/logosdelivery_example.c index 729f7f0dc..bccdc7d25 100644 --- a/library/examples/logosdelivery_example.c +++ b/library/examples/logosdelivery_example.c @@ -129,12 +129,14 @@ void simple_callback(int ret, const char *msg, size_t len, void *userData) { int main() { printf("=== Logos Messaging API (LMAPI) Example ===\n\n"); - // Configuration JSON using WakuNodeConf field names (flat structure). - // Field names match Nim identifiers from WakuNodeConf in tools/confutils/cli_args.nim. + // Layered messaging config: {mode, preset, messagingOverrides, channelsOverrides}. + // Override keys are MessagingClientConf field or CLI switch names. const char *config = "{" - "\"logLevel\": \"INFO\"," "\"mode\": \"Core\"," - "\"preset\": \"logos.dev\"" + "\"preset\": \"logos.dev\"," + "\"messagingOverrides\": {" + "\"log-level\": \"INFO\"" + "}" "}"; printf("1. Creating node...\n"); diff --git a/library/kernel_api/debug_node_api.nim b/library/kernel_api/debug_node_api.nim index bbce84939..7d39935c6 100644 --- a/library/kernel_api/debug_node_api.nim +++ b/library/kernel_api/debug_node_api.nim @@ -1,53 +1,46 @@ -import std/json -import - chronicles, - chronos, - results, - eth/p2p/discoveryv5/enr, - strutils, - libp2p/peerid, - metrics, - ffi -import - logos_delivery/waku/factory/waku, - logos_delivery/waku/node/waku_node, - logos_delivery/waku/node/health_monitor, - library/declare_lib - -proc getMultiaddresses(node: WakuNode): seq[string] = - return node.info().listenAddresses - -proc getMetrics(): string = - {.gcsafe.}: - return defaultRegistry.toText() ## defaultRegistry is {.global.} in metrics module +import std/strutils +import chronos, results, ffi +import logos_delivery, library/declare_lib proc waku_version( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - return ok(WakuNodeVersionString) + let v = (await ctx.myLib[].waku.version()).valueOr: + return err(error) + return ok(v) proc waku_listen_addresses( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = ## returns a comma-separated string of the listen addresses - return ok(ctx.myLib[].node.getMultiaddresses().join(",")) + let addrs = (await ctx.myLib[].waku.listenAddresses()).valueOr: + return err(error) + return ok(addrs.join(",")) proc waku_get_my_enr( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - return ok(ctx.myLib[].node.enr.toURI()) + let enrUri = (await ctx.myLib[].waku.myEnr()).valueOr: + return err(error) + return ok(enrUri) proc waku_get_my_peerid( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - return ok($ctx.myLib[].node.peerId()) + let peerId = (await ctx.myLib[].waku.myPeerId()).valueOr: + return err(error) + return ok(peerId) proc waku_get_metrics( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - return ok(getMetrics()) + let m = (await ctx.myLib[].waku.metrics()).valueOr: + return err(error) + return ok(m) proc waku_is_online( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - return ok($ctx.myLib[].healthMonitor.onlineMonitor.amIOnline()) + let online = (await ctx.myLib[].waku.isOnline()).valueOr: + return err(error) + return ok($online) diff --git a/library/kernel_api/discovery_api.nim b/library/kernel_api/discovery_api.nim index 9dec4575a..98c83e42d 100644 --- a/library/kernel_api/discovery_api.nim +++ b/library/kernel_api/discovery_api.nim @@ -1,96 +1,59 @@ -import logos_delivery/waku/compat/option_valueor -import std/json -import chronos, chronicles, results, strutils, libp2p/multiaddress, ffi -import - logos_delivery/waku/factory/waku, - logos_delivery/waku/discovery/waku_dnsdisc, - logos_delivery/waku/discovery/waku_discv5, - logos_delivery/waku/waku_core/peers, - logos_delivery/waku/waku_node, - library/declare_lib - -proc retrieveBootstrapNodes( - enrTreeUrl: string, ipDnsServer: string -): Future[Result[seq[string], string]] {.async.} = - let dnsNameServers = @[parseIpAddress(ipDnsServer)] - let discoveredPeers: seq[RemotePeerInfo] = ( - await retrieveDynamicBootstrapNodes(enrTreeUrl, dnsNameServers) - ).valueOr: - return err("failed discovering peers from DNS: " & $error) - - var multiAddresses = newSeq[string]() - - for discPeer in discoveredPeers: - for address in discPeer.addrs: - multiAddresses.add($address & "/p2p/" & $discPeer) - - return ok(multiAddresses) - -proc updateDiscv5BootstrapNodes(nodes: string, waku: Waku): Result[void, string] = - waku.wakuDiscv5.updateBootstrapRecords(nodes).isOkOr: - return err("error in updateDiscv5BootstrapNodes: " & $error) - return ok() - -proc performPeerExchangeRequestTo*( - numPeers: uint64, waku: Waku -): Future[Result[int, string]] {.async.} = - let numPeersRecv = (await waku.node.fetchPeerExchangePeers(numPeers)).valueOr: - return err($error) - return ok(numPeersRecv) +import std/strutils +import chronos, chronicles, results, ffi +import logos_delivery, library/declare_lib proc waku_discv5_update_bootnodes( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, bootnodes: cstring, ) {.ffi.} = ## Updates the bootnode list used for discovering new peers via DiscoveryV5 ## bootnodes - JSON array containing the bootnode ENRs i.e. `["enr:...", "enr:..."]` - - updateDiscv5BootstrapNodes($bootnodes, ctx.myLib[]).isOkOr: + (await ctx.myLib[].waku.discv5UpdateBootnodes($bootnodes)).isOkOr: error "UPDATE_DISCV5_BOOTSTRAP_NODES failed", error = error - return err($error) - + return err(error) return ok("discovery request processed correctly") proc waku_dns_discovery( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, enrTreeUrl: cstring, nameDnsServer: cstring, timeoutMs: cint, ) {.ffi.} = - let nodes = (await retrieveBootstrapNodes($enrTreeUrl, $nameDnsServer)).valueOr: + let nodes = ( + await ctx.myLib[].waku.dnsDiscovery($enrTreeUrl, $nameDnsServer, int(timeoutMs)) + ).valueOr: error "GET_BOOTSTRAP_NODES failed", error = error - return err($error) - + return err(error) ## returns a comma-separated string of bootstrap nodes' multiaddresses return ok(nodes.join(",")) proc waku_start_discv5( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - (await ctx.myLib[].wakuDiscv5.start()).isOkOr: + (await ctx.myLib[].waku.startDiscv5()).isOkOr: error "START_DISCV5 failed", error = error - return err("error starting discv5: " & $error) - + return err(error) return ok("discv5 started correctly") proc waku_stop_discv5( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - await ctx.myLib[].wakuDiscv5.stop() + (await ctx.myLib[].waku.stopDiscv5()).isOkOr: + error "STOP_DISCV5 failed", error = error + return err(error) return ok("discv5 stopped correctly") proc waku_peer_exchange_request( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, numPeers: uint64, ) {.ffi.} = - let numValidPeers = (await performPeerExchangeRequestTo(numPeers, ctx.myLib[])).valueOr: + let numValidPeers = (await ctx.myLib[].waku.peerExchangeRequest(numPeers)).valueOr: error "waku_peer_exchange_request failed", error = error - return err("failed peer exchange: " & $error) - + return err(error) return ok($numValidPeers) diff --git a/library/kernel_api/node_lifecycle_api.nim b/library/kernel_api/node_lifecycle_api.nim deleted file mode 100644 index 8ee735f52..000000000 --- a/library/kernel_api/node_lifecycle_api.nim +++ /dev/null @@ -1,86 +0,0 @@ -import logos_delivery/waku/compat/option_valueor -import std/[options, json, strutils, net] -import chronos, chronicles, results, confutils, confutils/std/net, ffi - -import - logos_delivery/waku/node/peer_manager/peer_manager, - tools/confutils/cli_args, - logos_delivery/waku/factory/waku, - logos_delivery/waku/factory/node_factory, - logos_delivery/waku/factory/app_callbacks, - logos_delivery/waku/rest_api/endpoint/builder, - library/declare_lib - -proc createWaku( - configJson: cstring, appCallbacks: AppCallbacks = nil -): Future[Result[Waku, string]] {.async.} = - var conf = defaultWakuNodeConf().valueOr: - return err("Failed creating node: " & error) - - var errorResp: string - - var jsonNode: JsonNode - try: - jsonNode = parseJson($configJson) - except Exception: - return err( - "exception in createWaku when calling parseJson: " & getCurrentExceptionMsg() & - " configJson string: " & $configJson - ) - - for confField, confValue in fieldPairs(conf): - if jsonNode.contains(confField): - # Make sure string doesn't contain the leading or trailing " character - let formattedString = ($jsonNode[confField]).strip(chars = {'\"'}) - # Override conf field with the value set in the json-string - try: - confValue = parseCmdArg(typeof(confValue), formattedString) - except Exception: - return err( - "exception in createWaku when parsing configuration. exc: " & - getCurrentExceptionMsg() & ". string that could not be parsed: " & - formattedString & ". expected type: " & $typeof(confValue) - ) - - # Don't send relay app callbacks if relay is disabled - if not conf.relay and not appCallbacks.isNil(): - appCallbacks.relayHandler = nil - appCallbacks.topicHealthChangeHandler = nil - - # TODO: Convert `confJson` directly to `WakuConf` - var wakuConf = conf.toWakuConf().valueOr: - return err("Configuration error: " & $error) - - wakuConf.restServerConf = none(RestServerConf) ## don't want REST in libwaku - - let wakuRes = (await Waku.new(wakuConf, appCallbacks)).valueOr: - error "waku initialization failed", error = error - return err("Failed setting up Waku: " & $error) - - return ok(wakuRes) - -registerReqFFI(CreateNodeWithCallbacksRequest, ctx: ptr FFIContext[Waku]): - proc( - configJson: cstring, appCallbacks: AppCallbacks - ): Future[Result[string, string]] {.async.} = - ctx.myLib[] = (await createWaku(configJson, cast[AppCallbacks](appCallbacks))).valueOr: - error "CreateNodeWithCallbacksRequest failed", error = error - return err($error) - - return ok("") - -proc waku_start( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer -) {.ffi.} = - (await ctx.myLib[].start()).isOkOr: - error "START_NODE failed", error = error - return err("failed to start: " & $error) - return ok("") - -proc waku_stop( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer -) {.ffi.} = - (await ctx.myLib[].stop()).isOkOr: - error "STOP_NODE failed", error = error - return err("failed to stop: " & $error) - return ok("") diff --git a/library/kernel_api/peer_manager_api.nim b/library/kernel_api/peer_manager_api.nim index 775e58cae..e14b8b2c9 100644 --- a/library/kernel_api/peer_manager_api.nim +++ b/library/kernel_api/peer_manager_api.nim @@ -1,128 +1,104 @@ -import logos_delivery/waku/compat/option_valueor -import std/[sequtils, strutils, tables] -import chronicles, chronos, results, options, json, ffi -import - logos_delivery/waku/factory/waku, - logos_delivery/waku/node/waku_node, - logos_delivery/waku/node/peer_manager, - library/declare_lib +import std/[strutils, tables, json] +import chronicles, chronos, results, ffi +import logos_delivery, library/declare_lib type PeerInfo = object protocols: seq[string] addresses: seq[string] proc waku_get_peerids_from_peerstore( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = ## returns a comma-separated string of peerIDs - let peerIDs = - ctx.myLib[].node.peerManager.switch.peerStore.peers().mapIt($it.peerId).join(",") - return ok(peerIDs) + let peerIds = (await ctx.myLib[].waku.peerIdsFromPeerstore()).valueOr: + return err(error) + return ok(peerIds.join(",")) proc waku_connect( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, peerMultiAddr: cstring, timeoutMs: cuint, ) {.ffi.} = - let peers = ($peerMultiAddr).split(",").mapIt(strip(it)) - await ctx.myLib[].node.connectToNodes(peers, source = "static") + let peers = ($peerMultiAddr).split(",") + (await ctx.myLib[].waku.connect(peers, uint32(timeoutMs))).isOkOr: + return err(error) return ok("") proc waku_disconnect_peer_by_id( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer, peerId: cstring + ctx: ptr FFIContext[LogosDelivery], + callback: FFICallBack, + userData: pointer, + peerId: cstring, ) {.ffi.} = - let pId = PeerId.init($peerId).valueOr: - error "DISCONNECT_PEER_BY_ID failed", error = $error - return err($error) - await ctx.myLib[].node.peerManager.disconnectNode(pId) + (await ctx.myLib[].waku.disconnectPeerById($peerId)).isOkOr: + error "DISCONNECT_PEER_BY_ID failed", error = error + return err(error) return ok("") proc waku_disconnect_all_peers( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - await ctx.myLib[].node.peerManager.disconnectAllPeers() + (await ctx.myLib[].waku.disconnectAllPeers()).isOkOr: + return err(error) return ok("") proc waku_dial_peer( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, peerMultiAddr: cstring, protocol: cstring, timeoutMs: cuint, ) {.ffi.} = - let remotePeerInfo = parsePeerInfo($peerMultiAddr).valueOr: - error "DIAL_PEER failed", error = $error - return err($error) - let conn = await ctx.myLib[].node.peerManager.dialPeer(remotePeerInfo, $protocol) - if conn.isNone(): - let msg = "failed dialing peer" - error "DIAL_PEER failed", error = msg, peerId = $remotePeerInfo.peerId - return err(msg) + (await ctx.myLib[].waku.dialPeer($peerMultiAddr, $protocol, int(timeoutMs))).isOkOr: + error "DIAL_PEER failed", error = error + return err(error) return ok("") proc waku_dial_peer_by_id( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, peerId: cstring, protocol: cstring, timeoutMs: cuint, ) {.ffi.} = - let pId = PeerId.init($peerId).valueOr: - error "DIAL_PEER_BY_ID failed", error = $error - return err($error) - let conn = await ctx.myLib[].node.peerManager.dialPeer(pId, $protocol) - if conn.isNone(): - let msg = "failed dialing peer" - error "DIAL_PEER_BY_ID failed", error = msg, peerId = $peerId - return err(msg) - + (await ctx.myLib[].waku.dialPeerById($peerId, $protocol, int(timeoutMs))).isOkOr: + error "DIAL_PEER_BY_ID failed", error = error + return err(error) return ok("") proc waku_get_connected_peers_info( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = ## returns a JSON string mapping peerIDs to objects with protocols and addresses + let peers = (await ctx.myLib[].waku.connectedPeersInfo()).valueOr: + return err(error) var peersMap = initTable[string, PeerInfo]() - let peers = ctx.myLib[].node.peerManager.switch.peerStore.peers().filterIt( - it.connectedness == Connected - ) - - # Build a map of peer IDs to peer info objects for peer in peers: - let peerIdStr = $peer.peerId - peersMap[peerIdStr] = - PeerInfo(protocols: peer.protocols, addresses: peer.addrs.mapIt($it)) + peersMap[peer.peerId] = + PeerInfo(protocols: peer.protocols, addresses: peer.addresses) - # Convert the map to JSON string - let jsonObj = %*peersMap - let jsonStr = $jsonObj - return ok(jsonStr) + return ok($(%*peersMap)) proc waku_get_connected_peers( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = ## returns a comma-separated string of peerIDs - let - (inPeerIds, outPeerIds) = ctx.myLib[].node.peerManager.connectedPeers() - connectedPeerids = concat(inPeerIds, outPeerIds) - - return ok(connectedPeerids.mapIt($it).join(",")) + let peerIds = (await ctx.myLib[].waku.connectedPeers()).valueOr: + return err(error) + return ok(peerIds.join(",")) proc waku_get_peerids_by_protocol( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, protocol: cstring, ) {.ffi.} = ## returns a comma-separated string of peerIDs that mount the given protocol - let connectedPeers = ctx.myLib[].node.peerManager.switch.peerStore - .peers($protocol) - .filterIt(it.connectedness == Connected) - .mapIt($it.peerId) - .join(",") - return ok(connectedPeers) + let peerIds = (await ctx.myLib[].waku.peerIdsByProtocol($protocol)).valueOr: + return err(error) + return ok(peerIds.join(",")) diff --git a/library/kernel_api/ping_api.nim b/library/kernel_api/ping_api.nim index da830bc5d..6570fffd5 100644 --- a/library/kernel_api/ping_api.nim +++ b/library/kernel_api/ping_api.nim @@ -1,45 +1,13 @@ -import std/[json, strutils] import chronos, results, ffi -import libp2p/[protocols/ping, switch, multiaddress, multicodec] -import - logos_delivery/waku/[factory/waku, waku_core/peers, node/waku_node], - library/declare_lib +import logos_delivery, library/declare_lib proc waku_ping_peer( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, peerAddr: cstring, timeoutMs: cuint, ) {.ffi.} = - let peerInfo = peers.parsePeerInfo(($peerAddr).split(",")).valueOr: - return err("PingRequest failed to parse peer addr: " & $error) - - let timeout = chronos.milliseconds(timeoutMs) - proc ping(): Future[Result[Duration, string]] {.async, gcsafe.} = - try: - let conn = - await ctx.myLib[].node.switch.dial(peerInfo.peerId, peerInfo.addrs, PingCodec) - defer: - await conn.close() - - let pingRTT = await ctx.myLib[].node.libp2pPing.ping(conn) - if pingRTT == 0.nanos: - return err("could not ping peer: rtt-0") - return ok(pingRTT) - except CatchableError as exc: - return err("could not ping peer: " & exc.msg) - - let pingFuture = ping() - let pingRTT: Duration = - if timeout == chronos.milliseconds(0): # No timeout expected - (await pingFuture).valueOr: - return err("ping failed, no timeout expected: " & error) - else: - let timedOut = not (await pingFuture.withTimeout(timeout)) - if timedOut: - return err("ping timed out") - pingFuture.read().valueOr: - return err("failed to read ping future: " & error) - - return ok($(pingRTT.nanos)) + let rttNanos = (await ctx.myLib[].waku.pingPeer($peerAddr, int(timeoutMs))).valueOr: + return err(error) + return ok($rttNanos) diff --git a/library/kernel_api/protocols/filter_api.nim b/library/kernel_api/protocols/filter_api.nim index 5e2628953..cd613c1e0 100644 --- a/library/kernel_api/protocols/filter_api.nim +++ b/library/kernel_api/protocols/filter_api.nim @@ -1,109 +1,57 @@ -import logos_delivery/waku/compat/option_valueor -import options, std/[strutils, sequtils] +import std/[strutils, sequtils] import chronicles, chronos, results, ffi import - logos_delivery/waku/waku_filter_v2/client, + logos_delivery, logos_delivery/waku/waku_core/message/message, - logos_delivery/waku/factory/waku, - logos_delivery/waku/waku_relay, - logos_delivery/waku/waku_filter_v2/common, logos_delivery/waku/waku_core/subscription/push_handler, - logos_delivery/waku/node/peer_manager/peer_manager, - logos_delivery/waku/waku_node, logos_delivery/waku/waku_core/topics/pubsub_topic, logos_delivery/waku/waku_core/topics/content_topic, library/events/json_message_event, library/declare_lib -const FilterOpTimeout = 5.seconds - -proc checkFilterClientMounted(waku: Waku): Result[string, string] = - if waku.node.wakuFilterClient.isNil(): - let errorMsg = "wakuFilterClient is not mounted" - error "fail filter process", error = errorMsg - return err(errorMsg) - return ok("") - proc waku_filter_subscribe( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, contentTopics: cstring, ) {.ffi.} = - proc onReceivedMessage(ctx: ptr FFIContext): WakuRelayHandler = + proc onReceivedMessage(ctx: ptr FFIContext[LogosDelivery]): FilterPushHandler = return proc(pubsubTopic: PubsubTopic, msg: WakuMessage) {.async.} = callEventCallback(ctx, "onReceivedMessage"): $JsonMessageEvent.new(pubsubTopic, msg) - checkFilterClientMounted(ctx.myLib[]).isOkOr: - return err($error) - - var filterPushEventCallback = FilterPushHandler(onReceivedMessage(ctx)) - ctx.myLib[].node.wakuFilterClient.registerPushHandler(filterPushEventCallback) - - let peer = ctx.myLib[].node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr: - let errorMsg = "could not find peer with WakuFilterSubscribeCodec when subscribing" - error "fail filter subscribe", error = errorMsg - return err(errorMsg) - - let subFut = ctx.myLib[].node.filterSubscribe( - some(PubsubTopic($pubsubTopic)), - ($contentTopics).split(",").mapIt(ContentTopic(it)), - peer, - ) - if not await subFut.withTimeout(FilterOpTimeout): - let errorMsg = "filter subscription timed out" - error "fail filter unsubscribe", error = errorMsg - - return err(errorMsg) - + ( + await ctx.myLib[].waku.filterSubscribe( + PubsubTopic($pubSubTopic), + ($contentTopics).split(",").mapIt(ContentTopic(it)), + FilterPushHandler(onReceivedMessage(ctx)), + ) + ).isOkOr: + error "fail filter subscribe", error = error + return err(error) return ok("") proc waku_filter_unsubscribe( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, contentTopics: cstring, ) {.ffi.} = - checkFilterClientMounted(ctx.myLib[]).isOkOr: - return err($error) - - let peer = ctx.myLib[].node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr: - let errorMsg = - "could not find peer with WakuFilterSubscribeCodec when unsubscribing" - error "fail filter process", error = errorMsg - return err(errorMsg) - - let subFut = ctx.myLib[].node.filterUnsubscribe( - some(PubsubTopic($pubsubTopic)), - ($contentTopics).split(",").mapIt(ContentTopic(it)), - peer, - ) - if not await subFut.withTimeout(FilterOpTimeout): - let errorMsg = "filter un-subscription timed out" - error "fail filter unsubscribe", error = errorMsg - return err(errorMsg) + ( + await ctx.myLib[].waku.filterUnsubscribe( + PubsubTopic($pubSubTopic), ($contentTopics).split(",").mapIt(ContentTopic(it)) + ) + ).isOkOr: + error "fail filter unsubscribe", error = error + return err(error) return ok("") proc waku_filter_unsubscribe_all( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - checkFilterClientMounted(ctx.myLib[]).isOkOr: - return err($error) - - let peer = ctx.myLib[].node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr: - let errorMsg = - "could not find peer with WakuFilterSubscribeCodec when unsubscribing all" - error "fail filter unsubscribe all", error = errorMsg - return err(errorMsg) - - let unsubFut = ctx.myLib[].node.filterUnsubscribeAll(peer) - - if not await unsubFut.withTimeout(FilterOpTimeout): - let errorMsg = "filter un-subscription all timed out" - error "fail filter unsubscribe all", error = errorMsg - - return err(errorMsg) + (await ctx.myLib[].waku.filterUnsubscribeAll()).isOkOr: + error "fail filter unsubscribe all", error = error + return err(error) return ok("") diff --git a/library/kernel_api/protocols/lightpush_api.nim b/library/kernel_api/protocols/lightpush_api.nim index dc9197481..c7bc8169d 100644 --- a/library/kernel_api/protocols/lightpush_api.nim +++ b/library/kernel_api/protocols/lightpush_api.nim @@ -1,29 +1,19 @@ -import logos_delivery/waku/compat/option_valueor -import options, std/[json, strformat] +import std/[json, strformat] import chronicles, chronos, results, ffi import - logos_delivery/waku/waku_core/message/message, - logos_delivery/waku/waku_core/codecs, - logos_delivery/waku/factory/waku, + logos_delivery, logos_delivery/waku/waku_core/message, logos_delivery/waku/waku_core/topics/pubsub_topic, - logos_delivery/waku/waku_lightpush_legacy/client, - logos_delivery/waku/node/peer_manager/peer_manager, library/events/json_message_event, library/declare_lib proc waku_lightpush_publish( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, jsonWakuMessage: cstring, ) {.ffi.} = - if ctx.myLib[].node.wakuLightpushClient.isNil(): - let errorMsg = "LightpushRequest waku.node.wakuLightpushClient is nil" - error "PUBLISH failed", error = errorMsg - return err(errorMsg) - var jsonMessage: JsonMessage try: let jsonContent = parseJson($jsonWakuMessage) @@ -35,18 +25,10 @@ proc waku_lightpush_publish( let msg = json_message_event.toWakuMessage(jsonMessage).valueOr: return err("Problem building the WakuMessage: " & $error) - let peerOpt = ctx.myLib[].node.peerManager.selectPeer(WakuLightPushCodec) - if peerOpt.isNone(): - let errorMsg = "failed to lightpublish message, no suitable remote peers" - error "PUBLISH failed", error = errorMsg - return err(errorMsg) - let msgHashHex = ( - await ctx.myLib[].node.wakuLegacyLightpushClient.publish( - $pubsubTopic, msg, peer = peerOpt.get() - ) + await ctx.myLib[].waku.lightpushPublish(PubsubTopic($pubSubTopic), msg) ).valueOr: error "PUBLISH failed", error = error - return err($error) + return err(error) return ok(msgHashHex) diff --git a/library/kernel_api/protocols/relay_api.nim b/library/kernel_api/protocols/relay_api.nim index ae7e29ceb..7a1fe446f 100644 --- a/library/kernel_api/protocols/relay_api.nim +++ b/library/kernel_api/protocols/relay_api.nim @@ -1,68 +1,61 @@ -import logos_delivery/waku/compat/option_valueor -import std/[net, sequtils, strutils, json], strformat -import chronicles, chronos, stew/byteutils, results, ffi +import std/[strutils, json] +import chronicles, chronos, results, ffi import - logos_delivery/waku/waku_core/message/message, - logos_delivery/waku/factory/[validator_signed, waku], - tools/confutils/cli_args, - logos_delivery/waku/waku_core/message, + logos_delivery, logos_delivery/waku/waku_core/topics/pubsub_topic, - logos_delivery/waku/waku_core/topics, - logos_delivery/waku/node/waku_node/relay, + logos_delivery/waku/waku_core/message, logos_delivery/waku/waku_relay/protocol, - logos_delivery/waku/node/peer_manager, library/events/json_message_event, library/declare_lib proc waku_relay_get_peers_in_mesh( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, ) {.ffi.} = - let meshPeers = ctx.myLib[].node.wakuRelay.getPeersInMesh($pubsubTopic).valueOr: + let peers = (await ctx.myLib[].waku.relayPeersInMesh(PubsubTopic($pubSubTopic))).valueOr: error "LIST_MESH_PEERS failed", error = error - return err($error) + return err(error) ## returns a comma-separated string of peerIDs - return ok(meshPeers.mapIt($it).join(",")) + return ok(peers.join(",")) proc waku_relay_get_num_peers_in_mesh( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, ) {.ffi.} = - let numPeersInMesh = ctx.myLib[].node.wakuRelay.getNumPeersInMesh($pubsubTopic).valueOr: + let n = (await ctx.myLib[].waku.relayNumPeersInMesh(PubsubTopic($pubSubTopic))).valueOr: error "NUM_MESH_PEERS failed", error = error - return err($error) - return ok($numPeersInMesh) + return err(error) + return ok($n) proc waku_relay_get_connected_peers( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, ) {.ffi.} = ## Returns the list of all connected peers to an specific pubsub topic - let connPeers = ctx.myLib[].node.wakuRelay.getConnectedPeers($pubsubTopic).valueOr: + let peers = (await ctx.myLib[].waku.relayConnectedPeers(PubsubTopic($pubSubTopic))).valueOr: error "LIST_CONNECTED_PEERS failed", error = error - return err($error) - ## returns a comma-separated string of peerIDs - return ok(connPeers.mapIt($it).join(",")) + return err(error) + return ok(peers.join(",")) proc waku_relay_get_num_connected_peers( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, ) {.ffi.} = - let numConnPeers = ctx.myLib[].node.wakuRelay.getNumConnectedPeers($pubsubTopic).valueOr: + let n = (await ctx.myLib[].waku.relayNumConnectedPeers(PubsubTopic($pubSubTopic))).valueOr: error "NUM_CONNECTED_PEERS failed", error = error - return err($error) - return ok($numConnPeers) + return err(error) + return ok($n) proc waku_relay_add_protected_shard( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, clusterId: cint, @@ -70,87 +63,82 @@ proc waku_relay_add_protected_shard( publicKey: cstring, ) {.ffi.} = ## Protects a shard with a public key - try: - let relayShard = RelayShard(clusterId: uint16(clusterId), shardId: uint16(shardId)) - let protectedShard = ProtectedShard.parseCmdArg($relayShard & ":" & $publicKey) - ctx.myLib[].node.wakuRelay.addSignedShardsValidator( - @[protectedShard], uint16(clusterId) + ( + await ctx.myLib[].waku.relayAddProtectedShard( + uint16(clusterId), uint16(shardId), $publicKey ) - except ValueError as exc: - return err("ERROR in waku_relay_add_protected_shard: " & exc.msg) - + ).isOkOr: + return err(error) return ok("") proc waku_relay_subscribe( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, ) {.ffi.} = - echo "Subscribing to topic: " & $pubSubTopic & " ..." - proc onReceivedMessage(ctx: ptr FFIContext[Waku]): WakuRelayHandler = + proc onReceivedMessage(ctx: ptr FFIContext[LogosDelivery]): WakuRelayHandler = return proc(pubsubTopic: PubsubTopic, msg: WakuMessage) {.async.} = callEventCallback(ctx, "onReceivedMessage"): $JsonMessageEvent.new(pubsubTopic, msg) - var cb = onReceivedMessage(ctx) - - ctx.myLib[].node.subscribe( - (kind: SubscriptionKind.PubsubSub, topic: $pubsubTopic), - handler = WakuRelayHandler(cb), + ( + await ctx.myLib[].waku.relaySubscribe( + PubsubTopic($pubSubTopic), WakuRelayHandler(onReceivedMessage(ctx)) + ) ).isOkOr: error "SUBSCRIBE failed", error = error - return err($error) + return err(error) return ok("") proc waku_relay_unsubscribe( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, ) {.ffi.} = - ctx.myLib[].node.unsubscribe((kind: SubscriptionKind.PubsubSub, topic: $pubsubTopic)).isOkOr: + (await ctx.myLib[].waku.relayUnsubscribe(PubsubTopic($pubSubTopic))).isOkOr: error "UNSUBSCRIBE failed", error = error - return err($error) - + return err(error) return ok("") proc waku_relay_publish( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, pubSubTopic: cstring, jsonWakuMessage: cstring, timeoutMs: cuint, ) {.ffi.} = - var - # https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms - jsonMessage: JsonMessage + var jsonMessage: JsonMessage try: let jsonContent = parseJson($jsonWakuMessage) jsonMessage = JsonMessage.fromJsonNode(jsonContent).valueOr: raise newException(JsonParsingError, $error) except JsonParsingError as exc: - return err(fmt"Error parsing json message: {exc.msg}") + return err("Error parsing json message: " & exc.msg) let msg = json_message_event.toWakuMessage(jsonMessage).valueOr: return err("Problem building the WakuMessage: " & $error) - (await ctx.myLib[].node.wakuRelay.publish($pubsubTopic, msg)).isOkOr: + let msgHash = ( + await ctx.myLib[].waku.relayPublish( + PubsubTopic($pubSubTopic), msg, uint32(timeoutMs) + ) + ).valueOr: error "PUBLISH failed", error = error - return err($error) - - let msgHash = computeMessageHash($pubSubTopic, msg).to0xHex + return err(error) return ok(msgHash) proc waku_default_pubsub_topic( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = - # https://rfc.vac.dev/spec/36/#extern-char-waku_default_pubsub_topic - return ok(DefaultPubsubTopic) + let topic = (await ctx.myLib[].waku.defaultPubsubTopic()).valueOr: + return err(error) + return ok(string(topic)) proc waku_content_topic( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, appName: cstring, @@ -158,15 +146,20 @@ proc waku_content_topic( contentTopicName: cstring, encoding: cstring, ) {.ffi.} = - # https://rfc.vac.dev/spec/36/#extern-char-waku_content_topicchar-applicationname-unsigned-int-applicationversion-char-contenttopicname-char-encoding - - return ok(fmt"/{$appName}/{$appVersion}/{$contentTopicName}/{$encoding}") + let topic = ( + await ctx.myLib[].waku.buildContentTopic( + $appName, uint32(appVersion), $contentTopicName, $encoding + ) + ).valueOr: + return err(error) + return ok(string(topic)) proc waku_pubsub_topic( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, topicName: cstring, ) {.ffi.} = - # https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding - return ok(fmt"/waku/2/{$topicName}") + let topic = (await ctx.myLib[].waku.buildPubsubTopic($topicName)).valueOr: + return err(error) + return ok(string(topic)) diff --git a/library/kernel_api/protocols/store_api.nim b/library/kernel_api/protocols/store_api.nim index 22d514d14..75d43fee1 100644 --- a/library/kernel_api/protocols/store_api.nim +++ b/library/kernel_api/protocols/store_api.nim @@ -1,13 +1,10 @@ -import logos_delivery/waku/compat/option_valueor -import std/[json, sugar, strutils, options] -import chronos, chronicles, results, stew/byteutils, ffi +import std/[json, sugar, options] +import chronos, chronicles, results, ffi import - logos_delivery/waku/factory/waku, + logos_delivery, library/utils, - logos_delivery/waku/waku_core/peers, logos_delivery/waku/waku_core/message/digest, logos_delivery/waku/waku_store/common, - logos_delivery/waku/waku_store/client, logos_delivery/waku/common/paging, library/declare_lib @@ -68,7 +65,7 @@ func fromJsonNode(jsonContent: JsonNode): Result[StoreQueryRequest, string] = ) proc waku_store_query( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, jsonQuery: cstring, @@ -83,13 +80,10 @@ proc waku_store_query( let storeQueryRequest = ?fromJsonNode(jsonContentRes.get()) - let peer = peers.parsePeerInfo(($peerAddr).split(",")).valueOr: - return err("StoreRequest failed to parse peer addr: " & $error) - let queryResponse = ( - await ctx.myLib[].node.wakuStoreClient.query(storeQueryRequest, peer) + await ctx.myLib[].waku.storeQuery(storeQueryRequest, $peerAddr, int(timeoutMs)) ).valueOr: - return err("StoreRequest failed store query: " & $error) + return err("StoreRequest failed store query: " & error) let res = $(%*(queryResponse.toHex())) return ok(res) ## returning the response in json format diff --git a/library/liblogosdelivery.h b/library/liblogosdelivery.h index 3965ef013..21d8890d2 100644 --- a/library/liblogosdelivery.h +++ b/library/liblogosdelivery.h @@ -22,9 +22,14 @@ extern "C" // Creates a new instance of the node from the given configuration JSON. // Returns a pointer to the Context needed by the rest of the API functions. - // Configuration should be in JSON format using WakuNodeConf field names. - // Field names match Nim identifiers from WakuNodeConf (camelCase). - // Example: {"mode": "Core", "clusterId": 42, "relay": true} + // The configuration is a JSON object with these optional keys: + // "mode": "Core" | "Edge" (messaging role; defaults to "Core") + // "preset": "" (e.g. "twn") + // "messagingOverrides": { ... } (per-field messaging config overrides) + // "channelsOverrides": { ... } (per-field reliable-channel overrides) + // Override keys accept the config field name or its CLI switch name (e.g. + // "clusterId" or "cluster-id"). Unknown keys are rejected. + // Example: {"mode":"Core","messagingOverrides":{"cluster-id":42,"log-level":"INFO"}} void *logosdelivery_create_node( const char *configJson, FFICallBack callback, @@ -71,6 +76,36 @@ extern "C" void *userData, const char *messageJson); + // --- Reliable Channels API (stable surface) --- + + // Create a reliable channel. Returns the channel id. + int logosdelivery_channel_create(void *ctx, + FFICallBack callback, + void *userData, + const char *channelId, + const char *contentTopic, + const char *senderId); + + // Send a message on a reliable channel. + // messageJson: { "payload": "base64-encoded-payload", "ephemeral": false } + // Returns a request ID that can be used to track delivery. + int logosdelivery_channel_send(void *ctx, + FFICallBack callback, + void *userData, + const char *channelId, + const char *messageJson); + + // Close a reliable channel: stops its SDS loops; persisted state survives, so + // re-creating the channel restores it. + int logosdelivery_channel_close(void *ctx, + FFICallBack callback, + void *userData, + const char *channelId); + + // Channel lifecycle events are delivered through the event callback set via + // logosdelivery_set_event_callback: "onChannelMessageReceived" (payload + // base64-encoded), "onChannelMessageSent", "onChannelMessageError". + // Sets a callback that will be invoked whenever an event occurs. // It is crucial that the passed callback is fast, non-blocking and potentially thread-safe. void logosdelivery_set_event_callback(void *ctx, diff --git a/library/liblogosdelivery.nim b/library/liblogosdelivery.nim index 08541634a..d3753da63 100644 --- a/library/liblogosdelivery.nim +++ b/library/liblogosdelivery.nim @@ -5,14 +5,11 @@ import logos_delivery/waku/waku_core/message/message, logos_delivery/waku/waku_core/topics/pubsub_topic, logos_delivery/waku/waku_relay, - logos_delivery/waku/factory/waku, + logos_delivery, + logos_delivery/waku/waku, logos_delivery/waku/node/waku_node, logos_delivery/waku/node/health_monitor/health_status, - ../logos_delivery/waku/factory/app_callbacks, ./events/json_message_event, - ./events/json_topic_health_change_event, - ./events/json_connection_change_event, - ./events/json_connection_status_change_event, ./declare_lib ################################################################################ @@ -24,89 +21,15 @@ include ./logos_delivery_api/debug_api, ./kernel_api/peer_manager_api, ./kernel_api/discovery_api, - ./kernel_api/node_lifecycle_api, ./kernel_api/debug_node_api, ./kernel_api/ping_api, ./kernel_api/protocols/relay_api, ./kernel_api/protocols/store_api, ./kernel_api/protocols/lightpush_api, - ./kernel_api/protocols/filter_api + ./kernel_api/protocols/filter_api, + ./channels_api/channel_api -################################################################################ -### Exported procs (former libwaku API) - -proc waku_new( - configJson: cstring, callback: FFICallback, userData: pointer -): pointer {.dynlib, exportc, cdecl.} = - initializeLibrary() - - ## Creates a new instance of the WakuNode. - if isNil(callback): - echo "error: missing callback in waku_new" - return nil - - ## Create the Waku thread that will keep waiting for req from the main thread. - var ctx = ffi.createFFIContext[Waku]().valueOr: - let msg = "Error in createFFIContext: " & $error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return nil - - ctx.userData = userData - - proc onReceivedMessage(ctx: ptr FFIContext): WakuRelayHandler = - return proc(pubsubTopic: PubsubTopic, msg: WakuMessage) {.async.} = - callEventCallback(ctx, "onReceivedMessage"): - $JsonMessageEvent.new(pubsubTopic, msg) - - proc onTopicHealthChange(ctx: ptr FFIContext): TopicHealthChangeHandler = - return proc(pubsubTopic: PubsubTopic, topicHealth: TopicHealth) {.async.} = - callEventCallback(ctx, "onTopicHealthChange"): - $JsonTopicHealthChangeEvent.new(pubsubTopic, topicHealth) - - proc onConnectionChange(ctx: ptr FFIContext): ConnectionChangeHandler = - return proc(peerId: PeerId, peerEvent: PeerEventKind) {.async.} = - callEventCallback(ctx, "onConnectionChange"): - $JsonConnectionChangeEvent.new($peerId, peerEvent) - - proc onConnectionStatusChange(ctx: ptr FFIContext): ConnectionStatusChangeHandler = - return proc(status: ConnectionStatus) {.async.} = - callEventCallback(ctx, "onConnectionStatusChange"): - $JsonConnectionStatusChangeEvent.new(status) - - let appCallbacks = AppCallbacks( - relayHandler: onReceivedMessage(ctx), - topicHealthChangeHandler: onTopicHealthChange(ctx), - connectionChangeHandler: onConnectionChange(ctx), - connectionStatusChangeHandler: onConnectionStatusChange(ctx), - ) - - ffi.sendRequestToFFIThread( - ctx, - CreateNodeWithCallbacksRequest.ffiNewReq( - callback, userData, configJson, appCallbacks - ), - ).isOkOr: - let msg = "error in sendRequestToFFIThread: " & $error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return nil - - return ctx - -proc waku_destroy( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer -): cint {.dynlib, exportc, cdecl.} = - initializeLibrary() - checkParams(ctx, callback, userData) - - ffi.destroyFFIContext(ctx).isOkOr: - let msg = "libwaku error: " & $error - callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) - return RET_ERR - - ## always need to invoke the callback although we don't retrieve value to the caller - callback(RET_OK, nil, 0, userData) - - return RET_OK - -# ### End of exported procs -# ################################################################################ +# Node lifecycle (create / start / stop / destroy) is unified under the stable +# logosdelivery_* surface in ./logos_delivery_api/node_api. The former +# waku_new / waku_start / waku_stop / waku_destroy entry points were removed to +# avoid maintaining two parallel node-lifecycle APIs. diff --git a/library/liblogosdelivery_kernel.h b/library/liblogosdelivery_kernel.h index 0b502327b..58c67b712 100644 --- a/library/liblogosdelivery_kernel.h +++ b/library/liblogosdelivery_kernel.h @@ -26,26 +26,11 @@ extern "C" { #endif - // Creates a new instance of the waku node. - // Sets up the waku node from the given configuration. - // Returns a pointer to the Context needed by the rest of the API functions. - void *waku_new( - const char *configJson, - FFICallBack callback, - void *userData); - - int waku_start(void *ctx, - FFICallBack callback, - void *userData); - - int waku_stop(void *ctx, - FFICallBack callback, - void *userData); - - // Destroys an instance of a waku node created with waku_new - int waku_destroy(void *ctx, - FFICallBack callback, - void *userData); + // NOTE: node lifecycle (create / start / stop / destroy) is unified and lives + // only in the stable header. Use logosdelivery_create_node, + // logosdelivery_start_node, logosdelivery_stop_node and logosdelivery_destroy + // (declared in liblogosdelivery.h, included above) regardless of whether you + // drive the node through the messaging surface or this kernel API. int waku_version(void *ctx, FFICallBack callback, diff --git a/library/logos_delivery_api/debug_api.nim b/library/logos_delivery_api/debug_api.nim index e93467d84..98d48c97c 100644 --- a/library/logos_delivery_api/debug_api.nim +++ b/library/logos_delivery_api/debug_api.nim @@ -3,17 +3,17 @@ import logos_delivery/waku/factory/waku_state_info import tools/confutils/[cli_args, config_option_meta] proc logosdelivery_get_available_node_info_ids( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = ## Returns the list of all available node info item ids that ## can be queried with `get_node_info_item`. requireInitializedNode(ctx, "GetNodeInfoIds"): return err(errMsg) - return ok($ctx.myLib[].stateInfo.getAllPossibleInfoItemIds()) + return ok($ctx.myLib[].waku.stateInfo.getAllPossibleInfoItemIds()) proc logosdelivery_get_node_info( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, nodeInfoId: cstring, @@ -28,10 +28,10 @@ proc logosdelivery_get_node_info( except ValueError: return err("Invalid node info id: " & $nodeInfoId) - return ok(ctx.myLib[].stateInfo.getNodeInfoItem(infoItemIdEnum)) + return ok(ctx.myLib[].waku.stateInfo.getNodeInfoItem(infoItemIdEnum)) proc logosdelivery_get_available_configs( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = ## Returns information about the accepted config items. requireInitializedNode(ctx, "GetAvailableConfigs"): diff --git a/library/logos_delivery_api/messaging_api.nim b/library/logos_delivery_api/messaging_api.nim index ff7c7f6d0..4cab9bd93 100644 --- a/library/logos_delivery_api/messaging_api.nim +++ b/library/logos_delivery_api/messaging_api.nim @@ -3,13 +3,13 @@ import chronos, results, ffi import stew/byteutils import logos_delivery/waku/common/base64, - logos_delivery/waku/factory/waku, + logos_delivery/waku/waku, logos_delivery/waku/waku_core/topics/content_topic, - logos_delivery/waku/api/[api, types], + logos_delivery/api/types, ../declare_lib proc logosdelivery_subscribe( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, contentTopicStr: cstring, @@ -17,17 +17,20 @@ proc logosdelivery_subscribe( requireInitializedNode(ctx, "Subscribe"): return err(errMsg) + requireMessaging(ctx, "Subscribe"): + return err(errMsg) + # ContentTopic is just a string type alias let contentTopic = ContentTopic($contentTopicStr) - (await api.subscribe(ctx.myLib[], contentTopic)).isOkOr: + (await ctx.myLib[].messagingClient.subscribe(contentTopic)).isOkOr: let errMsg = $error return err("Subscribe failed: " & errMsg) return ok("") proc logosdelivery_unsubscribe( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, contentTopicStr: cstring, @@ -35,17 +38,20 @@ proc logosdelivery_unsubscribe( requireInitializedNode(ctx, "Unsubscribe"): return err(errMsg) + requireMessaging(ctx, "Unsubscribe"): + return err(errMsg) + # ContentTopic is just a string type alias let contentTopic = ContentTopic($contentTopicStr) - api.unsubscribe(ctx.myLib[], contentTopic).isOkOr: + ctx.myLib[].messagingClient.unsubscribe(contentTopic).isOkOr: let errMsg = $error return err("Unsubscribe failed: " & errMsg) return ok("") proc logosdelivery_send( - ctx: ptr FFIContext[Waku], + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer, messageJson: cstring, @@ -53,6 +59,9 @@ proc logosdelivery_send( requireInitializedNode(ctx, "Send"): return err(errMsg) + requireMessaging(ctx, "Send"): + return err(errMsg) + ## Parse the message JSON and send the message var jsonNode: JsonNode try: @@ -83,8 +92,8 @@ proc logosdelivery_send( contentTopic = contentTopic, payload = payload, ephemeral = ephemeral ) - # Send the message - let requestId = (await api.send(ctx.myLib[], envelope)).valueOr: + # Send the message via the messaging layer's own API. + let requestId = (await ctx.myLib[].messagingClient.send(envelope)).valueOr: let errMsg = $error return err("Send failed: " & errMsg) diff --git a/library/logos_delivery_api/node_api.nim b/library/logos_delivery_api/node_api.nim index 58785e80d..d70c34252 100644 --- a/library/logos_delivery_api/node_api.nim +++ b/library/logos_delivery_api/node_api.nim @@ -1,11 +1,13 @@ import std/json import chronos, chronicles, results, ffi +import logos_delivery/waku/common/base64 import - logos_delivery/waku/factory/waku, + logos_delivery, logos_delivery/waku/node/waku_node, - logos_delivery/waku/api/[api, types], - logos_delivery/waku/events/[message_events, health_events], - tools/confutils/conf_from_json, + logos_delivery/api/types, + logos_delivery/waku/api/events/health_events, + logos_delivery/waku/api/events/peer_events, + logos_delivery/api/conf/logos_delivery_conf_json, ../declare_lib, ../json_event @@ -13,14 +15,14 @@ import proc `%`*(id: RequestId): JsonNode = %($id) -registerReqFFI(CreateNodeRequest, ctx: ptr FFIContext[Waku]): +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 api.createNode(conf)).valueOr: + ctx.myLib[] = (await LogosDelivery.new(conf)).valueOr: let errMsg = $error chronicles.error "CreateNodeRequest failed", err = errMsg return err(errMsg) @@ -28,7 +30,7 @@ registerReqFFI(CreateNodeRequest, ctx: ptr FFIContext[Waku]): return ok("") proc logosdelivery_destroy( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ): cint {.dynlib, exportc, cdecl.} = initializeLibrary() checkParams(ctx, callback, userData) @@ -52,7 +54,7 @@ proc logosdelivery_create_node( echo "error: missing callback in logosdelivery_create_node" return nil - var ctx = ffi.createFFIContext[Waku]().valueOr: + var ctx = ffi.createFFIContext[LogosDelivery]().valueOr: let msg = "Error in createFFIContext: " & $error callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](len(msg)), userData) return nil @@ -73,14 +75,14 @@ proc logosdelivery_create_node( return ctx proc logosdelivery_start_node( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = requireInitializedNode(ctx, "START_NODE"): return err(errMsg) # setting up outgoing event listeners let sentListener = MessageSentEvent.listen( - ctx.myLib[].brokerCtx, + ctx.myLib[].waku.brokerCtx, proc(event: MessageSentEvent) {.async: (raises: []).} = callEventCallback(ctx, "onMessageSent"): $newJsonEvent("message_sent", event), @@ -89,7 +91,7 @@ proc logosdelivery_start_node( return err("MessageSentEvent.listen failed: " & $error) let errorListener = MessageErrorEvent.listen( - ctx.myLib[].brokerCtx, + ctx.myLib[].waku.brokerCtx, proc(event: MessageErrorEvent) {.async: (raises: []).} = callEventCallback(ctx, "onMessageError"): $newJsonEvent("message_error", event), @@ -98,7 +100,7 @@ proc logosdelivery_start_node( return err("MessageErrorEvent.listen failed: " & $error) let propagatedListener = MessagePropagatedEvent.listen( - ctx.myLib[].brokerCtx, + ctx.myLib[].waku.brokerCtx, proc(event: MessagePropagatedEvent) {.async: (raises: []).} = callEventCallback(ctx, "onMessagePropagated"): $newJsonEvent("message_propagated", event), @@ -107,7 +109,7 @@ proc logosdelivery_start_node( return err("MessagePropagatedEvent.listen failed: " & $error) let receivedListener = MessageReceivedEvent.listen( - ctx.myLib[].brokerCtx, + ctx.myLib[].waku.brokerCtx, proc(event: MessageReceivedEvent) {.async: (raises: []).} = callEventCallback(ctx, "onMessageReceived"): $newJsonEvent("message_received", event), @@ -116,7 +118,7 @@ proc logosdelivery_start_node( return err("MessageReceivedEvent.listen failed: " & $error) let ConnectionStatusChangeListener = EventConnectionStatusChange.listen( - ctx.myLib[].brokerCtx, + ctx.myLib[].waku.brokerCtx, proc(event: EventConnectionStatusChange) {.async: (raises: []).} = callEventCallback(ctx, "onConnectionStatusChange"): $newJsonEvent("connection_status_change", event), @@ -124,15 +126,69 @@ proc logosdelivery_start_node( chronicles.error "ConnectionStatusChange.listen failed", err = $error return err("ConnectionStatusChange.listen failed: " & $error) - ctx.myLib[].mountMessagingClient().isOkOr: - let errMsg = $error - chronicles.error "mountMessagingClient failed", error = errMsg - return err("failed to mount messaging: " & errMsg) + let shardTopicHealthListener = EventShardTopicHealthChange.listen( + ctx.myLib[].waku.brokerCtx, + proc(event: EventShardTopicHealthChange) {.async: (raises: []).} = + callEventCallback(ctx, "onTopicHealthChange"): + $( + %*{ + "eventType": "relay_topic_health_change", + "pubsubTopic": $event.topic, + "topicHealth": $event.health, + } + ), + ).valueOr: + chronicles.error "EventShardTopicHealthChange.listen failed", err = $error + return err("EventShardTopicHealthChange.listen failed: " & $error) - ctx.myLib[].mountReliableChannelManager().isOkOr: - let errMsg = $error - chronicles.error "mountReliableChannelManager failed", err = errMsg - return err("failed to mount reliable channel manager: " & errMsg) + let peerEventListener = WakuPeerEvent.listen( + ctx.myLib[].waku.brokerCtx, + proc(event: WakuPeerEvent) {.async: (raises: []).} = + callEventCallback(ctx, "onConnectionChange"): + $( + %*{ + "eventType": "connection_change", + "peerId": $event.peerId, + "peerEvent": $event.kind, + } + ), + ).valueOr: + chronicles.error "WakuPeerEvent.listen failed", err = $error + return err("WakuPeerEvent.listen failed: " & $error) + + let channelReceivedListener = ChannelMessageReceivedEvent.listen( + ctx.myLib[].waku.brokerCtx, + proc(event: ChannelMessageReceivedEvent) {.async: (raises: []).} = + callEventCallback(ctx, "onChannelMessageReceived"): + $( + %*{ + "eventType": "channel_message_received", + "channelId": string(event.channelId), + "senderId": $event.senderId, + "payload": string(base64.encode(event.payload)), + } + ), + ).valueOr: + chronicles.error "ChannelMessageReceivedEvent.listen failed", err = $error + return err("ChannelMessageReceivedEvent.listen failed: " & $error) + + let channelSentListener = ChannelMessageSentEvent.listen( + ctx.myLib[].waku.brokerCtx, + proc(event: ChannelMessageSentEvent) {.async: (raises: []).} = + callEventCallback(ctx, "onChannelMessageSent"): + $newJsonEvent("channel_message_sent", event), + ).valueOr: + chronicles.error "ChannelMessageSentEvent.listen failed", err = $error + return err("ChannelMessageSentEvent.listen failed: " & $error) + + let channelErrorListener = ChannelMessageErrorEvent.listen( + ctx.myLib[].waku.brokerCtx, + proc(event: ChannelMessageErrorEvent) {.async: (raises: []).} = + callEventCallback(ctx, "onChannelMessageError"): + $newJsonEvent("channel_message_error", event), + ).valueOr: + chronicles.error "ChannelMessageErrorEvent.listen failed", err = $error + return err("ChannelMessageErrorEvent.listen failed: " & $error) (await ctx.myLib[].start()).isOkOr: let errMsg = $error @@ -141,16 +197,21 @@ proc logosdelivery_start_node( return ok("") proc logosdelivery_stop_node( - ctx: ptr FFIContext[Waku], callback: FFICallBack, userData: pointer + ctx: ptr FFIContext[LogosDelivery], callback: FFICallBack, userData: pointer ) {.ffi.} = requireInitializedNode(ctx, "STOP_NODE"): return err(errMsg) - await MessageErrorEvent.dropAllListeners(ctx.myLib[].brokerCtx) - await MessageSentEvent.dropAllListeners(ctx.myLib[].brokerCtx) - await MessagePropagatedEvent.dropAllListeners(ctx.myLib[].brokerCtx) - await MessageReceivedEvent.dropAllListeners(ctx.myLib[].brokerCtx) - await EventConnectionStatusChange.dropAllListeners(ctx.myLib[].brokerCtx) + await MessageErrorEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await MessageSentEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await MessagePropagatedEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await MessageReceivedEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await EventConnectionStatusChange.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await EventShardTopicHealthChange.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await WakuPeerEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await ChannelMessageReceivedEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await ChannelMessageSentEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) + await ChannelMessageErrorEvent.dropAllListeners(ctx.myLib[].waku.brokerCtx) (await ctx.myLib[].stop()).isOkOr: let errMsg = $error diff --git a/logos_delivery.nim b/logos_delivery.nim index c4edbe6d6..bab1d3e24 100644 --- a/logos_delivery.nim +++ b/logos_delivery.nim @@ -1,10 +1,15 @@ -## Main module for using nwaku as a Nimble library +## Package entry point for using Logos Messaging as a Nimble library. ## -## This module re-exports the public API for creating and managing Waku nodes -## when using nwaku as a library dependency. +## This root module is a thin aggregator, following the standard Nimble layout: +## the implementation lives under `./logos_delivery/`, and importing the package +## name re-exports `LogosDelivery` together with every per-layer public API. +## +## See `logos_delivery/logos_delivery.nim` for `LogosDelivery`, the pure +## concentrator that owns one instance of each API layer +## +## Waku <- MessagingClient <- ReliableChannelManager +## +## and drives their shared `new` / `start` / `stop` lifecycle. -import logos_delivery/waku/api -export api - -import logos_delivery/waku/factory/waku -export waku +import ./logos_delivery/logos_delivery as logos_delivery_impl +export logos_delivery_impl diff --git a/logos_delivery.nimble b/logos_delivery.nimble index 9c9ef7b96..5a3319f8d 100644 --- a/logos_delivery.nimble +++ b/logos_delivery.nimble @@ -65,7 +65,7 @@ requires "https://github.com/logos-messaging/nim-ffi#v0.1.3" requires "https://github.com/logos-messaging/nim-sds.git#b12f5ee07c5b764303b51fb948b32a4ade1de3b5" -requires "https://github.com/NagyZoltanPeter/nim-brokers.git#v3.1.1" +requires "https://github.com/NagyZoltanPeter/nim-brokers.git#v3.1.4" requires "https://github.com/vacp2p/nim-lsquic.git#v0.5.1" requires "https://github.com/vacp2p/nim-jwt.git#057ec95eb5af0eea9c49bfe9025b3312c95dc5f2" diff --git a/logos_delivery/api/conf/channels_conf.nim b/logos_delivery/api/conf/channels_conf.nim new file mode 100644 index 000000000..5d19e802a --- /dev/null +++ b/logos_delivery/api/conf/channels_conf.nim @@ -0,0 +1,15 @@ +import std/options + +type ReliableChannelManagerConf* = object + ## 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. diff --git a/logos_delivery/api/conf/kernel_conf.nim b/logos_delivery/api/conf/kernel_conf.nim new file mode 100644 index 000000000..d0131b561 --- /dev/null +++ b/logos_delivery/api/conf/kernel_conf.nim @@ -0,0 +1,6 @@ +import tools/confutils/cli_args +export cli_args + +type KernelConf* = distinct WakuNodeConf + ## Raw kernel config; distinct so `new(KernelConf)` doesn't collide with the + ## full-stack `new(WakuNodeConf)`. diff --git a/logos_delivery/api/conf/logos_delivery_conf.nim b/logos_delivery/api/conf/logos_delivery_conf.nim new file mode 100644 index 000000000..e5ac7432a --- /dev/null +++ b/logos_delivery/api/conf/logos_delivery_conf.nim @@ -0,0 +1,39 @@ +{.push raises: [].} + +import std/options +import results + +import logos_delivery/api/conf/messaging_conf +import logos_delivery/api/conf/channels_conf + +export options, messaging_conf, channels_conf + +type LogosDeliveryConf* = object + ## Aggregates the per-layer config objects. A layer is mounted iff its config + ## is present. + kernelConf*: KernelConf + messagingConf*: Option[MessagingClientConf] + channelsConf*: Option[ReliableChannelManagerConf] + +proc init*(T: type LogosDeliveryConf, kernelConf: KernelConf): LogosDeliveryConf = + return LogosDeliveryConf(kernelConf: kernelConf) + +proc init*( + T: type LogosDeliveryConf, + mode: LogosDeliveryMode, + preset: string, + messagingOverrides: MessagingClientConf, + channelsOverrides: ReliableChannelManagerConf, +): ConfResult[LogosDeliveryConf] = + let merged = merge(?resolvePreset(preset), messagingOverrides) + var kernelConf = ?toWakuNodeConf(merged, mode) + kernelConf.preset = preset + return ok( + LogosDeliveryConf( + kernelConf: KernelConf(kernelConf), + messagingConf: some(merged), + channelsConf: some(channelsOverrides), + ) + ) + +{.pop.} diff --git a/logos_delivery/api/conf/logos_delivery_conf_json.nim b/logos_delivery/api/conf/logos_delivery_conf_json.nim new file mode 100644 index 000000000..e5df174e4 --- /dev/null +++ b/logos_delivery/api/conf/logos_delivery_conf_json.nim @@ -0,0 +1,163 @@ +{.push raises: [].} + +import std/[json, options, strutils, tables] +import results + +import tools/confutils/conf_from_json +import logos_delivery/api/conf/logos_delivery_conf + +const + # Lowercased, since `collectJsonFields` keys the object case-insensitively. + KeyMode = "mode" + KeyPreset = "preset" + KeyKernelConf = "kernelconf" + KeyMessagingOverrides = "messagingoverrides" + KeyChannelsOverrides = "channelsoverrides" + # [Legacy flat JSON config] Keys that left the kernel and so must be lifted out of + # a flat blob before the WakuNodeConf walker sees them (it would reject them). + KeyReliabilityEnabled = "reliabilityenabled" + KeyReliability = "reliability" + +proc parseMode(s: string): Result[LogosDeliveryMode, string] = + case s.strip().toLowerAscii() + of "core": + return ok(LogosDeliveryMode.Core) + of "edge": + return ok(LogosDeliveryMode.Edge) + of "fleet": + return ok(LogosDeliveryMode.Fleet) + else: + return err("invalid mode: '" & s & "' (expected 'Core', 'Edge' or 'Fleet')") + +proc parseOverrides[T](defaults: T, node: JsonNode, label: string): Result[T, string] = + ## Parse the JSON object `node` as overrides on top of `defaults`. + if node.kind != JObject: + return err(label & " must be a JSON object") + var fields = ?collectJsonFields(node) + var conf = defaults + ?applyJsonFieldsToConf( + conf, + fields, + "Failed to parse " & label & " field", + "Unrecognized " & label & " option(s) found", + ) + return ok(conf) + +proc parseFlatConf( + mode: LogosDeliveryMode, topJsonNode: var Table[string, (string, JsonNode)] +): ConfResult[LogosDeliveryConf] = + ## [Legacy flat JSON config] Flat shape: a blob of `WakuNodeConf` fields. `mode` + ## expands to protocol flags over raw kernel defaults, `reliabilityEnabled` routes + ## to the messaging conf (it left the kernel), and the rest parses as a + ## `WakuNodeConf`. Full stack. Delete this proc and its call site to drop support. + var messaging = MessagingClientConf() + var reliabilityFields: Table[string, (string, JsonNode)] + for key in [KeyReliabilityEnabled, KeyReliability]: + if topJsonNode.hasKey(key): + reliabilityFields[key] = topJsonNode.getOrDefault(key) + topJsonNode.del(key) + if reliabilityFields.len > 0: + ?applyJsonFieldsToConf( + messaging, reliabilityFields, "Failed to parse reliability field", + "Unrecognized reliability option(s) found", + ) + + # [Legacy flat JSON config] The blob is a raw WakuNodeConf, exactly as the + # pre-refactor flat create_node parsed it: start from the kernel defaults and apply + # the mode's protocol flags (the kernel no longer owns `mode`, so we expand it here, + # like the old kernel builder did), then let explicit fields override. + var kernel = ?defaultWakuNodeConf() + ?applyMode(kernel, mode) + ?applyJsonFieldsToConf( + kernel, topJsonNode, "Failed to parse config field", + "Unrecognized configuration option(s) found", + ) + + # [Legacy flat JSON config] Reliability is resolved from the preset by the messaging + # layer (the kernel no longer carries it), so a flat blob's `preset` must lift it + # here to stay faithful to master. An explicit reliability in the blob still wins. + if kernel.preset.len > 0: + messaging = merge(?resolvePreset(kernel.preset), messaging) + + return ok( + LogosDeliveryConf( + kernelConf: KernelConf(kernel), + messagingConf: some(messaging), + channelsConf: some(ReliableChannelManagerConf()), + ) + ) + +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 = LogosDeliveryMode.Core + 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 mode == LogosDeliveryMode.Fleet: + # Kernel-only: a raw kernelConf and no upper layers. + if not top.hasKey(KeyKernelConf): + return err("fleet mode requires a 'kernelConf' object") + let (_, v) = top.getOrDefault(KeyKernelConf) + let kernel = ?parseOverrides(?defaultWakuNodeConf(), v, "kernelConf") + top.del(KeyKernelConf) + if top.len > 0: + return + err(unknownKeysError(top, "fleet mode takes only 'kernelConf'; unexpected")) + return ok(LogosDeliveryConf.init(KernelConf(kernel))) + + # [Legacy flat JSON config] A wrapper key marks our structured shape. Otherwise any + # leftover top-level key besides `preset` (mode is already consumed) is a bare + # kernel field -> flat blob. Delete this block to drop flat-shape support. + let hasWrapper = + top.hasKey(KeyMessagingOverrides) or top.hasKey(KeyChannelsOverrides) or + top.hasKey(KeyKernelConf) + if not hasWrapper: + var bareField = false + for k in top.keys: + if k != KeyPreset: + bareField = true + break + if bareField: + return parseFlatConf(mode, top) + + var preset = "" + var messagingOverrides = MessagingClientConf() + var channelsOverrides = ReliableChannelManagerConf() + + 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: + return err(unknownKeysError(top, "Unrecognized configuration option(s) found")) + + return LogosDeliveryConf.init(mode, preset, messagingOverrides, channelsOverrides) + +{.pop.} diff --git a/logos_delivery/api/conf/messaging_conf.nim b/logos_delivery/api/conf/messaging_conf.nim new file mode 100644 index 000000000..86a73575b --- /dev/null +++ b/logos_delivery/api/conf/messaging_conf.nim @@ -0,0 +1,147 @@ +import std/options +import std/net +import results +import libp2p/crypto/crypto + +import logos_delivery/api/conf/kernel_conf +import logos_delivery/waku/common/logging +import logos_delivery/waku/factory/networks_config + +export kernel_conf + +type LogosDeliveryMode* {.pure.} = enum + Edge # client-only node + Core # full service node + Fleet # kernel-only node from a raw kernel config + +type MessagingClientConf* = object + 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. + +proc applyMode*(conf: var WakuNodeConf, mode: LogosDeliveryMode): ConfResult[void] = + ## Sets the protocol flags implied by the mode. + case mode + of LogosDeliveryMode.Core: + conf.relay = true + conf.filter = true + conf.lightpush = true + conf.discv5Discovery = some(true) + conf.peerExchange = true + conf.rendezvous = true + of LogosDeliveryMode.Edge: + conf.peerExchange = true + conf.relay = false + conf.filter = false + conf.lightpush = false + conf.store = false + of LogosDeliveryMode.Fleet: + return + err("fleet mode takes a raw kernel config; use LogosDelivery.new(kernelConf)") + return ok() + +proc toWakuNodeConf*( + self: MessagingClientConf, mode: LogosDeliveryMode +): ConfResult[WakuNodeConf] = + ## Mode sets the protocol flags; set fields map to their kernel counterpart. + var conf = ?defaultWakuNodeConf() + ?applyMode(conf, mode) + + if self.store.isSome(): + conf.store = self.store.get() + if self.storeMessageDbUrl.isSome(): + conf.storeMessageDbUrl = self.storeMessageDbUrl.get() + if self.storeMessageRetentionPolicy.isSome(): + conf.storeMessageRetentionPolicy = self.storeMessageRetentionPolicy.get() + if self.storeMaxNumDbConnections.isSome(): + conf.storeMaxNumDbConnections = self.storeMaxNumDbConnections.get() + if self.storenode.isSome(): + conf.storenode = self.storenode.get() + + if self.clusterId.isSome(): + conf.clusterId = self.clusterId + if self.numShardsInCluster.isSome(): + conf.numShardsInNetwork = self.numShardsInCluster.get() + if self.listenIpv4.isSome(): + conf.listenAddress = self.listenIpv4.get() + if self.maxMessageSize.isSome(): + conf.maxMessageSize = self.maxMessageSize.get() + if self.entryNodes.isSome(): + conf.entryNodes = self.entryNodes.get() + if self.ethRpcEndpoints.isSome(): + conf.ethClientUrls = self.ethRpcEndpoints.get() + if self.rlnContractAddress.isSome(): + conf.rlnRelayEthContractAddress = self.rlnContractAddress.get() + conf.rlnRelay = some(true) + if self.rlnChainId.isSome(): + conf.rlnRelayChainId = self.rlnChainId.get() + if self.rlnEpochSizeSec.isSome(): + conf.rlnEpochSizeSec = some(self.rlnEpochSizeSec.get().uint64) + if self.logLevel.isSome(): + conf.logLevel = self.logLevel.get() + if self.logFormat.isSome(): + conf.logFormat = self.logFormat.get() + if self.nodeKey.isSome(): + conf.nodekey = self.nodeKey + + conf.tcpPort = self.p2pTcpPort.get(Port(0)) + conf.discv5UdpPort = self.discv5UdpPort.get(Port(0)) + conf.websocketPort = self.websocketPort.get(Port(0)) + conf.quicPort = self.quicPort.get(Port(0)) + conf.websocketSupport = self.websocketSupport.get(false) + conf.quicSupport = self.quicSupport.get(true) + + return ok(conf) + +proc merge*(base, overrides: MessagingClientConf): MessagingClientConf = + var m = base + for _, mField, oField in fieldPairs(m, overrides): + when oField is Option: + if oField.isSome(): + mField = oField + return m + +proc resolvePreset*(preset: string): ConfResult[MessagingClientConf] = + ## Preset to messaging-only fields. Kernel-mirrored fields stay unset; the + ## kernel resolves those from `conf.preset`. + let npcOpt = ?toNetworkPresetConf(preset, none(uint16)) + if npcOpt.isNone(): + return ok(MessagingClientConf()) + let npc = npcOpt.get() + return ok(MessagingClientConf(reliabilityEnabled: some(npc.p2pReliability))) diff --git a/logos_delivery/api/events/kernel_events.nim b/logos_delivery/api/events/kernel_events.nim new file mode 100644 index 000000000..d8dd46627 --- /dev/null +++ b/logos_delivery/api/events/kernel_events.nim @@ -0,0 +1,18 @@ +import brokers/event_broker + +import logos_delivery/api/types +import logos_delivery/waku/waku_core/topics/pubsub_topic +import logos_delivery/waku/waku_core/message + +export event_broker, pubsub_topic, message + +EventBroker: + # Internal event emitted when a message arrives from the network via any protocol + type MessageSeenEvent* = object + topic*: PubsubTopic + message*: WakuMessage + +# Emitted by the health monitor when overall node connectivity changes. +EventBroker: + type EventConnectionStatusChange* = object + connectionStatus*: ConnectionStatus diff --git a/logos_delivery/waku/events/message_events.nim b/logos_delivery/api/events/messaging_client_events.nim similarity index 72% rename from logos_delivery/waku/events/message_events.nim rename to logos_delivery/api/events/messaging_client_events.nim index 9338fda67..b3b7a46f6 100644 --- a/logos_delivery/waku/events/message_events.nim +++ b/logos_delivery/api/events/messaging_client_events.nim @@ -1,6 +1,8 @@ import brokers/event_broker -import logos_delivery/waku/[api/types, waku_core/message, waku_core/topics] -export types + +import logos_delivery/api/types as api_types + +export event_broker, api_types EventBroker: # Event emitted when a message is sent to the network @@ -26,9 +28,3 @@ EventBroker: type MessageReceivedEvent* = object messageHash*: string message*: WakuMessage - -EventBroker: - # Internal event emitted when a message arrives from the network via any protocol - type MessageSeenEvent* = object - topic*: PubsubTopic - message*: WakuMessage diff --git a/logos_delivery/channels/events.nim b/logos_delivery/api/events/reliable_channel_manager_events.nim similarity index 52% rename from logos_delivery/channels/events.nim rename to logos_delivery/api/events/reliable_channel_manager_events.nim index 5f69095e4..0f3b4e1ec 100644 --- a/logos_delivery/channels/events.nim +++ b/logos_delivery/api/events/reliable_channel_manager_events.nim @@ -1,20 +1,8 @@ -## Reliable Channel event types emitted to API consumers. -## -## Lifecycle events for individual segments (sent / propagated / errored) -## are the same as the network-level ones the MessagingClient already -## emits — `requestId` is shared across layers — so we just re-export -## `waku/events/message_events` and avoid declaring duplicates. -## -## Only the channel-level `MessageReceivedEvent` carries data that has -## no analogue in the lower layer (reassembled application payload, -## senderId, channelId), so it lives here. - -import logos_delivery/waku/events/message_events as waku_message_events import brokers/event_broker -import ./types as channel_types +import logos_delivery/channels/types as channel_types -export waku_message_events, channel_types, event_broker +export event_broker, channel_types EventBroker: type ChannelMessageReceivedEvent* = object diff --git a/logos_delivery/api/kernel_api.nim b/logos_delivery/api/kernel_api.nim new file mode 100644 index 000000000..01b0c30c0 --- /dev/null +++ b/logos_delivery/api/kernel_api.nim @@ -0,0 +1,81 @@ +import chronos, results + +import logos_delivery/api/types as api_types +import logos_delivery/waku/waku_core/topics/pubsub_topic +import logos_delivery/waku/waku_core/message +import logos_delivery/waku/waku_core/subscription/push_handler +import logos_delivery/waku/waku_store/common as store_types + +export api_types, pubsub_topic, store_types + +# Structural API contract for the Kernel surface, implemented by `Waku` +# (ops in `waku/api/*`). +type KernelApi* = concept w + # --- topic construction --- + buildContentTopic( + w, appName = string, appVersion = uint32, name = string, encoding = string + ) is Future[Result[ContentTopic, string]] + buildPubsubTopic(w, topicName = string) is Future[Result[PubsubTopic, string]] + defaultPubsubTopic(w) is Future[Result[PubsubTopic, string]] + + # --- relay --- + relayPublish(w, pubsubTopic = PubsubTopic, message = WakuMessage, timeoutMs = uint32) is + Future[Result[string, string]] + relaySubscribe(w, pubsubTopic = PubsubTopic) is Future[Result[bool, string]] + relayUnsubscribe(w, pubsubTopic = PubsubTopic) is Future[Result[bool, string]] + relayAddProtectedShard(w, clusterId = uint16, shardId = uint16, publicKey = string) is + Future[Result[bool, string]] + relayConnectedPeers(w, pubsubTopic = PubsubTopic) is + Future[Result[seq[string], string]] + relayPeersInMesh(w, pubsubTopic = PubsubTopic) is Future[Result[seq[string], string]] + relayNumPeersInMesh(w, pubsubTopic = PubsubTopic) is Future[Result[int, string]] + relayNumConnectedPeers(w, pubsubTopic = PubsubTopic) is Future[Result[int, string]] + + # --- filter --- + filterSubscribe( + w, + pubsubTopic = PubsubTopic, + contentTopics = seq[ContentTopic], + pushHandler = FilterPushHandler, + ) is Future[Result[bool, string]] + filterUnsubscribe(w, pubsubTopic = PubsubTopic, contentTopics = seq[ContentTopic]) is + Future[Result[bool, string]] + filterUnsubscribeAll(w) is Future[Result[bool, string]] + + # --- lightpush --- + lightpushPublish(w, pubsubTopic = PubsubTopic, message = WakuMessage) is + Future[Result[string, string]] + + # --- store --- + storeQuery(w, request = StoreQueryRequest, peer = string, timeoutMs = int) is + Future[Result[StoreQueryResponse, string]] + + # --- peer management --- + connect(w, peers = seq[string], timeoutMs = uint32) is Future[Result[bool, string]] + disconnectPeerById(w, peerId = string) is Future[Result[bool, string]] + disconnectAllPeers(w) is Future[Result[bool, string]] + dialPeer(w, peerAddr = string, protocol = string, timeoutMs = int) is + Future[Result[bool, string]] + dialPeerById(w, peerId = string, protocol = string, timeoutMs = int) is + Future[Result[bool, string]] + peerIdsFromPeerstore(w) is Future[Result[seq[string], string]] + connectedPeersInfo(w) is Future[Result[seq[PeerConnInfo], string]] + connectedPeers(w) is Future[Result[seq[string], string]] + peerIdsByProtocol(w, protocol = string) is Future[Result[seq[string], string]] + + # --- discovery --- + dnsDiscovery(w, enrTreeUrl = string, nameServer = string, timeoutMs = int) is + Future[Result[seq[string], string]] + discv5UpdateBootnodes(w, bootnodes = string) is Future[Result[bool, string]] + startDiscv5(w) is Future[Result[bool, string]] + stopDiscv5(w) is Future[Result[bool, string]] + peerExchangeRequest(w, numPeers = uint64) is Future[Result[int, string]] + + # --- debug / info --- + version(w) is Future[Result[string, string]] + listenAddresses(w) is Future[Result[seq[string], string]] + myEnr(w) is Future[Result[string, string]] + myPeerId(w) is Future[Result[string, string]] + metrics(w) is Future[Result[string, string]] + isOnline(w) is Future[Result[bool, string]] + pingPeer(w, peerAddr = string, timeoutMs = int) is Future[Result[int64, string]] diff --git a/logos_delivery/api/logos_delivery_api.nim b/logos_delivery/api/logos_delivery_api.nim new file mode 100644 index 000000000..708484553 --- /dev/null +++ b/logos_delivery/api/logos_delivery_api.nim @@ -0,0 +1,11 @@ +import chronos, results + +# Structural API contract for the top-level entry point, implemented by +# `LogosDelivery` (the aggregate owning one instance of each API layer). +type LogosDeliveryApi* = concept ld + # --- lifecycle --- + start(ld) is Future[Result[void, string]] + stop(ld) is Future[Result[void, string]] + + # --- health --- + isOnline(ld) is Future[Result[bool, string]] diff --git a/logos_delivery/api/messaging_client_api.nim b/logos_delivery/api/messaging_client_api.nim new file mode 100644 index 000000000..5906ab564 --- /dev/null +++ b/logos_delivery/api/messaging_client_api.nim @@ -0,0 +1,18 @@ +import chronos, results +import brokers/[request_broker, broker_context] +import logos_delivery/api/types as api_types + +export api_types, request_broker, broker_context + +# Structural API contract for a messaging client (ops in `messaging/api/*`). +type MessagingApi* = concept c + subscribe(c, contentTopic = ContentTopic) is Future[Result[void, string]] + unsubscribe(c, contentTopic = ContentTopic) is Result[void, string] + send(c, envelope = MessageEnvelope) is Future[Result[RequestId, string]] + +# Semi detached MessagingClient send interface. +# Can be used without MessagingClient, under the same context of MessagingClient instance. +RequestBroker: + proc MessagingSend( + envelope: MessageEnvelope + ): Future[Result[RequestId, string]] {.async.} diff --git a/logos_delivery/api/reliable_channel_manager_api.nim b/logos_delivery/api/reliable_channel_manager_api.nim new file mode 100644 index 000000000..8d3f63ff3 --- /dev/null +++ b/logos_delivery/api/reliable_channel_manager_api.nim @@ -0,0 +1,15 @@ +import chronos, results + +import logos_delivery/api/types as api_types +import logos_delivery/channels/types as channel_types + +export api_types, channel_types + +# Structural API contract for the reliable-channel surface (ops in `channels/api/*`). +type ReliableChannelApi* = concept c + createReliableChannel( + c, channelId = ChannelId, contentTopic = ContentTopic, senderId = SdsParticipantID + ) is Result[ChannelId, string] + closeChannel(c, channelId = ChannelId) is Future[Result[void, string]] + send(c, channelId = ChannelId, appPayload = seq[byte]) is + Future[Result[RequestId, string]] diff --git a/logos_delivery/waku/api/types.nim b/logos_delivery/api/types.nim similarity index 92% rename from logos_delivery/waku/api/types.nim rename to logos_delivery/api/types.nim index d51d46994..97e7a11c9 100644 --- a/logos_delivery/waku/api/types.nim +++ b/logos_delivery/api/types.nim @@ -6,7 +6,8 @@ import bearssl/rand, std/times, chronos import stew/byteutils import logos_delivery/waku/utils/requests as request_utils import logos_delivery/waku/waku_core/[topics/content_topic, message/message, time] -import logos_delivery/waku/requests/requests + +export content_topic, message type MessageEnvelope* = object @@ -25,6 +26,11 @@ type PartiallyConnected Connected + PeerConnInfo* = object ## structured connected-peer info for the api boundary + peerId*: string + protocols*: seq[string] + addresses*: seq[string] + proc new*(T: typedesc[RequestId], rng: crypto.Rng): T = ## Generate a new RequestId using the provided RNG. RequestId(request_utils.generateRequestId(rng)) diff --git a/logos_delivery/channels/api/channel_lifecycle.nim b/logos_delivery/channels/api/channel_lifecycle.nim new file mode 100644 index 000000000..d110ceb72 --- /dev/null +++ b/logos_delivery/channels/api/channel_lifecycle.nim @@ -0,0 +1,87 @@ +## Reliable Channel layer API — channel lifecycle +## (createReliableChannel / closeChannel). +import std/[options, tables] +import results, chronos, chronicles + +import logos_delivery/api/types +import logos_delivery/channels/reliable_channel_manager +import logos_delivery/channels/reliable_channel +import logos_delivery/waku/persistency/sds_persistency + +# ReliableChannel, config and wire-version markers. +export reliable_channel + +const SdsJobId = "sds" + ## One persistency job shared by every channel's SDS state; rows are + ## keyed by channelId. + +proc sdsPersistence(): Option[Persistence] = + ## SDS backend from the Persistency singleton; memory-only fallback when + ## it is unavailable (e.g. unit tests). + let p = Persistency.instance().valueOr: + info "SDS persistence disabled, running memory-only", reason = $error + return none(Persistence) + let job = p.openJob(SdsJobId).valueOr: + warn "SDS persistence disabled, could not open persistency job", + jobId = SdsJobId, reason = $error + return none(Persistence) + return some(newSdsPersistence(job)) + +proc createReliableChannel*( + self: ReliableChannelManager, + channelId: ChannelId, + 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: cc.segmentationSegmentSizeBytes.get(DefaultSegmentSizeBytes), + enableReedSolomon: cc.segmentationEnableReedSolomon.get(false), + persistence: nil, + ) + let sdsConfig = SdsConfig( + acknowledgementTimeoutMs: + cc.sdsAcknowledgementTimeoutMs.get(DefaultAcknowledgementTimeoutMs), + maxRetransmissions: cc.sdsMaxRetransmissions.get(DefaultMaxRetransmissions), + causalHistorySize: cc.sdsCausalHistorySize.get(DefaultCausalHistorySize), + persistence: sdsPersistence(), + ) + let rateConfig = RateLimitConfig( + # 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( + channelId = channelId, + contentTopic = contentTopic, + senderId = senderId, + segConfig = segConfig, + sdsConfig = sdsConfig, + rateConfig = rateConfig, + brokerCtx = self.brokerCtx, + ) + + self.channels[channelId] = chn + return ok(channelId) + +proc closeChannel*( + self: ReliableChannelManager, channelId: ChannelId +): Future[Result[void, string]] {.async: (raises: []).} = + ## Stops the channel's SDS loops and releases the channel. Persisted SDS + ## state survives, so re-creating the channel restores it. + let chn = self.channels.getOrDefault(channelId) + if chn.isNil(): + return err("unknown channel: " & channelId) + self.channels.del(channelId) + await chn.stop() + return ok() diff --git a/logos_delivery/channels/api/send.nim b/logos_delivery/channels/api/send.nim new file mode 100644 index 000000000..23c41ac13 --- /dev/null +++ b/logos_delivery/channels/api/send.nim @@ -0,0 +1,21 @@ +## Reliable Channel layer API — channel send operation. +import std/tables +import results, chronos + +import logos_delivery/api/types +import logos_delivery/channels/reliable_channel_manager +import logos_delivery/channels/reliable_channel + +proc send*( + self: ReliableChannelManager, + channelId: ChannelId, + appPayload: seq[byte], + ephemeral: bool = false, +): Future[Result[RequestId, string]] {.async: (raises: []).} = + ## Spec-level entry point. Looks the channel up by id and delegates + ## to `ReliableChannel.send`, which exposes the visible pipeline + ## segmentation -> sds -> rate_limit_manager -> encryption. + let chn = self.channels.getOrDefault(channelId) + if chn.isNil(): + return err("unknown channel: " & channelId) + return await chn.send(appPayload, ephemeral) diff --git a/logos_delivery/channels/reliable_channel.nim b/logos_delivery/channels/reliable_channel.nim index 5a7ab24d4..4daf8a1e2 100644 --- a/logos_delivery/channels/reliable_channel.nim +++ b/logos_delivery/channels/reliable_channel.nim @@ -21,19 +21,22 @@ import bearssl/rand import stew/byteutils import libp2p/crypto/crypto as libp2p_crypto -import logos_delivery/waku/api/types -import logos_delivery/messaging/delivery_service/send_service +import logos_delivery/api/types +import logos_delivery/api/reliable_channel_manager_api +import logos_delivery/api/events/messaging_client_events +import logos_delivery/api/messaging_client_api +import logos_delivery/api/events/reliable_channel_manager_events +import logos_delivery/messaging/messaging_client import logos_delivery/waku/waku_core/topics -import ./events import ./segmentation/segmentation import ./scalable_data_sync/scalable_data_sync import ./rate_limit_manager/rate_limit_manager import ./encryption/encryption export - types, send_service, events, segmentation, scalable_data_sync, rate_limit_manager, - encryption + types, reliable_channel_manager_api, segmentation, scalable_data_sync, + rate_limit_manager, encryption const LipWireReliableChannelVersion* = "RELIABLE-CHANNEL-API/1" ## Wire-format spec marker for the Reliable Channel layer, as defined @@ -44,14 +47,6 @@ const LipWireReliableChannelVersion* = "RELIABLE-CHANNEL-API/1" ## on breaking on-the-wire changes; implementations pin one version. type - SendHandler* = proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {. - async: (raises: [CatchableError]), gcsafe - .} - ## Egress dispatch boundary. Typically wraps `MessagingClient.send`; - ## tests inject a fake that records calls and returns canned - ## `RequestId`s so the send state machine can be exercised end-to-end - ## without a network. - MessagePersistence {.pure.} = enum Persistent Ephemeral @@ -90,7 +85,6 @@ type ## Spec-defined public type. Fields are private so callers cannot ## mutate internals and break invariants. Getters are added below ## for the few values consumers may need. - sendHandler: SendHandler channelId: ChannelId contentTopic: ContentTopic senderId: SdsParticipantID @@ -135,7 +129,7 @@ proc tryFinalizeChannelReq(self: ReliableChannel, channelReqId: RequestId) = ## and the total number of confirmed + failed segments equals the total expected segments. ## Therefore, the channel-level request is removed from `self.channelReqs` ## and the appropriate final event is emitted. - ## + ## let state = self.channelReqs.getOrDefault(channelReqId) if state.totalExpectedSegments == 0: ## Either already finalized (and removed) or never inserted. @@ -269,14 +263,7 @@ proc onReadyToSend( meta: LipWireReliableChannelVersion.toBytes(), ) - ## `sendHandler` is not annotated `(raises: [])`, but this listener is. - ## Convert any raise to a Result error so the state machine handles - ## both failure modes (Result.err and exception) through one path. - let sendRes = - try: - await self.sendHandler(envelope) - except CatchableError as e: - Result[RequestId, string].err("messaging send raised: " & e.msg) + let sendRes = await MessagingSend.request(self.brokerCtx, envelope) let messagingReqId = sendRes.valueOr: MessageErrorEvent.emit( @@ -366,14 +353,9 @@ proc dispatchRepair(self: ReliableChannel, wire: seq[byte]) {.async: (raises: [] meta: LipWireReliableChannelVersion.toBytes(), ) - let sendRes = - try: - await self.sendHandler(envelope) - except CatchableError as e: - Result[RequestId, string].err("messaging send raised: " & e.msg) - if sendRes.isErr(): + (await MessagingSend.request(self.brokerCtx, envelope)).isOkOr: debug "SDS repair rebroadcast dropped: dispatch failed", - channelId = self.channelId, error = sendRes.error + channelId = self.channelId, error = error proc onMessageReceived( self: ReliableChannel, messageHash: string, payload: seq[byte] @@ -421,7 +403,6 @@ proc onMessageReceived( proc new*( T: type ReliableChannel, - sendHandler: SendHandler, channelId: ChannelId, contentTopic: ContentTopic, senderId: SdsParticipantID, @@ -436,12 +417,7 @@ proc new*( ## should be wiring up. Encryption is delegated to the `Encrypt`/ ## `Decrypt` request brokers, so the channel keeps no per-instance ## encryption state either. - ## - ## `sendHandler` is the egress dispatch. The owning `ReliableChannelManager` - ## typically constructs it as a closure over `MessagingClient.send`. Tests - ## pass a fake to drive the send state machine without touching the network. let chn = T( - sendHandler: sendHandler, channelId: channelId, contentTopic: contentTopic, senderId: senderId, diff --git a/logos_delivery/channels/reliable_channel_manager.nim b/logos_delivery/channels/reliable_channel_manager.nim index 0717eb3f8..6f8ca14dc 100644 --- a/logos_delivery/channels/reliable_channel_manager.nim +++ b/logos_delivery/channels/reliable_channel_manager.nim @@ -5,7 +5,7 @@ ## ## See: https://lip.logos.co/messaging/raw/reliable-channel-api.html -import std/[options, tables] +import std/tables import results import chronos import chronicles @@ -13,44 +13,28 @@ import stew/byteutils import brokers/broker_context -import logos_delivery/waku/events/message_events as waku_message_events -import logos_delivery/messaging/messaging_client -import logos_delivery/waku/waku_core/topics -import logos_delivery/waku/persistency/sds_persistency +import logos_delivery/api/types +import logos_delivery/api/reliable_channel_manager_api +import logos_delivery/api/conf/channels_conf import ./reliable_channel -import ./encryption/noop_encryption -export reliable_channel +export reliable_channel, channels_conf -const SdsJobId = "sds" - ## One persistency job shared by every channel's SDS state; rows are - ## keyed by channelId. - -type ReliableChannelManager* = ref object - channels: Table[ChannelId, ReliableChannel] - messagingClient: MessagingClient ## Borrowed from the owning `Waku`. - sendHandler: SendHandler - ## Default egress dispatch for channels created through this manager. - ## Constructed at mount time as a closure over `MessagingClient.send` - ## so the channel layer itself stays callable-only. - brokerCtx: BrokerContext +type ReliableChannelManager* = ref object ## Implements `ReliableChannelApi`. + channels*: Table[ChannelId, ReliableChannel] ## read by `channels/api.nim` + conf*: ReliableChannelManagerConf + brokerCtx*: BrokerContext proc new*( T: type ReliableChannelManager, - messagingClient: MessagingClient, - sendHandler: SendHandler, + conf: ReliableChannelManagerConf, brokerCtx: BrokerContext = globalBrokerContext(), ): Result[T, string] = - if messagingClient.isNil(): - return err("messaging client is required") - if sendHandler.isNil(): - return err("sendHandler is required") return ok( T( channels: initTable[ChannelId, ReliableChannel](), - messagingClient: messagingClient, - sendHandler: sendHandler, + conf: conf, brokerCtx: brokerCtx, ) ) @@ -68,96 +52,6 @@ proc stop*(self: ReliableChannelManager) {.async.} = await chn.stop() self.channels.clear() -proc sdsPersistence(): Option[Persistence] = - ## SDS backend from the Persistency singleton; memory-only fallback when - ## it is unavailable (e.g. unit tests). - let p = Persistency.instance().valueOr: - info "SDS persistence disabled, running memory-only", reason = $error - return none(Persistence) - let job = p.openJob(SdsJobId).valueOr: - warn "SDS persistence disabled, could not open persistency job", - jobId = SdsJobId, reason = $error - return none(Persistence) - return some(newSdsPersistence(job)) - -proc createReliableChannel*( - self: ReliableChannelManager, - channelId: ChannelId, - contentTopic: ContentTopic, - senderId: SdsParticipantID, - sendHandler: SendHandler = nil, -): Result[ChannelId, string] = - ## Spec entry point. The `sendHandler` and `rng` the channel needs are - ## sourced from the owning `ReliableChannelManager` rather than passed - ## per call. Encryption is wired up through the `Encrypt`/`Decrypt` - ## request brokers — the application installs its own providers - ## (or `setNoopEncryption()`) before traffic flows. - ## - ## Segmentation, SDS and rate-limit configs will eventually be read - ## from the node's `NodeConfig`. Defaults for now. - ## - ## `sendHandler` defaults to the manager's default (constructed at mount - ## from `MessagingClient.send`); tests pass a fake to bypass the network. - if self.channels.hasKey(channelId): - return err("channel already exists: " & channelId) - - let segConfig = SegmentationConfig( - segmentSizeBytes: DefaultSegmentSizeBytes, - enableReedSolomon: false, - persistence: nil, - ) - let sdsConfig = SdsConfig( - acknowledgementTimeoutMs: DefaultAcknowledgementTimeoutMs, - maxRetransmissions: DefaultMaxRetransmissions, - causalHistorySize: DefaultCausalHistorySize, - persistence: sdsPersistence(), - ) - let rateConfig = RateLimitConfig( - epochPeriodSec: DefaultEpochPeriodSec, messagesPerEpoch: DefaultMessagesPerEpoch - ) - - let effectiveSendHandler = if sendHandler.isNil(): self.sendHandler else: sendHandler - - let chn = ReliableChannel.new( - sendHandler = effectiveSendHandler, - channelId = channelId, - contentTopic = contentTopic, - senderId = senderId, - segConfig = segConfig, - sdsConfig = sdsConfig, - rateConfig = rateConfig, - brokerCtx = self.brokerCtx, - ) - - self.channels[channelId] = chn - return ok(channelId) - -proc closeChannel*( - self: ReliableChannelManager, channelId: ChannelId -): Future[Result[void, string]] {.async: (raises: []).} = - ## Stops the channel's SDS loops and releases the channel. Persisted SDS - ## state survives, so re-creating the channel restores it. - let chn = self.channels.getOrDefault(channelId) - if chn.isNil(): - return err("unknown channel: " & channelId) - self.channels.del(channelId) - await chn.stop() - return ok() - -proc send*( - self: ReliableChannelManager, - channelId: ChannelId, - appPayload: seq[byte], - ephemeral: bool = false, -): Future[Result[RequestId, string]] {.async: (raises: []).} = - ## Spec-level entry point. Looks the channel up by id and delegates - ## to `ReliableChannel.send`, which exposes the visible pipeline - ## segmentation -> sds -> rate_limit_manager -> encryption. - let chn = self.channels.getOrDefault(channelId) - if chn.isNil(): - return err("unknown channel: " & channelId) - return await chn.send(appPayload, ephemeral) - ## Inbound messages are not handed to the manager by direct call. Each ## `ReliableChannel` installs its own `MessageReceivedEvent` listener ## in `ReliableChannel.new`, filters by spec marker and `contentTopic`, diff --git a/logos_delivery/channels/types.nim b/logos_delivery/channels/types.nim index ec663cf7b..7730c5c58 100644 --- a/logos_delivery/channels/types.nim +++ b/logos_delivery/channels/types.nim @@ -1,7 +1,7 @@ ## Core identifier types for the Reliable Channel API. import std/hashes -import logos_delivery/waku/api/types as api_types +import logos_delivery/api/types as api_types import ./scalable_data_sync/scalable_data_sync diff --git a/logos_delivery/logos_delivery.nim b/logos_delivery/logos_delivery.nim new file mode 100644 index 000000000..0e1f77104 --- /dev/null +++ b/logos_delivery/logos_delivery.nim @@ -0,0 +1,210 @@ +## `LogosDelivery` is the project entry point. It is a pure concentrator: it +## owns exactly one instance of each API layer +## +## Waku <- MessagingClient <- ReliableChannelManager +## +## and chains them together (each layer drives the one below it). Every layer +## keeps its own, separate public API — `LogosDelivery` only wires them up and +## drives the shared `new` / `start` / `stop` lifecycle. + +{.push raises: [].} + +import std/options +import results, chronos, chronicles + +# Each layer has a core module (type + new/start/stop) and an api/ folder whose +# modules each implement a differentiated set of operations, plus an events +# surface. The concentrator re-exports them so library consumers get the full +# surface from `import logos_delivery`. (The per-layer `events` modules share a +# stem, so they are imported under aliases.) + +# Waku layer +import logos_delivery/waku/waku +export waku +import + logos_delivery/waku/api/[ + topics, relay, subscriptions, filter, lightpush, store, peer_manager, discovery, + debug, health, ping, + ] +export + topics, relay, subscriptions, filter, lightpush, store, peer_manager, discovery, + debug, health, ping +# Kernel event surface (`MessageSeenEvent`) plus the remaining waku health events. +import logos_delivery/api/events/kernel_events +export kernel_events +import logos_delivery/waku/api/events/health_events +export health_events + +# Messaging layer +import logos_delivery/messaging/[messaging_client, messaging_client_lifecycle] +export messaging_client +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/conf/messaging_conf +export messaging_conf + +# Reliable Channel layer +import logos_delivery/channels/reliable_channel_manager +export reliable_channel_manager +import logos_delivery/channels/api/channel_lifecycle +export channel_lifecycle +import logos_delivery/channels/api/send as channel_send +export channel_send +import logos_delivery/api/events/reliable_channel_manager_events +export reliable_channel_manager_events + +import logos_delivery/api/logos_delivery_api +export logos_delivery_api +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/conf/logos_delivery_conf +export logos_delivery_conf + +logScope: + topics = "logosdelivery" + +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: LogosDeliveryConf, appCallbacks: AppCallbacks = nil +): Future[Result[LogosDelivery, string]] {.async.} = + ## Builds the stack bottom-up from a resolved per-layer config; each layer is + ## mounted iff its config is present. + let wakuConf = WakuNodeConf(conf.kernelConf).toWakuConf().valueOr: + return err("failed to handle the configuration: " & error) + let waku = (await Waku.new(wakuConf, appCallbacks)).valueOr: + return err("failed to create Waku: " & error) + + let messagingClient = + if conf.messagingConf.isSome(): + MessagingClient.new(conf.messagingConf.get(), waku).valueOr: + return err("failed to create MessagingClient: " & error) + else: + nil + + let reliableChannelManager = + if conf.channelsConf.isSome(): + ReliableChannelManager.new(conf.channelsConf.get(), waku.brokerCtx).valueOr: + return err("failed to create ReliableChannelManager: " & error) + else: + nil + + return ok( + 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: KernelConf(conf), + messagingConf: some(MessagingClientConf()), + channelsConf: some(ReliableChannelManagerConf()), + ), + appCallbacks, + ) + +proc new*( + T: type LogosDelivery, kernelConf: KernelConf, appCallbacks: AppCallbacks = nil +): Future[Result[LogosDelivery, string]] {.async.} = + ## Fleet mode: mounts the kernel only from a raw `KernelConf`; no messaging client, + ## no channel manager. + return await LogosDelivery.new(LogosDeliveryConf.init(kernelConf), appCallbacks) + +proc new*( + T: type LogosDelivery, + kernelConf: KernelConf, + messagingOverrides: MessagingClientConf, + channelsOverrides: ReliableChannelManagerConf, + appCallbacks: AppCallbacks = nil, +): Future[Result[LogosDelivery, string]] {.async.} = + ## Full stack: kernel + messaging + channels. Messaging is never skipped; a + ## kernel-only node uses `new(kernelConf)` instead. + return await LogosDelivery.new( + LogosDeliveryConf( + kernelConf: kernelConf, + messagingConf: some(messagingOverrides), + channelsConf: some(channelsOverrides), + ), + appCallbacks, + ) + +proc new*( + T: type LogosDelivery, + mode: LogosDeliveryMode = LogosDeliveryMode.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 present layer bottom-up: transport, then messaging, then channels. + if self.waku.isNil(): + return err("Waku node is not initialized") + + (await self.waku.start()).isOkOr: + return err("failed to start Waku: " & error) + + if not self.messagingClient.isNil(): + self.messagingClient.start().isOkOr: + return err("failed to start MessagingClient: " & error) + + if not self.reliableChannelManager.isNil(): + self.reliableChannelManager.start().isOkOr: + return err("failed to start ReliableChannelManager: " & error) + + return ok() + +proc stop*(self: LogosDelivery): Future[Result[void, string]] {.async.} = + ## Stops in reverse order so higher layers drain before their dependencies. + 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() + +proc isOnline*(self: LogosDelivery): Future[Result[bool, string]] {.async.} = + if self.waku.isNil(): + return err("Waku node is not initialized") + return await self.waku.isOnline() + +proc ensureMessaging*(self: LogosDelivery): Result[void, string] = + ## Fails if the node has no messaging client (a kernel-only / fleet node). + if self.isNil() or self.messagingClient.isNil(): + return err("node has no messaging client (kernel-only/fleet node)") + ok() + +proc ensureChannels*(self: LogosDelivery): Result[void, string] = + ## Fails if the node has no reliable channel manager (a kernel-only / fleet node). + if self.isNil() or self.reliableChannelManager.isNil(): + return err("node has no reliable channel manager (kernel-only/fleet node)") + ok() + +# Compile-time check that each concrete type satisfies its API concept. +static: + doAssert Waku is KernelApi + doAssert MessagingClient is MessagingApi + doAssert ReliableChannelManager is ReliableChannelApi + doAssert LogosDelivery is LogosDeliveryApi diff --git a/logos_delivery/messaging/api/send.nim b/logos_delivery/messaging/api/send.nim new file mode 100644 index 000000000..9a01c5f35 --- /dev/null +++ b/logos_delivery/messaging/api/send.nim @@ -0,0 +1,35 @@ +## Messaging layer API — send operation. +import results, chronos, chronicles + +import logos_delivery/api/types +import logos_delivery/api/events/messaging_client_events +import logos_delivery/messaging/messaging_client +import logos_delivery/waku/waku +import logos_delivery/waku/api/subscriptions +import logos_delivery/messaging/delivery_service/send_service +import logos_delivery/messaging/delivery_service/send_service/delivery_task + +proc send*( + self: MessagingClient, envelope: MessageEnvelope +): Future[Result[RequestId, string]] {.async.} = + ## High-level messaging API send. Auto-subscribes to the content topic + ## (so the local node sees its own gossipsub broadcast), builds a + ## `DeliveryTask`, and hands it to the send service. Returns the request + ## id the caller can correlate with `MessageSentEvent` / `MessageErrorEvent`. + ?self.checkApiAvailability() + + let isSubbed = self.waku.isSubscribed(envelope.contentTopic).valueOr(false) + if not isSubbed: + info "Auto-subscribing to topic on send", contentTopic = envelope.contentTopic + self.waku.subscribe(envelope.contentTopic).isOkOr: + warn "Failed to auto-subscribe", error = error + return err("Failed to auto-subscribe before sending: " & error) + + let requestId = RequestId.new(self.waku.rng) + + let deliveryTask = DeliveryTask.new(requestId, envelope, self.waku.brokerCtx).valueOr: + return err("MessagingClient.send: Failed to create delivery task: " & error) + + asyncSpawn self.sendService.send(deliveryTask) + + return ok(requestId) diff --git a/logos_delivery/messaging/api/subscription.nim b/logos_delivery/messaging/api/subscription.nim new file mode 100644 index 000000000..8893af7f1 --- /dev/null +++ b/logos_delivery/messaging/api/subscription.nim @@ -0,0 +1,19 @@ +## Messaging layer API — subscription operations. +import results, chronos + +import logos_delivery/api/types +import logos_delivery/messaging/messaging_client +import logos_delivery/waku/waku +import logos_delivery/waku/api/subscriptions + +proc subscribe*( + self: MessagingClient, contentTopic: ContentTopic +): Future[Result[void, string]] {.async.} = + ?self.checkApiAvailability() + return self.waku.subscribe(contentTopic) + +proc unsubscribe*( + self: MessagingClient, contentTopic: ContentTopic +): Result[void, string] = + ?self.checkApiAvailability() + return self.waku.unsubscribe(contentTopic) diff --git a/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim b/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim index 90bdb0839..d2c369d46 100644 --- a/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim +++ b/logos_delivery/messaging/delivery_service/recv_service/recv_service.nim @@ -7,17 +7,13 @@ import std/[tables, sequtils, options, sets] import chronos, chronicles, libp2p/utility import brokers/broker_context import - logos_delivery/waku/[ - waku_core, - waku_core/topics, - waku_store/client, - waku_store/common, - waku_filter_v2/client, - events/message_events, - events/health_events, - waku_node, - node/subscription_manager, - ] + logos_delivery/waku/[waku_core, waku_core/topics, waku_store/common], + logos_delivery/waku/waku, + logos_delivery/waku/api/[store, subscriptions] +import + logos_delivery/api/events/kernel_events, + # MessageSeenEvent + EventConnectionStatusChange + logos_delivery/api/events/messaging_client_events # MessageReceivedEvent const MaxMessageLife = chronos.minutes(7) ## Max time we will keep track of rx messages @@ -36,7 +32,7 @@ type RecvMessage = object type RecvService* = ref object of RootObj brokerCtx: BrokerContext - node: WakuNode + waku: Waku seenMsgListener: MessageSeenEventListener connStatusListener: EventConnectionStatusChangeListener @@ -58,7 +54,7 @@ proc getMissingMsgsFromStore( self: RecvService, msgHashes: seq[WakuMessageHash] ): Future[Result[seq[TupleHashAndMsg], string]] {.async.} = let storeResp: StoreQueryResponse = ( - await self.node.wakuStoreClient.queryToAny( + await self.waku.storeQueryToAny( StoreQueryRequest(includeData: true, messageHashes: msgHashes) ) ).valueOr: @@ -83,9 +79,7 @@ proc processIncomingMessage( ## or if the message is a duplicate (recently-seen). Otherwise, save it as ## recently-seen, emit a MessageReceivedEvent, and return true. - if not self.node.subscriptionManager.isContentSubscribed( - pubsubTopic, message.contentTopic - ): + if not self.waku.isContentSubscribed(pubsubTopic, message.contentTopic): trace "skipping message as I am not subscribed", shard = pubsubTopic, contentTopic = message.contentTopic return false @@ -106,16 +100,16 @@ proc processIncomingMessage( proc checkStore*(self: RecvService) {.async.} = ## Checks the store for messages that were not received directly and ## delivers them via MessageReceivedEvent. - if self.node.wakuStoreClient.isNil(): + if not self.waku.isStoreMounted(): error "recv service has no store client mounted, skipping store check" return self.endTimeToCheck = getNowInNanosecondTime() ## query store and deliver new recovered messages per subscribed topic - for pubsubTopic, contentTopics in self.node.subscriptionManager.subscribedContentTopics: + for (pubsubTopic, contentTopics) in self.waku.subscribedContentTopics(): let storeResp: StoreQueryResponse = ( - await self.node.wakuStoreClient.queryToAny( + await self.waku.storeQueryToAny( StoreQueryRequest( includeData: false, pubsubTopic: some(pubsubTopic), @@ -169,14 +163,14 @@ proc onConnectionStatusChange(self: RecvService, status: ConnectionStatus) = info "recv service backfilling missed messages after coming back online" self.backfillHandler = self.checkStore() -proc new*(T: typedesc[RecvService], node: WakuNode): T = +proc new*(T: typedesc[RecvService], waku: Waku): T = ## The storeClient will help to acquire any possible missed messages let now = getNowInNanosecondTime() var recvService = RecvService( - node: node, + waku: waku, startTimeToCheck: now, - brokerCtx: node.brokerCtx, + brokerCtx: waku.brokerCtx, recentReceivedMsgs: @[], ) diff --git a/logos_delivery/messaging/delivery_service/send_service/delivery_task.nim b/logos_delivery/messaging/delivery_service/send_service/delivery_task.nim index 1e38fcee1..1f76078fe 100644 --- a/logos_delivery/messaging/delivery_service/send_service/delivery_task.nim +++ b/logos_delivery/messaging/delivery_service/send_service/delivery_task.nim @@ -3,7 +3,7 @@ import std/[options, times], chronos import brokers/broker_context import logos_delivery/waku/waku_core, - logos_delivery/waku/api/types, + logos_delivery/api/types, logos_delivery/waku/requests/node_requests type DeliveryState* {.pure.} = enum @@ -24,6 +24,9 @@ type DeliveryTask* = ref object tryCount*: int state*: DeliveryState deliveryTime*: Moment + firstPropagatedTime*: Option[Moment] + ## Set once on the first successful propagation; never reset on re-publish. + ## Anchors the store-validation time cap (see propagationAge). propagateEventEmitted*: bool errorDesc*: string @@ -74,5 +77,13 @@ proc deliveryAge*(self: DeliveryTask): timer.Duration = else: ZeroDuration +proc propagationAge*(self: DeliveryTask): timer.Duration = + ## Time elapsed since the message was first successfully propagated. + ## Stable across re-publishes; ZeroDuration until first propagation. + if self.firstPropagatedTime.isSome(): + timer.Moment.now() - self.firstPropagatedTime.get() + else: + ZeroDuration + proc isEphemeral*(self: DeliveryTask): bool = return self.msg.ephemeral diff --git a/logos_delivery/messaging/delivery_service/send_service/lightpush_processor.nim b/logos_delivery/messaging/delivery_service/send_service/lightpush_processor.nim index 4105ba48d..fa199774b 100644 --- a/logos_delivery/messaging/delivery_service/send_service/lightpush_processor.nim +++ b/logos_delivery/messaging/delivery_service/send_service/lightpush_processor.nim @@ -2,10 +2,8 @@ import logos_delivery/waku/compat/option_valueor import chronicles, chronos, results import std/options import brokers/broker_context -import - logos_delivery/waku/node/peer_manager, - logos_delivery/waku/waku_core, - logos_delivery/waku/waku_lightpush/[common, client, rpc] +import logos_delivery/waku/waku_core, logos_delivery/waku/waku +import logos_delivery/waku/api/publish import ./[delivery_task, send_processor] @@ -13,27 +11,17 @@ logScope: topics = "send service lightpush processor" type LightpushSendProcessor* = ref object of BaseSendProcessor - peerManager: PeerManager - lightpushClient: WakuLightPushClient + waku: Waku proc new*( - T: typedesc[LightpushSendProcessor], - peerManager: PeerManager, - lightpushClient: WakuLightPushClient, - brokerCtx: BrokerContext, + T: typedesc[LightpushSendProcessor], waku: Waku, brokerCtx: BrokerContext ): T = - return - T(peerManager: peerManager, lightpushClient: lightpushClient, brokerCtx: brokerCtx) - -proc isLightpushPeerAvailable( - self: LightpushSendProcessor, pubsubTopic: PubsubTopic -): bool = - return self.peerManager.selectPeer(WakuLightPushCodec, some(pubsubTopic)).isSome() + return T(waku: waku, brokerCtx: brokerCtx) method isValidProcessor*( self: LightpushSendProcessor, task: DeliveryTask ): bool {.gcsafe.} = - return self.isLightpushPeerAvailable(task.pubsubTopic) + return self.waku.lightpushPeerAvailable(task.pubsubTopic) method sendImpl*( self: LightpushSendProcessor, task: DeliveryTask @@ -44,14 +32,8 @@ method sendImpl*( msgHash = task.msgHash.to0xHex(), tryCount = task.tryCount - let peer = self.peerManager.selectPeer(WakuLightPushCodec, some(task.pubsubTopic)).valueOr: - debug "No peer available for Lightpush, request pushed back for next round", - requestId = task.requestId - task.state = DeliveryState.NextRoundRetry - return - let numLightpushServers = ( - await self.lightpushClient.publish(some(task.pubsubTopic), task.msg, peer) + await self.waku.lightpushPublishToAny(task.pubsubTopic, task.msg) ).valueOr: error "LightpushSendProcessor.sendImpl failed", error = error.desc.get($error.code) case error.code @@ -71,6 +53,8 @@ method sendImpl*( requestId = task.requestId, msgHash = task.msgHash.to0xHex() task.state = DeliveryState.SuccessfullyPropagated task.deliveryTime = Moment.now() + if task.firstPropagatedTime.isNone(): + task.firstPropagatedTime = some(Moment.now()) # TODO: with a simple retry processor it might be more accurate to say `Sent` else: # Controversial state, publish says ok but no peer. It should not happen. diff --git a/logos_delivery/messaging/delivery_service/send_service/relay_processor.nim b/logos_delivery/messaging/delivery_service/send_service/relay_processor.nim index 23b5b846c..dc40b797d 100644 --- a/logos_delivery/messaging/delivery_service/send_service/relay_processor.nim +++ b/logos_delivery/messaging/delivery_service/send_service/relay_processor.nim @@ -4,7 +4,7 @@ import chronos, chronicles import brokers/broker_context import logos_delivery/waku/[waku_core], logos_delivery/waku/waku_lightpush/[common, rpc] import logos_delivery/waku/requests/health_requests -import logos_delivery/waku/api/types +import logos_delivery/api/types import ./[delivery_task, send_processor] logScope: @@ -76,6 +76,8 @@ method sendImpl*(self: RelaySendProcessor, task: DeliveryTask) {.async.} = noOfPeers = noOfPublishedPeers task.state = DeliveryState.SuccessfullyPropagated task.deliveryTime = Moment.now() + if task.firstPropagatedTime.isNone(): + task.firstPropagatedTime = some(Moment.now()) else: # It shall not happen, but still covering it task.state = self.fallbackStateToSet diff --git a/logos_delivery/messaging/delivery_service/send_service/send_processor.nim b/logos_delivery/messaging/delivery_service/send_service/send_processor.nim index 3782b9d4e..ae6d7141d 100644 --- a/logos_delivery/messaging/delivery_service/send_service/send_processor.nim +++ b/logos_delivery/messaging/delivery_service/send_service/send_processor.nim @@ -32,5 +32,10 @@ method process*( currentProcessor = currentProcessor.fallbackProcessor keepTrying = task.state == DeliveryState.FallbackRetry - if task.state == DeliveryState.FallbackRetry: + # A task still in `FallbackRetry` exhausted the chain without delivering, and + # one still in `Entry` was never attempted because no processor had a usable + # peer yet (e.g. a lightpush peer that finishes registering right after the + # first send). Both must be queued for the next round so the service loop + # retries them; otherwise the task would sit untouched until it ages out. + if task.state == DeliveryState.FallbackRetry or task.state == DeliveryState.Entry: task.state = DeliveryState.NextRoundRetry diff --git a/logos_delivery/messaging/delivery_service/send_service/send_service.nim b/logos_delivery/messaging/delivery_service/send_service/send_service.nim index 92026be5d..037ad879b 100644 --- a/logos_delivery/messaging/delivery_service/send_service/send_service.nim +++ b/logos_delivery/messaging/delivery_service/send_service/send_service.nim @@ -7,19 +7,10 @@ import chronos, chronicles, libp2p/utility import brokers/broker_context import ./[send_processor, relay_processor, lightpush_processor, delivery_task], - logos_delivery/waku/[ - waku_core, - node/waku_node, - node/subscription_manager, - node/peer_manager, - waku_store/client, - waku_store/common, - waku_relay/protocol, - waku_rln_relay/rln_relay, - waku_lightpush/client, - waku_lightpush/callbacks, - events/message_events, - ] + logos_delivery/waku/[waku_core, waku_store/common], + logos_delivery/waku/waku, + logos_delivery/waku/api/[store, subscriptions, publish] +import logos_delivery/api/events/messaging_client_events logScope: topics = "send service" @@ -57,18 +48,15 @@ type SendService* = ref object of RootObj serviceLoopHandle: Future[void] ## handle that allows to stop the async task sendProcessor: BaseSendProcessor - node: WakuNode + waku: Waku checkStoreForMessages: bool + lastStoreCheckTime: Moment ## throttles store validation queries to ArchiveTime cadence proc setupSendProcessorChain( - peerManager: PeerManager, - lightpushClient: WakuLightPushClient, - relay: WakuRelay, - rlnRelay: WakuRLNRelay, - brokerCtx: BrokerContext, + waku: Waku, brokerCtx: BrokerContext ): Result[BaseSendProcessor, string] = - let isRelayAvail = not relay.isNil() - let isLightPushAvail = not lightpushClient.isNil() + let isRelayAvail = waku.hasRelay() + let isLightPushAvail = waku.hasLightpush() if not isRelayAvail and not isLightPushAvail: return err("No valid send processor found for the delivery task") @@ -76,16 +64,10 @@ proc setupSendProcessorChain( var processors = newSeq[BaseSendProcessor]() if isRelayAvail: - let rln: Option[WakuRLNRelay] = - if rlnRelay.isNil(): - none[WakuRLNRelay]() - else: - some(rlnRelay) - let publishProc = getRelayPushHandler(relay, rln) - + let publishProc = waku.relayPushHandler() processors.add(RelaySendProcessor.new(isLightPushAvail, publishProc, brokerCtx)) if isLightPushAvail: - processors.add(LightpushSendProcessor.new(peerManager, lightpushClient, brokerCtx)) + processors.add(LightpushSendProcessor.new(waku, brokerCtx)) var currentProcessor: BaseSendProcessor = processors[0] for i in 1 ..< processors.len: @@ -96,27 +78,26 @@ proc setupSendProcessorChain( return ok(processors[0]) proc new*( - T: typedesc[SendService], preferP2PReliability: bool, w: WakuNode + T: typedesc[SendService], preferP2PReliability: bool, waku: Waku ): Result[T, string] = - if w.wakuRelay.isNil() and w.wakuLightpushClient.isNil(): + if not waku.hasRelay() and not waku.hasLightpush(): return err( "Could not create SendService. wakuRelay or wakuLightpushClient should be set" ) - let checkStoreForMessages = preferP2PReliability and not w.wakuStoreClient.isNil() + let checkStoreForMessages = preferP2PReliability and waku.isStoreMounted() - let sendProcessorChain = setupSendProcessorChain( - w.peerManager, w.wakuLightPushClient, w.wakuRelay, w.wakuRlnRelay, w.brokerCtx - ).valueOr: + let sendProcessorChain = setupSendProcessorChain(waku, waku.brokerCtx).valueOr: return err("failed to setup SendProcessorChain: " & $error) let sendService = SendService( - brokerCtx: w.brokerCtx, + brokerCtx: waku.brokerCtx, taskCache: newSeq[DeliveryTask](), serviceLoopHandle: nil, sendProcessor: sendProcessorChain, - node: w, + waku: waku, checkStoreForMessages: checkStoreForMessages, + lastStoreCheckTime: Moment.now(), ) return ok(sendService) @@ -125,7 +106,7 @@ proc addTask(self: SendService, task: DeliveryTask) = self.taskCache.addUnique(task) proc isStorePeerAvailable*(sendService: SendService): bool = - return sendService.node.peerManager.selectPeer(WakuStoreCodec).isSome() + return sendService.waku.hasStorePeer() proc checkMsgsInStore(self: SendService, tasksToValidate: seq[DeliveryTask]) {.async.} = if tasksToValidate.len() == 0: @@ -140,7 +121,7 @@ proc checkMsgsInStore(self: SendService, tasksToValidate: seq[DeliveryTask]) {.a # TODO: confirm hash format for store query!!! let storeResp: StoreQueryResponse = ( - await self.node.wakuStoreClient.queryToAny( + await self.waku.storeQueryToAny( StoreQueryRequest(includeData: false, messageHashes: hashesToValidate) ) ).valueOr: @@ -163,11 +144,20 @@ proc checkStoredMessages(self: SendService) {.async.} = if not self.checkStoreForMessages: return + # Throttle store queries so they run at most every ArchiveTime (3s), regardless + # of the 1s service loop cadence. + if Moment.now() - self.lastStoreCheckTime < ArchiveTime: + return + let tasksToValidate = self.taskCache.filterIt( - it.state == DeliveryState.SuccessfullyPropagated and it.deliveryAge() > ArchiveTime and - not it.isEphemeral() + it.state == DeliveryState.SuccessfullyPropagated and + it.propagationAge() > ArchiveTime and not it.isEphemeral() ) + if tasksToValidate.len() == 0: + return + + self.lastStoreCheckTime = Moment.now() await self.checkMsgsInStore(tasksToValidate) proc reportTaskResult(self: SendService, task: DeliveryTask) = @@ -200,7 +190,10 @@ proc reportTaskResult(self: SendService, task: DeliveryTask) = # rest of the states are intermediate and does not translate to event discard - if task.messageAge() > MaxTimeInCache: + # Only tasks that never propagated are reported as hard send failures here. + # Propagated-but-not-store-validated tasks are handled (warn + drop, no event) + # in evaluateAndCleanUp. + if task.firstPropagatedTime.isNone() and task.messageAge() > MaxTimeInCache: error "Failed to send message", requestId = task.requestId, msgHash = task.msgHash.to0xHex(), @@ -229,6 +222,25 @@ proc evaluateAndCleanUp(self: SendService) = ) ) + # Store validation timed out: the message was propagated but never confirmed in a + # store node within MaxTimeInCache (measured from first propagation). Warn and drop + # without emitting an app event. + for task in self.taskCache: + if task.firstPropagatedTime.isSome() and + task.state != DeliveryState.SuccessfullyValidated and + task.propagationAge() > MaxTimeInCache: + warn "Message propagated but not validated by a store node within time window; stop trying.", + requestId = task.requestId, + msgHash = task.msgHash.to0xHex(), + propagationAge = task.propagationAge() + + self.taskCache.keepItIf( + not ( + it.firstPropagatedTime.isSome() and it.state != DeliveryState.SuccessfullyValidated and + it.propagationAge() > MaxTimeInCache + ) + ) + proc trySendMessages(self: SendService) {.async.} = let tasksToSend = self.taskCache.filterIt(it.state == DeliveryState.NextRoundRetry) @@ -259,7 +271,7 @@ proc send*(self: SendService, task: DeliveryTask) {.async.} = info "SendService.send: processing delivery task", requestId = task.requestId, msgHash = task.msgHash.to0xHex() - self.node.subscriptionManager.subscribe(task.msg.contentTopic).isOkOr: + self.waku.subscribe(task.msg.contentTopic).isOkOr: error "SendService.send: failed to subscribe to content topic", contentTopic = task.msg.contentTopic, error = error diff --git a/logos_delivery/messaging/messaging_client.nim b/logos_delivery/messaging/messaging_client.nim index 1d3892250..1044122d3 100644 --- a/logos_delivery/messaging/messaging_client.nim +++ b/logos_delivery/messaging/messaging_client.nim @@ -1,59 +1,44 @@ -import results, chronos -import chronicles +## 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 +import results, chronos, chronicles import - logos_delivery/waku/api/types, - logos_delivery/waku/node/[waku_node, subscription_manager], - logos_delivery/messaging/delivery_service/[recv_service, send_service], - logos_delivery/messaging/delivery_service/send_service/delivery_task + logos_delivery/api/conf/messaging_conf, + 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] + +export messaging_client_api, messaging_conf type MessagingClient* = ref object - node: WakuNode + brokerCtx*: BrokerContext + waku*: Waku ## The Waku kernel this layer drives; read by `messaging/api/*`. sendService*: SendService recvService*: RecvService - started: bool + started*: bool proc new*( - T: type MessagingClient, useP2PReliability: bool, node: WakuNode + T: type MessagingClient, conf: MessagingClientConf, waku: Waku ): Result[T, string] = - let sendService = ?SendService.new(useP2PReliability, node) - let recvService = RecvService.new(node) - ok(T(node: node, sendService: sendService, recvService: recvService)) + ## The messaging layer chains onto Waku: it drives the underlying Waku kernel + ## for transport while exposing its own send/recv API. + let reliability = conf.reliabilityEnabled.get(DefaultP2pReliability) + let sendService = ?SendService.new(reliability, waku) + let recvService = RecvService.new(waku) + return ok( + T( + waku: waku, + sendService: sendService, + recvService: recvService, + brokerCtx: waku.brokerCtx, + ) + ) -proc start*(self: MessagingClient): Result[void, string] = - if self.started: - return ok() - self.recvService.startRecvService() - self.sendService.startSendService() - self.started = true - ok() +proc checkApiAvailability*(self: MessagingClient): Result[void, string] = + ## Shared guard for the api operation module. + if self.isNil(): + return err("MessagingClient is not initialized") -proc stop*(self: MessagingClient) {.async.} = - if not self.started: - return - await self.sendService.stopSendService() - await self.recvService.stopRecvService() - self.started = false - -proc send*( - self: MessagingClient, envelope: MessageEnvelope -): Future[Result[RequestId, string]] {.async.} = - ## High-level messaging API send. Auto-subscribes to the content topic - ## (so the local node sees its own gossipsub broadcast), builds a - ## `DeliveryTask`, and hands it to the send service. Returns the request - ## id the caller can correlate with `MessageSentEvent` / `MessageErrorEvent`. - let isSubbed = - self.node.subscriptionManager.isSubscribed(envelope.contentTopic).valueOr(false) - if not isSubbed: - info "Auto-subscribing to topic on send", contentTopic = envelope.contentTopic - self.node.subscriptionManager.subscribe(envelope.contentTopic).isOkOr: - warn "Failed to auto-subscribe", error = error - return err("Failed to auto-subscribe before sending: " & error) - - let requestId = RequestId.new(self.node.rng) - - let deliveryTask = DeliveryTask.new(requestId, envelope, self.node.brokerCtx).valueOr: - return err("MessagingClient.send: Failed to create delivery task: " & error) - - asyncSpawn self.sendService.send(deliveryTask) - - return ok(requestId) + return ok() diff --git a/logos_delivery/messaging/messaging_client_lifecycle.nim b/logos_delivery/messaging/messaging_client_lifecycle.nim new file mode 100644 index 000000000..52ea295aa --- /dev/null +++ b/logos_delivery/messaging/messaging_client_lifecycle.nim @@ -0,0 +1,37 @@ +## Messaging layer core: the `MessagingClient` type plus its construction and +## lifecycle. The public operations (subscribe / unsubscribe / send) live in +## `messaging/api.nim`. +import results, chronos +import chronicles +import + logos_delivery/messaging/messaging_client, + logos_delivery/messaging/api/send, + logos_delivery/api/messaging_client_api, + logos_delivery/waku/waku, + logos_delivery/messaging/delivery_service/[recv_service, send_service] + +# Surfaces the messaging API interface (and its Message* events) to consumers. +export messaging_client + +proc start*(self: MessagingClient): Result[void, string] = + if self.started: + return ok() + self.recvService.startRecvService() + self.sendService.startSendService() + + ?MessagingSend.setProvider( + self.brokerCtx, + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + return await self.send(envelope), + ) + + self.started = true + ok() + +proc stop*(self: MessagingClient) {.async.} = + if not self.started: + return + MessagingSend.clearProvider(self.brokerCtx) + await self.sendService.stopSendService() + await self.recvService.stopRecvService() + self.started = false diff --git a/logos_delivery/waku/README.md b/logos_delivery/waku/README.md index d9f160cb5..73af43fe4 100644 --- a/logos_delivery/waku/README.md +++ b/logos_delivery/waku/README.md @@ -232,4 +232,20 @@ However, they can be used for local testing purposes: mkdir -p ./ssl_dir/ openssl req -x509 -newkey rsa:4096 -keyout ./ssl_dir/key.pem -out ./ssl_dir/cert.pem -sha256 -nodes wakunode2 --websocket-secure-support=true --websocket-secure-key-path="./ssl_dir/key.pem" --websocket-secure-cert-path="./ssl_dir/cert.pem" +``` + +## Enabling QUIC + +QUIC is a UDP-based transport that peers can use to connect to your node. + +The default port for QUIC is 60000. + +```shell +wakunode2 --quic-support=true +``` + +To listen on a different UDP port, use `--quic-port`: + +```shell +wakunode2 --quic-support=true --quic-port= ``` \ No newline at end of file diff --git a/logos_delivery/waku/api.nim b/logos_delivery/waku/api.nim deleted file mode 100644 index a977a062a..000000000 --- a/logos_delivery/waku/api.nim +++ /dev/null @@ -1,5 +0,0 @@ -import ./api/[api, api_conf] -import ./events/message_events -import tools/confutils/entry_nodes - -export api, api_conf, entry_nodes, message_events diff --git a/logos_delivery/waku/api/api.nim b/logos_delivery/waku/api/api.nim deleted file mode 100644 index 5be1e2086..000000000 --- a/logos_delivery/waku/api/api.nim +++ /dev/null @@ -1,56 +0,0 @@ -import logos_delivery/waku/compat/option_valueor -import std/[net, options] - -import chronicles, chronos, libp2p/peerid, results - -import logos_delivery/waku/factory/waku -import logos_delivery/messaging/messaging_client -import logos_delivery/waku/[requests/health_requests, waku_core, waku_node] -import logos_delivery/messaging/delivery_service/send_service -import logos_delivery/waku/node/subscription_manager -import libp2p/peerid -import tools/confutils/cli_args -import ./[api_conf, types] - -export cli_args - -logScope: - topics = "api" - -proc createNode*(conf: WakuNodeConf): Future[Result[Waku, string]] {.async.} = - let wakuConf = conf.toWakuConf().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) - -proc checkApiAvailability(w: Waku): Result[void, string] = - if w.isNil(): - return err("Waku node is not initialized") - - # TODO: Conciliate request-bouncing health checks here with unit testing. - # (For now, better to just allow all sends and rely on retries.) - - return ok() - -proc subscribe*( - w: Waku, contentTopic: ContentTopic -): Future[Result[void, string]] {.async.} = - ?checkApiAvailability(w) - - return w.node.subscriptionManager.subscribe(contentTopic) - -proc unsubscribe*(w: Waku, contentTopic: ContentTopic): Result[void, string] = - ?checkApiAvailability(w) - - return w.node.subscriptionManager.unsubscribe(contentTopic) - -proc send*( - w: Waku, envelope: MessageEnvelope -): Future[Result[RequestId, string]] {.async.} = - ?checkApiAvailability(w) - return await w.messagingClient.send(envelope) diff --git a/logos_delivery/waku/api/api_conf.nim b/logos_delivery/waku/api/api_conf.nim deleted file mode 100644 index 5810b1eb2..000000000 --- a/logos_delivery/waku/api/api_conf.nim +++ /dev/null @@ -1,534 +0,0 @@ -import logos_delivery/waku/compat/option_valueor -import std/[net, options] - -import results -import json_serialization, json_serialization/std/options as json_options - -import - logos_delivery/waku/common/utils/parse_size_units, - logos_delivery/waku/common/logging, - logos_delivery/waku/factory/waku_conf, - logos_delivery/waku/factory/conf_builder/conf_builder, - logos_delivery/waku/factory/networks_config, - tools/confutils/entry_nodes - -export json_serialization, json_options - -type AutoShardingConfig* = object - numShardsInCluster*: uint16 - -type RlnConfig* = object - contractAddress*: string - chainId*: uint - epochSizeSec*: uint64 - -type NetworkingConfig* = object - listenIpv4*: string - p2pTcpPort*: uint16 - discv5UdpPort*: uint16 - -type MessageValidation* = object - maxMessageSize*: string # Accepts formats like "150 KiB", "1500 B" - rlnConfig*: Option[RlnConfig] - -type ProtocolsConfig* = object - entryNodes: seq[string] - staticStoreNodes: seq[string] - clusterId: uint16 - autoShardingConfig: AutoShardingConfig - messageValidation: MessageValidation - -const DefaultNetworkingConfig* = - NetworkingConfig(listenIpv4: "0.0.0.0", p2pTcpPort: 60000, discv5UdpPort: 9000) - -const DefaultAutoShardingConfig* = AutoShardingConfig(numShardsInCluster: 1) - -const DefaultMessageValidation* = - MessageValidation(maxMessageSize: "150 KiB", rlnConfig: none(RlnConfig)) - -proc init*( - T: typedesc[ProtocolsConfig], - entryNodes: seq[string], - staticStoreNodes: seq[string] = @[], - clusterId: uint16, - autoShardingConfig: AutoShardingConfig = DefaultAutoShardingConfig, - messageValidation: MessageValidation = DefaultMessageValidation, -): T = - return T( - entryNodes: entryNodes, - staticStoreNodes: staticStoreNodes, - clusterId: clusterId, - autoShardingConfig: autoShardingConfig, - messageValidation: messageValidation, - ) - -const TheWakuNetworkPreset* = ProtocolsConfig( - entryNodes: @[ - "enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im" - ], - staticStoreNodes: @[], - clusterId: 1, - autoShardingConfig: AutoShardingConfig(numShardsInCluster: 8), - messageValidation: MessageValidation( - maxMessageSize: "150 KiB", - rlnConfig: some( - RlnConfig( - contractAddress: "0xB9cd878C90E49F797B4431fBF4fb333108CB90e6", - chainId: 59141, - epochSizeSec: 600, # 10 minutes - ) - ), - ), -) - -type WakuMode* {.pure.} = enum - Edge - Core - -type NodeConfig* {. - requiresInit, deprecated: "Use WakuNodeConf from tools/confutils/cli_args instead" -.} = object - mode: WakuMode - protocolsConfig: ProtocolsConfig - networkingConfig: NetworkingConfig - ethRpcEndpoints: seq[string] - p2pReliability: bool - logLevel: LogLevel - logFormat: LogFormat - -proc init*( - T: typedesc[NodeConfig], - mode: WakuMode = WakuMode.Core, - protocolsConfig: ProtocolsConfig = TheWakuNetworkPreset, - networkingConfig: NetworkingConfig = DefaultNetworkingConfig, - ethRpcEndpoints: seq[string] = @[], - p2pReliability: bool = false, - logLevel: LogLevel = LogLevel.INFO, - logFormat: LogFormat = LogFormat.TEXT, -): T = - return T( - mode: mode, - protocolsConfig: protocolsConfig, - networkingConfig: networkingConfig, - ethRpcEndpoints: ethRpcEndpoints, - p2pReliability: p2pReliability, - logLevel: logLevel, - logFormat: logFormat, - ) - -# -- Getters for ProtocolsConfig (private fields) - used for testing -- - -proc entryNodes*(c: ProtocolsConfig): seq[string] = - c.entryNodes - -proc staticStoreNodes*(c: ProtocolsConfig): seq[string] = - c.staticStoreNodes - -proc clusterId*(c: ProtocolsConfig): uint16 = - c.clusterId - -proc autoShardingConfig*(c: ProtocolsConfig): AutoShardingConfig = - c.autoShardingConfig - -proc messageValidation*(c: ProtocolsConfig): MessageValidation = - c.messageValidation - -# -- Getters for NodeConfig (private fields) - used for testing -- - -proc mode*(c: NodeConfig): WakuMode = - c.mode - -proc protocolsConfig*(c: NodeConfig): ProtocolsConfig = - c.protocolsConfig - -proc networkingConfig*(c: NodeConfig): NetworkingConfig = - c.networkingConfig - -proc ethRpcEndpoints*(c: NodeConfig): seq[string] = - c.ethRpcEndpoints - -proc p2pReliability*(c: NodeConfig): bool = - c.p2pReliability - -proc logLevel*(c: NodeConfig): LogLevel = - c.logLevel - -proc logFormat*(c: NodeConfig): LogFormat = - c.logFormat - -proc toWakuConf*( - nodeConfig: NodeConfig -): Result[WakuConf, string] {.deprecated: "Use WakuNodeConf.toWakuConf instead".} = - var b = WakuConfBuilder.init() - - # Apply log configuration - b.withLogLevel(nodeConfig.logLevel) - b.withLogFormat(nodeConfig.logFormat) - - # Apply networking configuration - let networkingConfig = nodeConfig.networkingConfig - let ip = parseIpAddress(networkingConfig.listenIpv4) - - b.withP2pListenAddress(ip) - b.withP2pTcpPort(networkingConfig.p2pTcpPort) - b.discv5Conf.withUdpPort(networkingConfig.discv5UdpPort) - - case nodeConfig.mode - of Core: - b.withRelay(true) - - # Metadata is always mounted - - b.filterServiceConf.withEnabled(true) - b.filterServiceConf.withMaxPeersToServe(20) - - b.withLightPush(true) - - b.discv5Conf.withEnabled(true) - b.withPeerExchange(true) - b.withRendezvous(true) - - # TODO: fix store as client usage - - b.rateLimitConf.withRateLimits(@["filter:100/1s", "lightpush:5/1s", "px:5/1s"]) - of Edge: - # All client side protocols are mounted by default - # Peer exchange client is always enabled and start_node will start the px loop - # Metadata is always mounted - b.withPeerExchange(true) - # switch off all service side protocols and relay - b.withRelay(false) - b.filterServiceConf.withEnabled(false) - b.withLightPush(false) - b.storeServiceConf.withEnabled(false) - # Leave discv5 and rendezvous for user choice - - ## Network Conf - let protocolsConfig = nodeConfig.protocolsConfig - - # Set cluster ID - b.withClusterId(protocolsConfig.clusterId) - - # Set sharding configuration - b.withShardingConf(ShardingConfKind.AutoSharding) - let autoShardingConfig = protocolsConfig.autoShardingConfig - b.withNumShardsInCluster(autoShardingConfig.numShardsInCluster) - - # Process entry nodes - supports enrtree:, enr:, and multiaddress formats - if protocolsConfig.entryNodes.len > 0: - let (enrTreeUrls, bootstrapEnrs, staticNodesFromEntry) = processEntryNodes( - protocolsConfig.entryNodes - ).valueOr: - return err("Failed to process entry nodes: " & error) - - # Set ENRTree URLs for DNS discovery - if enrTreeUrls.len > 0: - for url in enrTreeUrls: - b.dnsDiscoveryConf.withEnrTreeUrl(url) - b.dnsDiscoveryconf.withNameServers( - @[parseIpAddress("1.1.1.1"), parseIpAddress("1.0.0.1")] - ) - - # Set ENR records as bootstrap nodes for discv5 - if bootstrapEnrs.len > 0: - b.discv5Conf.withBootstrapNodes(bootstrapEnrs) - - # Add static nodes (multiaddrs and those extracted from ENR entries) - if staticNodesFromEntry.len > 0: - b.withStaticNodes(staticNodesFromEntry) - - # TODO: verify behaviour - # Set static store nodes - if protocolsConfig.staticStoreNodes.len > 0: - b.withStaticNodes(protocolsConfig.staticStoreNodes) - - # Set message validation - let msgValidation = protocolsConfig.messageValidation - let maxSizeBytes = parseMsgSize(msgValidation.maxMessageSize).valueOr: - return err("Failed to parse max message size: " & error) - b.withMaxMessageSize(maxSizeBytes) - - # Set RLN config if provided - if msgValidation.rlnConfig.isSome(): - let rlnConfig = msgValidation.rlnConfig.get() - b.rlnRelayConf.withEnabled(true) - b.rlnRelayConf.withEthContractAddress(rlnConfig.contractAddress) - b.rlnRelayConf.withChainId(rlnConfig.chainId) - b.rlnRelayConf.withEpochSizeSec(rlnConfig.epochSizeSec) - b.rlnRelayConf.withDynamic(true) - b.rlnRelayConf.withEthClientUrls(nodeConfig.ethRpcEndpoints) - - # TODO: we should get rid of those two - b.rlnRelayconf.withUserMessageLimit(100) - - ## Various configurations - b.withNatStrategy("any") - b.withP2PReliability(nodeConfig.p2pReliability) - - let wakuConf = b.build().valueOr: - return err("Failed to build configuration: " & error) - - wakuConf.validate().isOkOr: - return err("Failed to validate configuration: " & error) - - return ok(wakuConf) - -# ---- JSON serialization (writeValue / readValue) ---- -# ---------- AutoShardingConfig ---------- - -proc writeValue*(w: var JsonWriter, val: AutoShardingConfig) {.raises: [IOError].} = - w.beginRecord() - w.writeField("numShardsInCluster", val.numShardsInCluster) - w.endRecord() - -proc readValue*( - r: var JsonReader, val: var AutoShardingConfig -) {.raises: [SerializationError, IOError].} = - var numShardsInCluster: Option[uint16] - - for fieldName in readObjectFields(r): - case fieldName - of "numShardsInCluster": - numShardsInCluster = some(r.readValue(uint16)) - else: - r.raiseUnexpectedField(fieldName, "AutoShardingConfig") - - if numShardsInCluster.isNone(): - r.raiseUnexpectedValue("Missing required field 'numShardsInCluster'") - - val = AutoShardingConfig(numShardsInCluster: numShardsInCluster.get()) - -# ---------- RlnConfig ---------- - -proc writeValue*(w: var JsonWriter, val: RlnConfig) {.raises: [IOError].} = - w.beginRecord() - w.writeField("contractAddress", val.contractAddress) - w.writeField("chainId", val.chainId) - w.writeField("epochSizeSec", val.epochSizeSec) - w.endRecord() - -proc readValue*( - r: var JsonReader, val: var RlnConfig -) {.raises: [SerializationError, IOError].} = - var - contractAddress: Option[string] - chainId: Option[uint] - epochSizeSec: Option[uint64] - - for fieldName in readObjectFields(r): - case fieldName - of "contractAddress": - contractAddress = some(r.readValue(string)) - of "chainId": - chainId = some(r.readValue(uint)) - of "epochSizeSec": - epochSizeSec = some(r.readValue(uint64)) - else: - r.raiseUnexpectedField(fieldName, "RlnConfig") - - if contractAddress.isNone(): - r.raiseUnexpectedValue("Missing required field 'contractAddress'") - if chainId.isNone(): - r.raiseUnexpectedValue("Missing required field 'chainId'") - if epochSizeSec.isNone(): - r.raiseUnexpectedValue("Missing required field 'epochSizeSec'") - - val = RlnConfig( - contractAddress: contractAddress.get(), - chainId: chainId.get(), - epochSizeSec: epochSizeSec.get(), - ) - -# ---------- NetworkingConfig ---------- - -proc writeValue*(w: var JsonWriter, val: NetworkingConfig) {.raises: [IOError].} = - w.beginRecord() - w.writeField("listenIpv4", val.listenIpv4) - w.writeField("p2pTcpPort", val.p2pTcpPort) - w.writeField("discv5UdpPort", val.discv5UdpPort) - w.endRecord() - -proc readValue*( - r: var JsonReader, val: var NetworkingConfig -) {.raises: [SerializationError, IOError].} = - var - listenIpv4: Option[string] - p2pTcpPort: Option[uint16] - discv5UdpPort: Option[uint16] - - for fieldName in readObjectFields(r): - case fieldName - of "listenIpv4": - listenIpv4 = some(r.readValue(string)) - of "p2pTcpPort": - p2pTcpPort = some(r.readValue(uint16)) - of "discv5UdpPort": - discv5UdpPort = some(r.readValue(uint16)) - else: - r.raiseUnexpectedField(fieldName, "NetworkingConfig") - - if listenIpv4.isNone(): - r.raiseUnexpectedValue("Missing required field 'listenIpv4'") - if p2pTcpPort.isNone(): - r.raiseUnexpectedValue("Missing required field 'p2pTcpPort'") - if discv5UdpPort.isNone(): - r.raiseUnexpectedValue("Missing required field 'discv5UdpPort'") - - val = NetworkingConfig( - listenIpv4: listenIpv4.get(), - p2pTcpPort: p2pTcpPort.get(), - discv5UdpPort: discv5UdpPort.get(), - ) - -# ---------- MessageValidation ---------- - -proc writeValue*(w: var JsonWriter, val: MessageValidation) {.raises: [IOError].} = - w.beginRecord() - w.writeField("maxMessageSize", val.maxMessageSize) - w.writeField("rlnConfig", val.rlnConfig) - w.endRecord() - -proc readValue*( - r: var JsonReader, val: var MessageValidation -) {.raises: [SerializationError, IOError].} = - var - maxMessageSize: Option[string] - rlnConfig: Option[Option[RlnConfig]] - - for fieldName in readObjectFields(r): - case fieldName - of "maxMessageSize": - maxMessageSize = some(r.readValue(string)) - of "rlnConfig": - rlnConfig = some(r.readValue(Option[RlnConfig])) - else: - r.raiseUnexpectedField(fieldName, "MessageValidation") - - if maxMessageSize.isNone(): - r.raiseUnexpectedValue("Missing required field 'maxMessageSize'") - - val = MessageValidation( - maxMessageSize: maxMessageSize.get(), rlnConfig: rlnConfig.get(none(RlnConfig)) - ) - -# ---------- ProtocolsConfig ---------- - -proc writeValue*(w: var JsonWriter, val: ProtocolsConfig) {.raises: [IOError].} = - w.beginRecord() - w.writeField("entryNodes", val.entryNodes) - w.writeField("staticStoreNodes", val.staticStoreNodes) - w.writeField("clusterId", val.clusterId) - w.writeField("autoShardingConfig", val.autoShardingConfig) - w.writeField("messageValidation", val.messageValidation) - w.endRecord() - -proc readValue*( - r: var JsonReader, val: var ProtocolsConfig -) {.raises: [SerializationError, IOError].} = - var - entryNodes: Option[seq[string]] - staticStoreNodes: Option[seq[string]] - clusterId: Option[uint16] - autoShardingConfig: Option[AutoShardingConfig] - messageValidation: Option[MessageValidation] - - for fieldName in readObjectFields(r): - case fieldName - of "entryNodes": - entryNodes = some(r.readValue(seq[string])) - of "staticStoreNodes": - staticStoreNodes = some(r.readValue(seq[string])) - of "clusterId": - clusterId = some(r.readValue(uint16)) - of "autoShardingConfig": - autoShardingConfig = some(r.readValue(AutoShardingConfig)) - of "messageValidation": - messageValidation = some(r.readValue(MessageValidation)) - else: - r.raiseUnexpectedField(fieldName, "ProtocolsConfig") - - if entryNodes.isNone(): - r.raiseUnexpectedValue("Missing required field 'entryNodes'") - if clusterId.isNone(): - r.raiseUnexpectedValue("Missing required field 'clusterId'") - - val = ProtocolsConfig.init( - entryNodes = entryNodes.get(), - staticStoreNodes = staticStoreNodes.get(@[]), - clusterId = clusterId.get(), - autoShardingConfig = autoShardingConfig.get(DefaultAutoShardingConfig), - messageValidation = messageValidation.get(DefaultMessageValidation), - ) - -# ---------- NodeConfig ---------- - -proc writeValue*(w: var JsonWriter, val: NodeConfig) {.raises: [IOError].} = - w.beginRecord() - w.writeField("mode", val.mode) - w.writeField("protocolsConfig", val.protocolsConfig) - w.writeField("networkingConfig", val.networkingConfig) - w.writeField("ethRpcEndpoints", val.ethRpcEndpoints) - w.writeField("p2pReliability", val.p2pReliability) - w.writeField("logLevel", val.logLevel) - w.writeField("logFormat", val.logFormat) - w.endRecord() - -proc readValue*( - r: var JsonReader, val: var NodeConfig -) {.raises: [SerializationError, IOError].} = - var - mode: Option[WakuMode] - protocolsConfig: Option[ProtocolsConfig] - networkingConfig: Option[NetworkingConfig] - ethRpcEndpoints: Option[seq[string]] - p2pReliability: Option[bool] - logLevel: Option[LogLevel] - logFormat: Option[LogFormat] - - for fieldName in readObjectFields(r): - case fieldName - of "mode": - mode = some(r.readValue(WakuMode)) - of "protocolsConfig": - protocolsConfig = some(r.readValue(ProtocolsConfig)) - of "networkingConfig": - networkingConfig = some(r.readValue(NetworkingConfig)) - of "ethRpcEndpoints": - ethRpcEndpoints = some(r.readValue(seq[string])) - of "p2pReliability": - p2pReliability = some(r.readValue(bool)) - of "logLevel": - logLevel = some(r.readValue(LogLevel)) - of "logFormat": - logFormat = some(r.readValue(LogFormat)) - else: - r.raiseUnexpectedField(fieldName, "NodeConfig") - - val = NodeConfig.init( - mode = mode.get(WakuMode.Core), - protocolsConfig = protocolsConfig.get(TheWakuNetworkPreset), - networkingConfig = networkingConfig.get(DefaultNetworkingConfig), - ethRpcEndpoints = ethRpcEndpoints.get(@[]), - p2pReliability = p2pReliability.get(false), - logLevel = logLevel.get(LogLevel.INFO), - logFormat = logFormat.get(LogFormat.TEXT), - ) - -# ---------- Decode helper ---------- -# Json.decode returns T via `result`, which conflicts with {.requiresInit.} -# on Nim 2.x. This helper avoids the issue by using readValue into a var. - -proc decodeNodeConfigFromJson*( - jsonStr: string -): NodeConfig {. - raises: [SerializationError], - deprecated: "Use WakuNodeConf with fieldPairs-based JSON parsing instead" -.} = - var val = NodeConfig.init() # default-initialized - try: - var stream = unsafeMemoryInput(jsonStr) - var reader = (JsonReader[DefaultFlavor].init(stream)) - reader.readValue(val) - except IOError as err: - raise (ref SerializationError)(msg: err.msg) - return val diff --git a/logos_delivery/waku/api/debug.nim b/logos_delivery/waku/api/debug.nim new file mode 100644 index 000000000..10582316b --- /dev/null +++ b/logos_delivery/waku/api/debug.nim @@ -0,0 +1,36 @@ +## Waku layer API — debug / info operations. +{.push raises: [].} + +import results, chronos, chronicles, metrics +import eth/p2p/discoveryv5/enr + +import logos_delivery/waku/waku +import logos_delivery/waku/[waku_core, node/waku_node] + +proc version*(self: Waku): Future[Result[string, string]] {.async.} = + return ok(WakuNodeVersionString) + +proc listenAddresses*(self: Waku): Future[Result[seq[string], string]] {.async.} = + try: + return ok(self.node.info().listenAddresses) + except CatchableError as e: + return err(e.msg) + +proc myEnr*(self: Waku): Future[Result[string, string]] {.async.} = + try: + return ok(self.node.enr.toURI()) + except CatchableError as e: + return err(e.msg) + +proc myPeerId*(self: Waku): Future[Result[string, string]] {.async.} = + try: + return ok($self.node.peerId()) + except CatchableError as e: + return err(e.msg) + +proc metrics*(self: Waku): Future[Result[string, string]] {.async.} = + {.gcsafe.}: + try: + return ok(defaultRegistry.toText()) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/discovery.nim b/logos_delivery/waku/api/discovery.nim new file mode 100644 index 000000000..fefeb3d5d --- /dev/null +++ b/logos_delivery/waku/api/discovery.nim @@ -0,0 +1,76 @@ +## Waku layer API — discovery operations (DNS, discv5, peer exchange). +{.push raises: [].} + +import std/[net, sequtils] +import results, chronos, chronicles + +import logos_delivery/waku/waku +import + logos_delivery/waku/[ + waku_core, + node/waku_node, + node/waku_node/peer_exchange, + discovery/waku_dnsdisc, + discovery/waku_discv5, + ] + +proc dnsDiscovery*( + self: Waku, enrTreeUrl: string, nameServer: string, timeoutMs: int +): Future[Result[seq[string], string]] {.async.} = + try: + let dnsNameServers = @[parseIpAddress(nameServer)] + let discoveredPeers = ( + await retrieveDynamicBootstrapNodes(enrTreeUrl, dnsNameServers) + ).valueOr: + return err("failed discovering peers from DNS: " & $error) + + var multiAddresses = newSeq[string]() + for discPeer in discoveredPeers: + for address in discPeer.addrs: + multiAddresses.add($address & "/p2p/" & $discPeer) + + return ok(multiAddresses) + except CatchableError as e: + return err(e.msg) + +proc discv5UpdateBootnodes*( + self: Waku, bootnodes: string +): Future[Result[bool, string]] {.async.} = + ## `bootnodes` is a JSON array of ENRs, e.g. `["enr:...", "enr:..."]`. + try: + if self.wakuDiscv5.isNil(): + return err("discv5 not started") + self.wakuDiscv5.updateBootstrapRecords(bootnodes).isOkOr: + return err("error in discv5UpdateBootnodes: " & $error) + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc startDiscv5*(self: Waku): Future[Result[bool, string]] {.async.} = + try: + if self.wakuDiscv5.isNil(): + return err("discv5 not started") + (await self.wakuDiscv5.start()).isOkOr: + return err("error starting discv5: " & $error) + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc stopDiscv5*(self: Waku): Future[Result[bool, string]] {.async.} = + try: + if self.wakuDiscv5.isNil(): + return err("discv5 not started") + await self.wakuDiscv5.stop() + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc peerExchangeRequest*( + self: Waku, numPeers: uint64 +): Future[Result[int, string]] {.async.} = + try: + let numPeersRecv = (await self.node.fetchPeerExchangePeers(numPeers)).valueOr: + return err("failed peer exchange: " & $error) + return ok(numPeersRecv) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/events/discovery_events.nim b/logos_delivery/waku/api/events/discovery_events.nim similarity index 100% rename from logos_delivery/waku/events/discovery_events.nim rename to logos_delivery/waku/api/events/discovery_events.nim diff --git a/logos_delivery/waku/api/events/events.nim b/logos_delivery/waku/api/events/events.nim new file mode 100644 index 000000000..e9f175e65 --- /dev/null +++ b/logos_delivery/waku/api/events/events.nim @@ -0,0 +1,3 @@ +import ./[filter_subscribe_events, health_events, peer_events, discovery_events] + +export filter_subscribe_events, health_events, peer_events, discovery_events diff --git a/logos_delivery/waku/events/delivery_events.nim b/logos_delivery/waku/api/events/filter_subscribe_events.nim similarity index 100% rename from logos_delivery/waku/events/delivery_events.nim rename to logos_delivery/waku/api/events/filter_subscribe_events.nim diff --git a/logos_delivery/waku/events/health_events.nim b/logos_delivery/waku/api/events/health_events.nim similarity index 75% rename from logos_delivery/waku/events/health_events.nim rename to logos_delivery/waku/api/events/health_events.nim index 4ff6f0c6c..9c49402f7 100644 --- a/logos_delivery/waku/events/health_events.nim +++ b/logos_delivery/waku/api/events/health_events.nim @@ -1,15 +1,10 @@ import brokers/event_broker -import logos_delivery/waku/api/types +import logos_delivery/api/types import logos_delivery/waku/node/health_monitor/[protocol_health, topic_health] import logos_delivery/waku/waku_core/topics -export protocol_health, topic_health - -# Notify health changes to node connectivity -EventBroker: - type EventConnectionStatusChange* = object - connectionStatus*: ConnectionStatus +export event_broker, protocol_health, topic_health # Notify health changes to a subscribed topic # TODO: emit content topic health change events when subscribe/unsubscribe diff --git a/logos_delivery/waku/events/peer_events.nim b/logos_delivery/waku/api/events/peer_events.nim similarity index 100% rename from logos_delivery/waku/events/peer_events.nim rename to logos_delivery/waku/api/events/peer_events.nim diff --git a/logos_delivery/waku/api/filter.nim b/logos_delivery/waku/api/filter.nim new file mode 100644 index 000000000..06c78213f --- /dev/null +++ b/logos_delivery/waku/api/filter.nim @@ -0,0 +1,88 @@ +## Waku layer API — filter (light client) operations. +import logos_delivery/waku/compat/option_valueor +{.push raises: [].} + +import std/options +import results, chronos, chronicles + +import logos_delivery/waku/waku +import + logos_delivery/waku/[ + waku_core, + waku_core/subscription/push_handler, + node/waku_node, + node/waku_node/filter, + node/peer_manager, + waku_filter_v2/client, + waku_filter_v2/common, + ] + +const FilterOpTimeout = 5.seconds + +proc filterSubscribe*( + self: Waku, + pubsubTopic: PubsubTopic, + contentTopics: seq[ContentTopic], + pushHandler: FilterPushHandler, +): Future[Result[bool, string]] {.async.} = + ## Registers `pushHandler` for incoming filtered messages, selects a filter + ## service peer, and subscribes. + try: + if self.node.wakuFilterClient.isNil(): + return err("wakuFilterClient is not mounted") + + self.node.wakuFilterClient.registerPushHandler(pushHandler) + + let peer = self.node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr: + return err("could not find peer with WakuFilterSubscribeCodec when subscribing") + + let subFut = self.node.filterSubscribe(some(pubsubTopic), contentTopics, peer) + if not await subFut.withTimeout(FilterOpTimeout): + return err("filter subscription timed out") + subFut.read().isOkOr: + return err($error) + + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc filterUnsubscribe*( + self: Waku, pubsubTopic: PubsubTopic, contentTopics: seq[ContentTopic] +): Future[Result[bool, string]] {.async.} = + ## Selects a filter service peer and unsubscribes the given content topics. + try: + if self.node.wakuFilterClient.isNil(): + return err("wakuFilterClient is not mounted") + + let peer = self.node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr: + return err("could not find peer with WakuFilterSubscribeCodec when unsubscribing") + + let unsubFut = self.node.filterUnsubscribe(some(pubsubTopic), contentTopics, peer) + if not await unsubFut.withTimeout(FilterOpTimeout): + return err("filter un-subscription timed out") + unsubFut.read().isOkOr: + return err($error) + + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc filterUnsubscribeAll*(self: Waku): Future[Result[bool, string]] {.async.} = + ## Selects a filter service peer and unsubscribes from everything. + try: + if self.node.wakuFilterClient.isNil(): + return err("wakuFilterClient is not mounted") + + let peer = self.node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr: + return + err("could not find peer with WakuFilterSubscribeCodec when unsubscribing all") + + let unsubFut = self.node.filterUnsubscribeAll(peer) + if not await unsubFut.withTimeout(FilterOpTimeout): + return err("filter un-subscription all timed out") + unsubFut.read().isOkOr: + return err($error) + + return ok(true) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/health.nim b/logos_delivery/waku/api/health.nim new file mode 100644 index 000000000..7faeebe4d --- /dev/null +++ b/logos_delivery/waku/api/health.nim @@ -0,0 +1,13 @@ +## Waku layer API — health / connectivity. +{.push raises: [].} + +import results, chronos, chronicles + +import logos_delivery/waku/waku +import logos_delivery/waku/[node/health_monitor, node/health_monitor/online_monitor] + +proc isOnline*(self: Waku): Future[Result[bool, string]] {.async.} = + try: + return ok(self.healthMonitor.onlineMonitor.amIOnline()) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/lightpush.nim b/logos_delivery/waku/api/lightpush.nim new file mode 100644 index 000000000..6208a2643 --- /dev/null +++ b/logos_delivery/waku/api/lightpush.nim @@ -0,0 +1,37 @@ +## Waku layer API — lightpush (light client publish) operations. +import logos_delivery/waku/compat/option_valueor +{.push raises: [].} + +import results, chronos, chronicles + +import logos_delivery/waku/waku +import + logos_delivery/waku/[ + waku_core, + waku_core/codecs, + node/waku_node, + node/peer_manager, + waku_lightpush_legacy/client, + ] + +proc lightpushPublish*( + self: Waku, pubsubTopic: PubsubTopic, message: WakuMessage +): Future[Result[string, string]] {.async.} = + ## Selects a lightpush service peer and publishes; returns the message hash. + try: + if self.node.wakuLegacyLightpushClient.isNil(): + return err("wakuLegacyLightpushClient is not mounted") + + let remotePeer = self.node.peerManager.selectPeer(WakuLightPushCodec).valueOr: + return err("failed to lightpublish message, no suitable remote peers") + + let msgHashHex = ( + await self.node.wakuLegacyLightpushClient.publish( + pubsubTopic, message, remotePeer + ) + ).valueOr: + return err($error) + + return ok(msgHashHex) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/peer_manager.nim b/logos_delivery/waku/api/peer_manager.nim new file mode 100644 index 000000000..3a73051b8 --- /dev/null +++ b/logos_delivery/waku/api/peer_manager.nim @@ -0,0 +1,109 @@ +## Waku layer API — peer management operations. +{.push raises: [].} + +import std/[options, sequtils, strutils] +import results, chronos, chronicles +import libp2p/[peerid, peerstore] + +import logos_delivery/waku/waku +import logos_delivery/waku/[waku_core, node/waku_node, node/peer_manager] + +# `PeerConnInfo` is defined in `api/types` (surfaced here via `import waku`). + +proc connect*( + self: Waku, peers: seq[string], timeoutMs: uint32 +): Future[Result[bool, string]] {.async.} = + try: + await self.node.connectToNodes(peers.mapIt(strip(it)), source = "static") + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc disconnectPeerById*( + self: Waku, peerId: string +): Future[Result[bool, string]] {.async.} = + try: + let pId = PeerId.init(peerId).valueOr: + return err($error) + await self.node.peerManager.disconnectNode(pId) + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc disconnectAllPeers*(self: Waku): Future[Result[bool, string]] {.async.} = + try: + await self.node.peerManager.disconnectAllPeers() + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc dialPeer*( + self: Waku, peerAddr: string, protocol: string, timeoutMs: int +): Future[Result[bool, string]] {.async.} = + try: + let remotePeerInfo = parsePeerInfo(peerAddr).valueOr: + return err($error) + let conn = await self.node.peerManager.dialPeer(remotePeerInfo, protocol) + if conn.isNone(): + return err("failed dialing peer") + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc dialPeerById*( + self: Waku, peerId: string, protocol: string, timeoutMs: int +): Future[Result[bool, string]] {.async.} = + try: + let pId = PeerId.init(peerId).valueOr: + return err($error) + let conn = await self.node.peerManager.dialPeer(pId, protocol) + if conn.isNone(): + return err("failed dialing peer") + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc peerIdsFromPeerstore*(self: Waku): Future[Result[seq[string], string]] {.async.} = + try: + return ok(self.node.peerManager.switch.peerStore.peers().mapIt($it.peerId)) + except CatchableError as e: + return err(e.msg) + +proc connectedPeersInfo*( + self: Waku +): Future[Result[seq[PeerConnInfo], string]] {.async.} = + ## Structured info (protocols, addresses) for every connected peer. + try: + var infos: seq[PeerConnInfo] + for peer in self.node.peerManager.switch.peerStore.peers(): + if peer.connectedness == Connected: + infos.add( + PeerConnInfo( + peerId: $peer.peerId, + protocols: peer.protocols, + addresses: peer.addrs.mapIt($it), + ) + ) + return ok(infos) + except CatchableError as e: + return err(e.msg) + +proc connectedPeers*(self: Waku): Future[Result[seq[string], string]] {.async.} = + try: + let (inPeerIds, outPeerIds) = self.node.peerManager.connectedPeers() + return ok(concat(inPeerIds, outPeerIds).mapIt($it)) + except CatchableError as e: + return err(e.msg) + +proc peerIdsByProtocol*( + self: Waku, protocol: string +): Future[Result[seq[string], string]] {.async.} = + try: + return ok( + self.node.peerManager.switch.peerStore + .peers(protocol) + .filterIt(it.connectedness == Connected) + .mapIt($it.peerId) + ) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/ping.nim b/logos_delivery/waku/api/ping.nim new file mode 100644 index 000000000..9cf2dfc33 --- /dev/null +++ b/logos_delivery/waku/api/ping.nim @@ -0,0 +1,45 @@ +## Waku layer API — ping operation. +{.push raises: [].} + +import results, chronos, chronicles +import libp2p/protocols/ping +import libp2p/switch + +import logos_delivery/waku/waku +import logos_delivery/waku/[waku_core, node/waku_node, node/waku_node/ping] + +proc pingPeer*( + self: Waku, peerAddr: string, timeoutMs: int +): Future[Result[int64, string]] {.async.} = + ## Pings the peer; `timeoutMs <= 0` means no timeout. Returns RTT in nanos. + try: + let peerInfo = parsePeerInfo(peerAddr).valueOr: + return err("pingPeer failed to parse peer addr: " & $error) + + proc doPing(): Future[Result[Duration, string]] {.async.} = + try: + let conn = + await self.node.switch.dial(peerInfo.peerId, peerInfo.addrs, PingCodec) + defer: + await conn.close() + let rtt = await self.node.libp2pPing.ping(conn) + if rtt == 0.nanos: + return err("could not ping peer: rtt-0") + return ok(rtt) + except CatchableError as e: + return err("could not ping peer: " & e.msg) + + let pingFut = doPing() + let rtt: Duration = + if timeoutMs <= 0: + (await pingFut).valueOr: + return err(error) + else: + if not await pingFut.withTimeout(chronos.milliseconds(timeoutMs)): + return err("ping timed out") + pingFut.read().valueOr: + return err(error) + + return ok(rtt.nanos) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/publish.nim b/logos_delivery/waku/api/publish.nim new file mode 100644 index 000000000..e45eff942 --- /dev/null +++ b/logos_delivery/waku/api/publish.nim @@ -0,0 +1,60 @@ +## Waku layer API — message publish primitives used by the messaging send +## pipeline. +## +## Unlike `relay.nim`/`lightpush.nim`, these preserve the rich +## `WakuLightPushResult` (status code + description) that the send processors +## branch on for their retry decisions, and expose relay/lightpush availability +## so the messaging layer never inspects `waku.node` directly. +import logos_delivery/waku/compat/option_valueor +{.push raises: [].} + +import std/options +import results, chronos + +import logos_delivery/waku/waku +import + logos_delivery/waku/[ + waku_core, + node/waku_node, + node/peer_manager, + waku_relay/protocol, + rln, + waku_lightpush/common, + waku_lightpush/rpc, + waku_lightpush/client, + waku_lightpush/callbacks, + ] + +# WakuLightPushResult, PushMessageHandler, LightPushErrorCode (common) plus the +# LightPushStatusCode `$`/`==` the send processors branch on (rpc). +export common, rpc + +proc hasRelay*(self: Waku): bool = + ## True if relay (gossipsub publishing) is mounted. + return not self.node.wakuRelay.isNil() + +proc hasLightpush*(self: Waku): bool = + ## True if a lightpush client is mounted. + return not self.node.wakuLightpushClient.isNil() + +proc relayPushHandler*(self: Waku): PushMessageHandler = + ## Builds the relay publish handler used by the send pipeline. Caller + ## ensures relay is mounted. RLN proof generation is handled client-side + ## in (legacy)lightpushPublish; this handler only validates and republishes. + return getRelayPushHandler(self.node.wakuRelay) + +proc lightpushPeerAvailable*(self: Waku, shard: PubsubTopic): bool = + ## True if a lightpush service peer is available for `shard`. + return self.node.peerManager.selectPeer(WakuLightPushCodec, some(shard)).isSome() + +proc lightpushPublishToAny*( + self: Waku, shard: PubsubTopic, message: WakuMessage +): Future[WakuLightPushResult] {.async.} = + ## Selects a lightpush service peer for `shard` and publishes `message`. + ## Returns SERVICE_NOT_AVAILABLE when no peer is available. + let peer = self.node.peerManager.selectPeer(WakuLightPushCodec, some(shard)).valueOr: + return lightpushResultServiceUnavailable("no lightpush peer available for shard") + try: + return await self.node.wakuLightpushClient.publish(some(shard), message, peer) + except CatchableError as e: + return lightpushResultInternalError(e.msg) diff --git a/logos_delivery/waku/api/relay.nim b/logos_delivery/waku/api/relay.nim new file mode 100644 index 000000000..930221300 --- /dev/null +++ b/logos_delivery/waku/api/relay.nim @@ -0,0 +1,132 @@ +## Waku layer API — relay (gossipsub) operations. +{.push raises: [].} + +import std/sequtils +import results, chronos, chronicles, secp256k1, stew/byteutils + +import logos_delivery/waku/waku +import + logos_delivery/waku/[ + waku_core, + node/waku_node, + node/waku_node/relay, + node/subscription_manager, + waku_relay/protocol, + factory/waku_conf, + factory/validator_signed, + ] + +proc relayPublish*( + self: Waku, pubsubTopic: PubsubTopic, message: WakuMessage, timeoutMs: uint32 +): Future[Result[string, string]] {.async.} = + ## Publishes `message` and returns its message hash (0x-hex). + try: + if self.node.wakuRelay.isNil(): + return err("relayPublish: WakuRelay not mounted") + + (await self.node.wakuRelay.publish(pubsubTopic, message)).isOkOr: + return err($error) + + return ok(computeMessageHash(pubsubTopic, message).to0xHex) + except CatchableError as e: + return err(e.msg) + +proc relaySubscribe*( + self: Waku, + pubsubTopic: PubsubTopic, + handler: WakuRelayHandler = WakuRelayHandler(nil), +): Future[Result[bool, string]] {.async.} = + ## Subscribes to `pubsubTopic`. `handler` (optional) is invoked per message; + ## pass nil to subscribe without a message callback. + try: + if self.node.wakuRelay.isNil(): + return err("relaySubscribe: WakuRelay not mounted") + + self.node.subscribe((kind: SubscriptionKind.PubsubSub, topic: pubsubTopic), handler).isOkOr: + return err($error) + + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc relayUnsubscribe*( + self: Waku, pubsubTopic: PubsubTopic +): Future[Result[bool, string]] {.async.} = + try: + if self.node.wakuRelay.isNil(): + return err("relayUnsubscribe: WakuRelay not mounted") + + self.node.unsubscribe((kind: SubscriptionKind.PubsubSub, topic: pubsubTopic)).isOkOr: + return err($error) + + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc relayAddProtectedShard*( + self: Waku, clusterId: uint16, shardId: uint16, publicKey: string +): Future[Result[bool, string]] {.async.} = + try: + if self.node.wakuRelay.isNil(): + return err("relayAddProtectedShard: WakuRelay not mounted") + + let pubKey = SkPublicKey.fromHex(publicKey).valueOr: + return err("relayAddProtectedShard: invalid public key: " & $error) + + let protectedShard = ProtectedShard(shard: shardId, key: pubKey) + self.node.wakuRelay.addSignedShardsValidator(@[protectedShard], clusterId) + return ok(true) + except CatchableError as e: + return err(e.msg) + +proc relayConnectedPeers*( + self: Waku, pubsubTopic: PubsubTopic +): Future[Result[seq[string], string]] {.async.} = + try: + if self.node.wakuRelay.isNil(): + return err("relayConnectedPeers: WakuRelay not mounted") + + let connPeers = self.node.wakuRelay.getConnectedPeers(pubsubTopic).valueOr: + return err($error) + + return ok(connPeers.mapIt($it)) + except CatchableError as e: + return err(e.msg) + +proc relayPeersInMesh*( + self: Waku, pubsubTopic: PubsubTopic +): Future[Result[seq[string], string]] {.async.} = + try: + if self.node.wakuRelay.isNil(): + return err("relayPeersInMesh: WakuRelay not mounted") + + let meshPeers = self.node.wakuRelay.getPeersInMesh(pubsubTopic).valueOr: + return err($error) + + return ok(meshPeers.mapIt($it)) + except CatchableError as e: + return err(e.msg) + +proc relayNumPeersInMesh*( + self: Waku, pubsubTopic: PubsubTopic +): Future[Result[int, string]] {.async.} = + try: + if self.node.wakuRelay.isNil(): + return err("relayNumPeersInMesh: WakuRelay not mounted") + let n = self.node.wakuRelay.getNumPeersInMesh(pubsubTopic).valueOr: + return err($error) + return ok(n) + except CatchableError as e: + return err(e.msg) + +proc relayNumConnectedPeers*( + self: Waku, pubsubTopic: PubsubTopic +): Future[Result[int, string]] {.async.} = + try: + if self.node.wakuRelay.isNil(): + return err("relayNumConnectedPeers: WakuRelay not mounted") + let n = self.node.wakuRelay.getNumConnectedPeers(pubsubTopic).valueOr: + return err($error) + return ok(n) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/send_api.md b/logos_delivery/waku/api/send_api.md deleted file mode 100644 index 2a5a2f8a4..000000000 --- a/logos_delivery/waku/api/send_api.md +++ /dev/null @@ -1,46 +0,0 @@ -# SEND API - -**THIS IS TO BE REMOVED BEFORE PR MERGE** - -This document collects logic and todo's around the Send API. - -## Overview - -Send api hides the complex logic of using raw protocols for reliable message delivery. -The delivery method is chosen based on the node configuration and actual availabilities of peers. - -## Delivery task - -Each message send request is bundled into a task that not just holds the composed message but also the state of the delivery. - -## Delivery methods - -Depending on the configuration and the availability of store client protocol + actual configured and/or discovered store nodes: -- P2PReliability validation - checking network store node whether the message is reached at least a store node. -- Simple retry until message is propagated to the network - - Relay says >0 peers as publish result - - LightpushClient returns with success - -Depending on node config: -- Relay -- Lightpush - -These methods are used in combination to achieve the best reliability. -Fallback mechanism is used to switch between methods if the current one fails. - -Relay+StoreCheck -> Relay+simple retry -> Lightpush+StoreCheck -> Lightpush simple retry -> Error - -Combination is dynamically chosen on node configuration. Levels can be skipped depending on actual connectivity. -Actual connectivity is checked: -- Relay's topic health check - at least dLow peers in the mesh for the topic -- Store nodes availability - at least one store service node is available in peer manager -- Lightpush client availability - at least one lightpush service node is available in peer manager - -## Delivery processing - -At every send request, each task is tried to be delivered right away. -Any further retries and store check is done as a background task in a loop with predefined intervals. -Each task is set for a maximum number of retries and/or maximum time to live. - -In each round of store check and retry send tasks are selected based on their state. -The state is updated based on the result of the delivery method. diff --git a/logos_delivery/waku/api/store.nim b/logos_delivery/waku/api/store.nim new file mode 100644 index 000000000..a54ebe1f3 --- /dev/null +++ b/logos_delivery/waku/api/store.nim @@ -0,0 +1,54 @@ +## Waku layer API — store (historical query) operations. +{.push raises: [].} + +import std/options +import results, chronos, chronicles + +import logos_delivery/waku/waku +import + logos_delivery/waku/ + [waku_core, node/waku_node, node/peer_manager, waku_store/common, waku_store/client] + +proc isStoreMounted*(self: Waku): bool = + ## True if a store client is mounted (the node can run store queries). + return not self.node.wakuStoreClient.isNil() + +proc hasStorePeer*(self: Waku): bool = + ## True if at least one store service peer is available to query. + return self.node.peerManager.selectPeer(WakuStoreCodec).isSome() + +proc storeQueryToAny*( + self: Waku, request: StoreQueryRequest +): Future[Result[StoreQueryResponse, string]] {.async.} = + ## Runs a store query against any available store peer (retries across peers). + try: + if self.node.wakuStoreClient.isNil(): + return err("wakuStoreClient is not mounted") + + let queryResponse = (await self.node.wakuStoreClient.queryToAny(request)).valueOr: + return err($error) + + return ok(queryResponse) + except CatchableError as e: + return err(e.msg) + +proc storeQuery*( + self: Waku, request: StoreQueryRequest, peer: string, timeoutMs: int +): Future[Result[StoreQueryResponse, string]] {.async.} = + try: + if self.node.wakuStoreClient.isNil(): + return err("wakuStoreClient is not mounted") + + let remotePeer = parsePeerInfo(peer).valueOr: + return err("storeQuery failed to parse peer addr: " & $error) + + let queryFut = self.node.wakuStoreClient.query(request, remotePeer) + if not await queryFut.withTimeout(timeoutMs.milliseconds): + return err("storeQuery timed out") + + let queryResponse = queryFut.read().valueOr: + return err("storeQuery failed: " & $error) + + return ok(queryResponse) + except CatchableError as e: + return err(e.msg) diff --git a/logos_delivery/waku/api/subscriptions.nim b/logos_delivery/waku/api/subscriptions.nim new file mode 100644 index 000000000..7638abf3e --- /dev/null +++ b/logos_delivery/waku/api/subscriptions.nim @@ -0,0 +1,37 @@ +## Waku layer API — content-topic subscription operations. +## +## These wrap the node's `SubscriptionManager`, which resolves each content +## topic to its autosharding shard. They give the layers above (messaging) a +## kernel-level entry point so they never reach into `waku.node` internals. +{.push raises: [].} + +import std/sets +import results + +import logos_delivery/waku/waku +import logos_delivery/waku/[waku_core, node/waku_node, node/subscription_manager] + +proc subscribe*(self: Waku, contentTopic: ContentTopic): Result[void, string] = + ## Subscribes to `contentTopic`, resolving its shard via autosharding. + return self.node.subscriptionManager.subscribe(contentTopic) + +proc unsubscribe*(self: Waku, contentTopic: ContentTopic): Result[void, string] = + ## Unsubscribes from `contentTopic`, resolving its shard via autosharding. + return self.node.subscriptionManager.unsubscribe(contentTopic) + +proc isSubscribed*(self: Waku, contentTopic: ContentTopic): Result[bool, string] = + ## True if the node already subscribes to `contentTopic`. + return self.node.subscriptionManager.isSubscribed(contentTopic) + +proc isContentSubscribed*( + self: Waku, shard: PubsubTopic, contentTopic: ContentTopic +): bool = + ## True if `contentTopic` is subscribed on the given `shard` (pubsub topic). + return self.node.subscriptionManager.isContentSubscribed(shard, contentTopic) + +proc subscribedContentTopics*(self: Waku): seq[(PubsubTopic, HashSet[ContentTopic])] = + ## Snapshot of every shard with its non-empty content-topic set. + var res: seq[(PubsubTopic, HashSet[ContentTopic])] + for shard, contentTopics in self.node.subscriptionManager.subscribedContentTopics: + res.add((shard, contentTopics)) + return res diff --git a/logos_delivery/waku/api/topics.nim b/logos_delivery/waku/api/topics.nim new file mode 100644 index 000000000..223291169 --- /dev/null +++ b/logos_delivery/waku/api/topics.nim @@ -0,0 +1,27 @@ +## Waku layer API — topic construction. +{.push raises: [].} + +import std/strformat +import results, chronos + +import logos_delivery/waku/waku +import logos_delivery/waku/waku_core + +proc buildContentTopic*( + self: Waku, appName: string, appVersion: uint32, name: string, encoding: string +): Future[Result[ContentTopic, string]] {.async.} = + try: + return ok(ContentTopic(fmt"/{appName}/{appVersion}/{name}/{encoding}")) + except CatchableError as e: + return err(e.msg) + +proc buildPubsubTopic*( + self: Waku, topicName: string +): Future[Result[PubsubTopic, string]] {.async.} = + try: + return ok(PubsubTopic(fmt"/waku/2/{topicName}")) + except CatchableError as e: + return err(e.msg) + +proc defaultPubsubTopic*(self: Waku): Future[Result[PubsubTopic, string]] {.async.} = + return ok(DefaultPubsubTopic) diff --git a/logos_delivery/waku/discovery/waku_discv5.nim b/logos_delivery/waku/discovery/waku_discv5.nim index ade874733..5f831730d 100644 --- a/logos_delivery/waku/discovery/waku_discv5.nim +++ b/logos_delivery/waku/discovery/waku_discv5.nim @@ -413,7 +413,6 @@ proc setupDiscoveryV5*( rng: crypto.Rng, key: crypto.PrivateKey, p2pListenAddress: IpAddress, - portsShift: uint16, ): Result[WakuDiscoveryV5, string] = ## Public only for testing. Callers should use `setupAndStartDiscv5`, which ## additionally handles `udpPort == 0` via auto-port retry. @@ -443,7 +442,7 @@ proc setupDiscoveryV5*( let discv5Config = DiscoveryConfig.init(conf.tableIpLimit, conf.bucketIpLimit, conf.bitsPerHop) - let discv5UdpPort = Port(uint16(conf.udpPort) + portsShift) + let discv5UdpPort = conf.udpPort let discv5Conf = WakuDiscoveryV5Config( discv5Config: some(discv5Config), @@ -469,7 +468,6 @@ proc setupAndStartDiscv5*( rng: crypto.Rng, key: crypto.PrivateKey, p2pListenAddress: IpAddress, - portsShift: uint16, ): Future[Result[WakuDiscoveryV5, string]] {.async: (raises: []).} = ## Construct and start a `WakuDiscoveryV5` instance, handling auto-port ## retry when the caller asks for `udpPort == 0`. @@ -480,7 +478,7 @@ proc setupAndStartDiscv5*( c.udpPort = port let wd = setupDiscoveryV5( myENR, nodePeerManager, nodeTopicSubscriptionQueue, c, dynamicBootstrapNodes, rng, - key, p2pListenAddress, portsShift, + key, p2pListenAddress, ).valueOr: return err(error) let startRes = await wd.start() diff --git a/logos_delivery/waku/discovery/waku_kademlia.nim b/logos_delivery/waku/discovery/waku_kademlia.nim index 9b0e43abc..e6775530c 100644 --- a/logos_delivery/waku/discovery/waku_kademlia.nim +++ b/logos_delivery/waku/discovery/waku_kademlia.nim @@ -21,7 +21,7 @@ import import logos_delivery/waku/waku_core, logos_delivery/waku/node/peer_manager, - logos_delivery/waku/events/discovery_events + logos_delivery/waku/api/events/discovery_events logScope: topics = "waku service discovery" diff --git a/logos_delivery/waku/events/events.nim b/logos_delivery/waku/events/events.nim deleted file mode 100644 index 130d7c018..000000000 --- a/logos_delivery/waku/events/events.nim +++ /dev/null @@ -1,9 +0,0 @@ -import - ./[ - message_events, delivery_events, health_events, peer_events, lifecycle_events, - discovery_events, - ] - -export - message_events, delivery_events, health_events, peer_events, lifecycle_events, - discovery_events diff --git a/logos_delivery/waku/factory/app_callbacks.nim b/logos_delivery/waku/factory/app_callbacks.nim index f1d3369be..0945b56bf 100644 --- a/logos_delivery/waku/factory/app_callbacks.nim +++ b/logos_delivery/waku/factory/app_callbacks.nim @@ -1,5 +1,9 @@ import ../waku_relay, ../node/peer_manager, ../node/health_monitor/connection_status +# Re-export the modules that define the handler types below, so that consumers +# of `AppCallbacks` (e.g. the FFI library) can construct the handlers. +export waku_relay, peer_manager, connection_status + type AppCallbacks* = ref object relayHandler*: WakuRelayHandler topicHealthChangeHandler*: TopicHealthChangeHandler diff --git a/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim index f4e70767e..8835f4c66 100644 --- a/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/rln_relay_conf_builder.nim @@ -12,7 +12,7 @@ const ############################## ## RLN Relay Config Builder ## ############################## -type RlnRelayConfBuilder* = object +type RlnConfBuilder* = object enabled*: Option[bool] chainId*: Option[UInt256] ethClientUrls*: Option[seq[string]] @@ -24,58 +24,58 @@ type RlnRelayConfBuilder* = object epochSizeSec*: Option[uint64] userMessageLimit*: Option[uint64] -proc init*(T: type RlnRelayConfBuilder): RlnRelayConfBuilder = - RlnRelayConfBuilder() +proc init*(T: type RlnConfBuilder): RlnConfBuilder = + RlnConfBuilder() -proc withEnabled*(b: var RlnRelayConfBuilder, enabled: bool) = +proc withEnabled*(b: var RlnConfBuilder, enabled: bool) = b.enabled = some(enabled) -proc withChainId*(b: var RlnRelayConfBuilder, chainId: uint | UInt256) = +proc withChainId*(b: var RlnConfBuilder, chainId: uint | UInt256) = when chainId is uint: b.chainId = some(UInt256.fromBytesBE(chainId.toBytesBE())) else: b.chainId = some(chainId) -proc withCredIndex*(b: var RlnRelayConfBuilder, credIndex: uint) = +proc withCredIndex*(b: var RlnConfBuilder, credIndex: uint) = b.credIndex = some(credIndex) -proc withCredPassword*(b: var RlnRelayConfBuilder, credPassword: string) = +proc withCredPassword*(b: var RlnConfBuilder, credPassword: string) = b.credPassword = some(credPassword) -proc withCredPath*(b: var RlnRelayConfBuilder, credPath: string) = +proc withCredPath*(b: var RlnConfBuilder, credPath: string) = b.credPath = some(credPath) -proc withDynamic*(b: var RlnRelayConfBuilder, dynamic: bool) = +proc withDynamic*(b: var RlnConfBuilder, dynamic: bool) = b.dynamic = some(dynamic) -proc withEthClientUrls*(b: var RlnRelayConfBuilder, ethClientUrls: seq[string]) = +proc withEthClientUrls*(b: var RlnConfBuilder, ethClientUrls: seq[string]) = b.ethClientUrls = some(ethClientUrls) -proc withEthContractAddress*(b: var RlnRelayConfBuilder, ethContractAddress: string) = +proc withEthContractAddress*(b: var RlnConfBuilder, ethContractAddress: string) = b.ethContractAddress = some(ethContractAddress) -proc withEpochSizeSec*(b: var RlnRelayConfBuilder, epochSizeSec: uint64) = +proc withEpochSizeSec*(b: var RlnConfBuilder, epochSizeSec: uint64) = b.epochSizeSec = some(epochSizeSec) -proc withUserMessageLimit*(b: var RlnRelayConfBuilder, userMessageLimit: uint64) = +proc withUserMessageLimit*(b: var RlnConfBuilder, userMessageLimit: uint64) = b.userMessageLimit = some(userMessageLimit) -proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] = +proc build*(b: RlnConfBuilder): Result[Option[RlnConf], string] = if not b.enabled.get(DefaultRlnRelayEnabled): - return ok(none(RlnRelayConf)) + return ok(none(RlnConf)) if b.chainId.isNone(): return err("RLN Relay Chain Id is not specified") let creds = if b.credPath.isSome() and b.credPassword.isSome(): - some(RlnRelayCreds(path: b.credPath.get(), password: b.credPassword.get())) + some(RlnCreds(path: b.credPath.get(), password: b.credPassword.get())) elif b.credPath.isSome() and b.credPassword.isNone(): return err("RLN Relay Credential Password is not specified but path is") elif b.credPath.isNone() and b.credPassword.isSome(): return err("RLN Relay Credential Path is not specified but password is") else: - none(RlnRelayCreds) + none(RlnCreds) if b.dynamic.isNone(): return err("rlnRelay.dynamic is not specified") @@ -85,7 +85,7 @@ proc build*(b: RlnRelayConfBuilder): Result[Option[RlnRelayConf], string] = return err("rlnRelay.ethContractAddress is not specified") return ok( some( - RlnRelayConf( + RlnConf( chainId: b.chainId.get(), credIndex: b.credIndex, creds: creds, diff --git a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim index dc17fb4ff..0eaec4934 100644 --- a/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim +++ b/logos_delivery/waku/factory/conf_builder/waku_conf_builder.nim @@ -116,7 +116,7 @@ type WakuConfBuilder* = object filterServiceConf*: FilterServiceConfBuilder metricsServerConf*: MetricsServerConfBuilder restServerConf*: RestServerConfBuilder - rlnRelayConf*: RlnRelayConfBuilder + rlnRelayConf*: RlnConfBuilder storeServiceConf*: StoreServiceConfBuilder mixConf*: MixConfBuilder webSocketConf*: WebSocketConfBuilder @@ -170,7 +170,6 @@ type WakuConfBuilder* = object relayShardedPeerManagement: Option[bool] relayServiceRatio: Option[string] circuitRelayClient: Option[bool] - p2pReliability: Option[bool] localStoragePath: Option[string] @@ -181,7 +180,7 @@ proc init*(T: type WakuConfBuilder): WakuConfBuilder = filterServiceConf: FilterServiceConfBuilder.init(), metricsServerConf: MetricsServerConfBuilder.init(), restServerConf: RestServerConfBuilder.init(), - rlnRelayConf: RlnRelayConfBuilder.init(), + rlnRelayConf: RlnConfBuilder.init(), storeServiceConf: StoreServiceConfBuilder.init(), webSocketConf: WebSocketConfBuilder.init(), quicConf: QuicConfBuilder.init(), @@ -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: @@ -529,6 +521,13 @@ proc enforceSecurityConstraints(builder: WakuConfBuilder): Result[void, string] ok() +func resolvePortsShift(configured: Port, portsShift: uint16): Port = + ## Fold portsShift into a configured port. Port(0) (auto-assign) is left as-is. + if configured == Port(0): + configured + else: + Port(configured.uint16 + portsShift) + proc build*( builder: var WakuConfBuilder, rng: crypto.Rng = crypto.newRng() ): Result[WakuConf, string] = @@ -624,7 +623,7 @@ proc build*( let contentTopics = builder.contentTopics.get(@[]) # Build sub-configs - let discv5Conf = builder.discv5Conf.build().valueOr: + var discv5Conf = builder.discv5Conf.build().valueOr: return err("Discv5 Conf building failed: " & $error) let dnsDiscoveryConf = builder.dnsDiscoveryConf.build().valueOr: @@ -633,10 +632,10 @@ proc build*( let filterServiceConf = builder.filterServiceConf.build().valueOr: return err("Filter Service Conf building failed: " & $error) - let metricsServerConf = builder.metricsServerConf.build().valueOr: + var metricsServerConf = builder.metricsServerConf.build().valueOr: return err("Metrics Server Conf building failed: " & $error) - let restServerConf = builder.restServerConf.build().valueOr: + var restServerConf = builder.restServerConf.build().valueOr: return err("REST Server Conf building failed: " & $error) let rlnRelayConf = builder.rlnRelayConf.build().valueOr: @@ -648,10 +647,10 @@ proc build*( let mixConf = builder.mixConf.build().valueOr: return err("Mix Conf building failed: " & $error) - let webSocketConf = builder.webSocketConf.build().valueOr: + var webSocketConf = builder.webSocketConf.build().valueOr: return err("WebSocket Conf building failed: " & $error) - let quicConf = builder.quicConf.build().valueOr: + var quicConf = builder.quicConf.build().valueOr: return err("QUIC Conf building failed: " & $error) let rateLimit = builder.rateLimitConf.build().valueOr: @@ -683,7 +682,7 @@ proc build*( warn "Nat Strategy is not specified, defaulting to none" DefaultNatStrategy - let p2pTcpPort = builder.p2pTcpPort.get(DefaultP2pTcpPort) + var p2pTcpPort = builder.p2pTcpPort.get(DefaultP2pTcpPort) let p2pListenAddress = if builder.p2pListenAddress.isSome(): @@ -765,6 +764,20 @@ proc build*( mix = mix, ) + # portsShift is consumed here, WakuConf carries final bind ports. + p2pTcpPort = resolvePortsShift(p2pTcpPort, portsShift) + if webSocketConf.isSome(): + webSocketConf.get().port = resolvePortsShift(webSocketConf.get().port, portsShift) + if quicConf.isSome(): + quicConf.get().port = resolvePortsShift(quicConf.get().port, portsShift) + if discv5Conf.isSome(): + discv5Conf.get().udpPort = resolvePortsShift(discv5Conf.get().udpPort, portsShift) + if restServerConf.isSome(): + restServerConf.get().port = resolvePortsShift(restServerConf.get().port, portsShift) + if metricsServerConf.isSome(): + metricsServerConf.get().httpPort = + resolvePortsShift(metricsServerConf.get().httpPort, portsShift) + let wakuConf = WakuConf( # confs storeServiceConf: storeServiceConf, @@ -784,7 +797,7 @@ proc build*( subscribeShards: subscribeShards, protectedShards: protectedShards, relay: relay, - lightPush: lightPush, + lightPush: lightPush and relay, # can't mount lightpush without relay peerExchangeService: peerExchange, rendezvous: rendezvous, peerExchangeDiscovery: true, @@ -806,7 +819,6 @@ proc build*( extMultiAddrs: extMultiAddrs, extMultiAddrsOnly: extMultiAddrsOnly, ), - portsShift: portsShift, webSocketConf: webSocketConf, quicConf: quicConf, dnsAddrsNameServers: dnsAddrsNameServers, @@ -821,7 +833,6 @@ proc build*( circuitRelayClient: builder.circuitRelayClient.get(DefaultCircuitRelayClient), staticNodes: builder.staticNodes, relayShardedPeerManagement: relayShardedPeerManagement, - p2pReliability: builder.p2pReliability.get(DefaultP2pReliability), wakuFlags: wakuFlags, localStoragePath: builder.localStoragePath.get(DefaultStoragePath), ) diff --git a/logos_delivery/waku/factory/internal_config.nim b/logos_delivery/waku/factory/internal_config.nim index 08f6e2568..45d7f0b4c 100644 --- a/logos_delivery/waku/factory/internal_config.nim +++ b/logos_delivery/waku/factory/internal_config.nim @@ -90,15 +90,14 @@ proc networkConfiguration*( quicConf: Option[QuicConf], wakuFlags: CapabilitiesBitfield, dnsAddrsNameServers: seq[IpAddress], - portsShift: uint16, clientId: string, ): Future[NetConfigResult] {.async.} = - let tcpBindPort = Port(uint16(conf.p2pTcpPort) + portsShift) + let tcpBindPort = conf.p2pTcpPort let (quicEnabled, quicBindPort) = if quicConf.isSome(): let qConf = quicConf.get() - (true, some(Port(qConf.port.uint16 + portsShift))) + (true, some(qConf.port)) else: (false, none(Port)) @@ -111,7 +110,7 @@ proc networkConfiguration*( let discv5UdpPort = if discv5Conf.isSome(): - some(Port(uint16(discv5Conf.get().udpPort) + portsShift)) + some(discv5Conf.get().udpPort) else: none(Port) @@ -145,7 +144,7 @@ proc networkConfiguration*( let (wsEnabled, wsBindPort, wssEnabled) = if webSocketConf.isSome: let wsConf = webSocketConf.get() - (true, some(Port(wsConf.port.uint16 + portsShift)), wsConf.secureConf.isSome) + (true, some(wsConf.port), wsConf.secureConf.isSome) else: (false, none(Port), false) diff --git a/logos_delivery/waku/factory/networks_config.nim b/logos_delivery/waku/factory/networks_config.nim index 3d1e296fe..339922c02 100644 --- a/logos_delivery/waku/factory/networks_config.nim +++ b/logos_delivery/waku/factory/networks_config.nim @@ -123,6 +123,39 @@ proc LogosTestConf*(T: type NetworkPresetConf): NetworkPresetConf = ], ) +# cluster-id=16 (Status Production Network) +# Cluster configuration for the `status.prod` network that Status runs on. +# RLN is disabled. Starting from the logos-delivery integration, status.prod +# defaults to auto-sharding with a single shard (numShardsInCluster = 1). +# Bootstrap is done through the status.prod DNS discovery enrtree plus the +# fleet boot nodes. +# Source: https://fleets.waku.org/ and each host's `/config.toml`. +proc StatusProdConf*(T: type NetworkPresetConf): NetworkPresetConf = + const ZeroChainId = 0'u256 + return NetworkPresetConf( + maxMessageSize: "1024KiB", + clusterId: 16, + rlnRelay: false, + rlnRelayEthContractAddress: "", + rlnRelayDynamic: false, + rlnRelayChainId: ZeroChainId, + rlnEpochSizeSec: 0, + rlnRelayUserMessageLimit: 0, + shardingConf: ShardingConf(kind: AutoSharding, numShardsInCluster: 1), + enableKadDiscovery: false, + kadBootstrapNodes: @[], + mix: false, + p2pReliability: false, + discv5Discovery: true, + discv5BootstrapNodes: @[], + entryNodes: @[ + "enrtree://AMOJVZX4V6EXP7NTJPMAYJYST2QP6AJXYW76IU6VGJS7UVSNDYZG4@boot.prod.status.nodes.status.im", + "/dns4/boot-01.do-ams3.status.prod.status.im/tcp/30303/p2p/16Uiu2HAmAR24Mbb6VuzoyUiGx42UenDkshENVDj4qnmmbabLvo31", + "/dns4/boot-01.gc-us-central1-a.status.prod.status.im/tcp/30303/p2p/16Uiu2HAm8mUZ18tBWPXDQsaF7PbCKYA35z7WB2xNZH2EVq1qS8LJ", + "/dns4/boot-01.ac-cn-hongkong-c.status.prod.status.im/tcp/30303/p2p/16Uiu2HAmGwcE8v7gmJNEWFtZtojYpPMTHy2jBLL6xRk33qgDxFWX", + ], + ) + proc validateShards*( shardingConf: ShardingConf, shards: seq[uint16] ): Result[void, string] = diff --git a/logos_delivery/waku/factory/node_factory.nim b/logos_delivery/waku/factory/node_factory.nim index 30e37850a..daa2e5fd0 100644 --- a/logos_delivery/waku/factory/node_factory.nim +++ b/logos_delivery/waku/factory/node_factory.nim @@ -23,7 +23,7 @@ import ../net/net_config, ../waku_core, ../waku_core/codecs, - ../waku_rln_relay, + ../rln, ../discovery/waku_dnsdisc, ../waku_archive/retention_policy as policy, ../waku_archive/retention_policy/builder as policy_builder, @@ -39,7 +39,7 @@ import ../node/peer_manager/peer_store/migrations as peer_store_sqlite_migrations, ../waku_lightpush_legacy/common, ../common/rate_limit/setting, - ../events/discovery_events + ../api/events/discovery_events ## Peer persistence @@ -337,7 +337,7 @@ proc setupProtocols( ) try: - await node.mountRlnRelay(rlnConf) + await node.setRlnValidator(rlnConf) except CatchableError: return err("failed to mount waku RLN relay protocol: " & getCurrentExceptionMsg()) @@ -462,7 +462,7 @@ proc setupNode*( await networkConfiguration( wakuConf.clusterId, wakuConf.endpointConf, wakuConf.discv5Conf, wakuConf.webSocketConf, wakuConf.quicConf, wakuConf.wakuFlags, - wakuConf.dnsAddrsNameServers, wakuConf.portsShift, clientId, + wakuConf.dnsAddrsNameServers, clientId, ) ).valueOr: error "failed to create internal config", error = error diff --git a/logos_delivery/waku/factory/waku_conf.nim b/logos_delivery/waku/factory/waku_conf.nim index 9de3d3bc8..db1a8aa28 100644 --- a/logos_delivery/waku/factory/waku_conf.nim +++ b/logos_delivery/waku/factory/waku_conf.nim @@ -13,7 +13,7 @@ import results import - ../waku_rln_relay/rln_relay, + ../rln/rln, ../rest_api/endpoint/builder, ../discovery/waku_discv5, ../discovery/waku_kademlia, @@ -25,7 +25,7 @@ import ../waku_mix, ./conf_builder/kademlia_discovery_conf_builder -export RlnRelayConf, RlnRelayCreds, RestServerConf, Discv5Conf, MetricsServerConf +export RlnConf, RlnCreds, RestServerConf, Discv5Conf, MetricsServerConf logScope: topics = "waku conf" @@ -113,7 +113,7 @@ type WakuConf* {.requiresInit.} = ref object dnsDiscoveryConf*: Option[DnsDiscoveryConf] filterServiceConf*: Option[FilterServiceConf] storeServiceConf*: Option[StoreServiceConf] - rlnRelayConf*: Option[RlnRelayConf] + rlnRelayConf*: Option[RlnConf] restServerConf*: Option[RestServerConf] metricsServerConf*: Option[MetricsServerConf] webSocketConf*: Option[WebSocketConf] @@ -121,7 +121,6 @@ type WakuConf* {.requiresInit.} = ref object mixConf*: Option[MixConf] kademliaDiscoveryConf*: Option[KademliaDiscoveryConf] - portsShift*: uint16 dnsAddrsNameServers*: seq[IpAddress] endpointConf*: EndpointConf wakuFlags*: CapabilitiesBitfield @@ -156,8 +155,6 @@ type WakuConf* {.requiresInit.} = ref object # TODO: use proper type relayServiceRatio*: string - p2pReliability*: bool - localStoragePath*: string proc logConf*(conf: WakuConf) = diff --git a/logos_delivery/waku/net/auto_port.nim b/logos_delivery/waku/net/auto_port.nim index 38176d27d..3191b25a6 100644 --- a/logos_delivery/waku/net/auto_port.nim +++ b/logos_delivery/waku/net/auto_port.nim @@ -9,7 +9,7 @@ const AutoPortMax = 59000'u16 AutoPortAttemptTimeout = chronos.seconds(30) -proc getAutoPort*(): uint16 = +proc getAutoPort(): uint16 = var rng = initRand() uint16(rng.rand(AutoPortMin.int .. AutoPortMax.int)) diff --git a/logos_delivery/waku/net/net_config.nim b/logos_delivery/waku/net/net_config.nim index b5eda9d6c..7d1fca9ff 100644 --- a/logos_delivery/waku/net/net_config.nim +++ b/logos_delivery/waku/net/net_config.nim @@ -1,7 +1,9 @@ {.push raises: [].} import - std/[options, sequtils, strutils, net], results, libp2p/[multiaddress, multicodec] + std/[options, sequtils, strutils, net], + results, + libp2p/[multiaddress, multicodec, wire] import ../../waku/waku_core/peers import ../waku_enr @@ -74,6 +76,32 @@ proc isWsAddress*(ma: MultiAddress): bool = proc isQuicAddress*(ma: MultiAddress): bool = return ma.hasProtocol("quic-v1") +proc isP2pTcpAddress*(ma: MultiAddress): bool = + return ma.hasProtocol("tcp") and not ma.isWsAddress() + +proc getPorts*( + listenAddrs: seq[MultiAddress] +): Result[tuple[tcpPort, websocketPort, quicPort: Option[Port]], string] = + var tcpPort, websocketPort, quicPort = none(Port) + + for a in listenAddrs: + if a.isWsAddress(): + if websocketPort.isNone(): + let wsAddress = initTAddress(a).valueOr: + return err("getPorts wsAddr error:" & $error) + websocketPort = some(wsAddress.port) + elif a.isQuicAddress(): + if quicPort.isNone(): + let quicAddress = initTAddress(a).valueOr: + return err("getPorts quicAddr error:" & $error) + quicPort = some(quicAddress.port) + elif tcpPort.isNone(): + let tcpAddress = initTAddress(a).valueOr: + return err("getPorts tcpAddr error:" & $error) + tcpPort = some(tcpAddress.port) + + return ok((tcpPort: tcpPort, websocketPort: websocketPort, quicPort: quicPort)) + proc containsWsAddress(extMultiAddrs: seq[MultiAddress]): bool = return extMultiAddrs.filterIt(it.isWsAddress()).len > 0 diff --git a/logos_delivery/waku/node/health_monitor/connection_status.nim b/logos_delivery/waku/node/health_monitor/connection_status.nim index 68ec9d4be..fd0328fb7 100644 --- a/logos_delivery/waku/node/health_monitor/connection_status.nim +++ b/logos_delivery/waku/node/health_monitor/connection_status.nim @@ -1,4 +1,5 @@ -import chronos, results, std/strutils, ../../api/types +import chronos, results, std/strutils +from logos_delivery/api/types import ConnectionStatus export ConnectionStatus diff --git a/logos_delivery/waku/node/health_monitor/node_health_monitor.nim b/logos_delivery/waku/node/health_monitor/node_health_monitor.nim index 6e77816f0..8a4aadb4b 100644 --- a/logos_delivery/waku/node/health_monitor/node_health_monitor.nim +++ b/logos_delivery/waku/node/health_monitor/node_health_monitor.nim @@ -8,12 +8,13 @@ import libp2p/protocols/rendezvous, libp2p/protocols/pubsub, libp2p/protocols/pubsub/rpc/messages, + logos_delivery/api/types, + logos_delivery/api/events/kernel_events, # EventConnectionStatusChange logos_delivery/waku/[ waku_relay, - waku_rln_relay, - api/types, - events/health_events, - events/peer_events, + api/events/health_events, + api/events/peer_events, + rln, node/waku_node, node/node_telemetry, node/peer_manager, @@ -89,12 +90,12 @@ proc getRelayHealth(hm: NodeHealthMonitor): ProtocolHealth = proc getRlnRelayHealth(hm: NodeHealthMonitor): Future[ProtocolHealth] {.async.} = var p = ProtocolHealth.init(WakuProtocol.RlnRelayProtocol) - if isNil(hm.node.wakuRlnRelay): + if isNil(hm.node.rln): return p.notMounted() const FutIsReadyTimout = 5.seconds - let isReadyStateFut = hm.node.wakuRlnRelay.isReady() + let isReadyStateFut = hm.node.rln.isReady() if not await isReadyStateFut.withTimeout(FutIsReadyTimout): return p.notReady("Ready state check timed out") diff --git a/logos_delivery/waku/node/peer_manager/peer_manager.nim b/logos_delivery/waku/node/peer_manager/peer_manager.nim index 60bd2acb1..31c6133ab 100644 --- a/logos_delivery/waku/node/peer_manager/peer_manager.nim +++ b/logos_delivery/waku/node/peer_manager/peer_manager.nim @@ -20,7 +20,7 @@ import waku_relay/protocol, waku_enr/sharding, waku_enr/capabilities, - events/peer_events, + api/events/peer_events, common/nimchronos, common/enr, common/callbacks, diff --git a/logos_delivery/waku/node/subscription_manager.nim b/logos_delivery/waku/node/subscription_manager.nim index 15b582ea6..2c18a7b5e 100644 --- a/logos_delivery/waku/node/subscription_manager.nim +++ b/logos_delivery/waku/node/subscription_manager.nim @@ -15,14 +15,14 @@ import waku_filter_v2/common as filter_common, waku_filter_v2/client as filter_client, waku_filter_v2/protocol as filter_protocol, - events/health_events, - events/message_events, - events/peer_events, + api/events/health_events, + api/events/peer_events, requests/health_requests, node/peer_manager, node/health_monitor/topic_health, node/health_monitor/connection_status, ] +import logos_delivery/api/events/kernel_events # MessageSeenEvent {.push raises: [].} diff --git a/logos_delivery/waku/node/waku_metrics.nim b/logos_delivery/waku/node/waku_metrics.nim index 08eecbe5c..30e5e6a2a 100644 --- a/logos_delivery/waku/node/waku_metrics.nim +++ b/logos_delivery/waku/node/waku_metrics.nim @@ -4,7 +4,7 @@ import logos_delivery/waku/compat/option_valueor import chronicles, chronos, metrics, metrics/chronos_httpserver import logos_delivery/waku/ - [net/auto_port, waku_rln_relay/protocol_metrics as rln_metrics, utils/collector], + [net/auto_port, rln/protocol_metrics as rln_metrics, utils/collector], ./peer_manager, ./node_telemetry, ./waku_node @@ -86,13 +86,9 @@ proc startMetricsServer( return ok(started) proc startMetricsServerAndLogging*( - conf: MetricsServerConf, portsShift: uint16 + conf: MetricsServerConf ): Future[Result[StartedMetricsServer, string]] {.async.} = - let started = ( - await ( - startMetricsServer(conf.httpAddress, Port(conf.httpPort.uint16 + portsShift)) - ) - ).valueOr: + let started = (await (startMetricsServer(conf.httpAddress, conf.httpPort))).valueOr: return err("Starting metrics server failed. Continuing in current state:" & $error) if conf.logging: diff --git a/logos_delivery/waku/node/waku_node.nim b/logos_delivery/waku/node/waku_node.nim index af68ef2f8..e386ef18c 100644 --- a/logos_delivery/waku/node/waku_node.nim +++ b/logos_delivery/waku/node/waku_node.nim @@ -52,17 +52,17 @@ import waku_lightpush as lightpush_protocol, waku_enr, waku_peer_exchange, - waku_rln_relay, + rln, common/rate_limit/setting, common/callbacks, common/nimchronos, waku_mix, requests/node_requests, requests/health_requests, - events/health_events, - events/message_events, - events/peer_events, + api/events/health_events, + api/events/peer_events, ], + logos_delivery/api/events/kernel_events, # MessageSeenEvent logos_delivery/waku/discovery/waku_kademlia, logos_delivery/waku/net/[bound_ports, net_config], ./peer_manager, @@ -110,7 +110,7 @@ type wakuStoreTransfer*: SyncTransfer wakuFilter*: waku_filter_v2.WakuFilter wakuFilterClient*: filter_client.WakuFilterClient - wakuRlnRelay*: WakuRLNRelay + rln*: Rln wakuLegacyLightPush*: WakuLegacyLightPush wakuLegacyLightpushClient*: WakuLegacyLightPushClient wakuLightPush*: WakuLightPush @@ -136,6 +136,7 @@ type wakuMix*: WakuMix wakuKademlia*: WakuKademlia ports*: BoundPorts + relayReconnectFut*: Future[void] SubscriptionManager* = ref object of RootObj node*: WakuNode @@ -622,8 +623,9 @@ proc start*(node: WakuNode) {.async.} = ## NOTE: This will dispatch gossipsub start to the WakuRelay.start method override await node.switch.start() - # After switch.start, run custom Logos Delivery relay start logic - await node.reconnectRelayPeers() + # Reconnect to known relay peers in the background; it waits a prune backoff + # and must not block startup. + node.relayReconnectFut = node.reconnectRelayPeers() node.started = true @@ -652,6 +654,10 @@ proc start*(node: WakuNode) {.async.} = proc stop*(node: WakuNode) {.async.} = ## By stopping the switch we are stopping all the underlying mounted protocols + # Cancel the background relay reconnection (may still be in its backoff wait). + if not node.relayReconnectFut.isNil(): + await node.relayReconnectFut.cancelAndWait() + await node.subscriptionManager.stop() node.stopProvidersAndListeners() @@ -664,9 +670,9 @@ proc stop*(node: WakuNode) {.async.} = node.peerManager.stop() - if not node.wakuRlnRelay.isNil(): + if not node.rln.isNil(): try: - await node.wakuRlnRelay.stop() ## this can raise an exception + await node.rln.stop() ## this can raise an exception except Exception: error "exception stopping the node", error = getCurrentExceptionMsg() @@ -686,7 +692,7 @@ proc stop*(node: WakuNode) {.async.} = node.started = false proc isReady*(node: WakuNode): Future[bool] {.async: (raises: [Exception]).} = - if node.wakuRlnRelay == nil: + if node.rln == nil: return true - return await node.wakuRlnRelay.isReady() + return await node.rln.isReady() ## TODO: add other protocol `isReady` checks diff --git a/logos_delivery/waku/node/waku_node/lightpush.nim b/logos_delivery/waku/node/waku_node/lightpush.nim index 10f27e32f..1bb4ddcbb 100644 --- a/logos_delivery/waku/node/waku_node/lightpush.nim +++ b/logos_delivery/waku/node/waku_node/lightpush.nim @@ -30,7 +30,7 @@ import ../../waku_lightpush as lightpush_protocol, ../peer_manager, ../../common/rate_limit/setting, - ../../waku_rln_relay + ../../rln logScope: topics = "waku node lightpush api" @@ -47,15 +47,7 @@ proc mountLegacyLightPush*( return err(MountWithoutRelayError) info "mounting legacy lightpush with relay" - let rlnPeer = - if node.wakuRlnRelay.isNil(): - info "mounting legacy lightpush without rln-relay" - none(WakuRLNRelay) - else: - info "mounting legacy lightpush with rln-relay" - some(node.wakuRlnRelay) - let pushHandler = - legacy_lightpush_protocol.getRelayPushHandler(node.wakuRelay, rlnPeer) + let pushHandler = legacy_lightpush_protocol.getRelayPushHandler(node.wakuRelay) node.wakuLegacyLightPush = WakuLegacyLightPush.new(node.peerManager, node.rng, pushHandler, some(rateLimit)) @@ -90,6 +82,19 @@ proc legacyLightpushPublish*( error "failed to publish message as legacy lightpush not available" return err("Waku lightpush not available") + # toRLNSignal includes the timestamp in the proof input, so the timestamp + # must be fixed before proof generation. The downstream ensureTimestampSet + # in the client publish becomes an idempotent no-op safety net. + let message = ensureTimestampSet(message) + + let rln = + if node.rln.isNil(): + none(Rln) + else: + some(node.rln) + let msgWithProof = (await checkAndGenerateRLNProof(rln, message)).valueOr: + return err("failed call checkAndGenerateRLNProof from lightpush: " & error) + let internalPublish = proc( node: WakuNode, pubsubTopic: PubsubTopic, @@ -115,7 +120,7 @@ proc legacyLightpushPublish*( await node.wakuLegacyLightPush.handleSelfLightPushRequest(pubsubTopic, message) try: if pubsubTopic.isSome(): - return await internalPublish(node, pubsubTopic.get(), message, peer) + return await internalPublish(node, pubsubTopic.get(), msgWithProof, peer) if node.wakuAutoSharding.isNone(): return err("Pubsub topic must be specified when static sharding is enabled") @@ -123,7 +128,7 @@ proc legacyLightpushPublish*( ?node.wakuAutoSharding.get().getShardsFromContentTopics(message.contentTopic) for pubsub, _ in topicMap.pairs: # There's only one pair anyway - return await internalPublish(node, $pubsub, message, peer) + return await internalPublish(node, $pubsub, msgWithProof, peer) except CatchableError: return err(getCurrentExceptionMsg()) @@ -158,14 +163,7 @@ proc mountLightPush*( return err(MountWithoutRelayError) info "mounting lightpush with relay" - let rlnPeer = - if node.wakuRlnRelay.isNil(): - info "mounting lightpush without rln-relay" - none(WakuRLNRelay) - else: - info "mounting lightpush with rln-relay" - some(node.wakuRlnRelay) - let pushHandler = lightpush_protocol.getRelayPushHandler(node.wakuRelay, rlnPeer) + let pushHandler = lightpush_protocol.getRelayPushHandler(node.wakuRelay) node.wakuLightPush = WakuLightPush.new( node.peerManager, node.rng, pushHandler, node.wakuAutoSharding, some(rateLimit) @@ -282,4 +280,18 @@ proc lightpushPublish*( error "lightpush publish error", error = msg return lighpushErrorResult(LightPushErrorCode.INTERNAL_SERVER_ERROR, msg) - return await lightpushPublishHandler(node, pubsubForPublish, message, toPeer, mixify) + # toRLNSignal includes the timestamp in the proof input, so the timestamp + # must be fixed before proof generation. The downstream ensureTimestampSet + # in the client publish becomes an idempotent no-op safety net. + let message = ensureTimestampSet(message) + + let rln = + if node.rln.isNil(): + none(Rln) + else: + some(node.rln) + let msgWithProof = (await checkAndGenerateRLNProof(rln, message)).valueOr: + return lighpushErrorResult(LightPushErrorCode.OUT_OF_RLN_PROOF, error) + + return + await lightpushPublishHandler(node, pubsubForPublish, msgWithProof, toPeer, mixify) diff --git a/logos_delivery/waku/node/waku_node/relay.nim b/logos_delivery/waku/node/waku_node/relay.nim index 57904dc94..18a3f0f59 100644 --- a/logos_delivery/waku/node/waku_node/relay.nim +++ b/logos_delivery/waku/node/waku_node/relay.nim @@ -13,6 +13,7 @@ import libp2p/crypto/crypto, libp2p/protocols/ping, libp2p/protocols/pubsub/gossipsub, + libp2p/protocols/pubsub/pubsub, libp2p/protocols/pubsub/rpc/messages, libp2p/builders, libp2p/transports/tcptransport, @@ -28,12 +29,12 @@ import waku_filter_v2, waku_archive, waku_store_sync, - waku_rln_relay, + rln, node/waku_node, node/subscription_manager, node/peer_manager, - events/message_events, ] +import logos_delivery/api/events/kernel_events # MessageSeenEvent export waku_relay.WakuRelayHandler @@ -184,27 +185,80 @@ proc mountRelay*( ## Waku RLN Relay -proc mountRlnRelay*( +proc setRlnValidator*( node: WakuNode, rlnConf: WakuRlnConfig, spamHandler = none(SpamHandler), registrationHandler = none(RegistrationHandler), ) {.async.} = - info "mounting rln relay" + info "setting rln validator" + + let rln = (await Rln.new(rlnConf, registrationHandler)).valueOr: + raise newException(CatchableError, "failed to set rln validator: " & error) + if (rlnConf.userMessageLimit > rln.groupManager.rlnRelayMaxMessageLimit): + error "rln-user-message-limit can't exceed the MAX_MESSAGE_LIMIT in the rln contract" + + node.rln = rln if node.wakuRelay.isNil(): - raise newException( - CatchableError, "WakuRelay protocol is not mounted, cannot mount WakuRlnRelay" - ) + info "WakuRelay not mounted; RLN validator not set" + return - let rlnRelay = (await WakuRlnRelay.new(rlnConf, registrationHandler)).valueOr: - raise newException(CatchableError, "failed to mount WakuRlnRelay: " & error) - if (rlnConf.userMessageLimit > rlnRelay.groupManager.rlnRelayMaxMessageLimit): - error "rln-relay-user-message-limit can't exceed the MAX_MESSAGE_LIMIT in the rln contract" - let validator = generateRlnValidator(rlnRelay, spamHandler) + ## Bridges RLN's protocol-agnostic message validation into a relay + ## (gossipsub) validator. The core decision is made by + ## `validateMessageAndUpdateLog`; this maps the result to + ## `pubsub.ValidationResult` so the validator can be installed on + ## WakuRelay's validator chain. + proc validator( + topic: string, message: WakuMessage + ): Future[pubsub.ValidationResult] {.async.} = + trace "rln-relay topic validator is called" + rln.clearNullifierLog() + + let msgProof = RateLimitProof.init(message.proof).valueOr: + trace "rln validator reject", error = error + return pubsub.ValidationResult.Reject + + # validate the message and update log + let validationRes = await rln.validateMessageAndUpdateLog(message) + + let + proof = byteutils.toHex(msgProof.proof) + root = inHex(msgProof.merkleRoot) + shareX = inHex(msgProof.shareX) + shareY = inHex(msgProof.shareY) + nullifier = inHex(msgProof.nullifier) + + case validationRes + of Valid: + trace "message validity is verified, relaying", + proof = proof, + root = root, + shareX = shareX, + shareY = shareY, + nullifier = nullifier + waku_rln_valid_messages_total.inc(labelValues = [topic]) + return pubsub.ValidationResult.Accept + of Invalid: + trace "message validity could not be verified, discarding", + proof = proof, + root = root, + shareX = shareX, + shareY = shareY, + nullifier = nullifier + return pubsub.ValidationResult.Reject + of Spam: + trace "A spam message is found! yay! discarding:", + proof = proof, + root = root, + shareX = shareX, + shareY = shareY, + nullifier = nullifier + if spamHandler.isSome(): + let handler = spamHandler.get() + handler(message) + return pubsub.ValidationResult.Reject # register rln validator as default validator info "Registering RLN validator" node.wakuRelay.addValidator(validator, "RLN validation failed") - - node.wakuRlnRelay = rlnRelay diff --git a/logos_delivery/waku/persistency/backend_thread.nim b/logos_delivery/waku/persistency/backend_thread.nim index e32e5c209..28f328729 100644 --- a/logos_delivery/waku/persistency/backend_thread.nim +++ b/logos_delivery/waku/persistency/backend_thread.nim @@ -162,13 +162,13 @@ proc registerProviders(backend: KvBackend, ctx: BrokerContext): Result[void, str return ok() -proc clearProviders(ctx: BrokerContext) = +proc clearProviders(ctx: BrokerContext) {.async.} = KvGet.clearProvider(ctx) KvExists.clearProvider(ctx) KvScan.clearProvider(ctx) KvCount.clearProvider(ctx) KvDelete.clearProvider(ctx) - PersistEvent.dropAllListeners(ctx) + await PersistEvent.dropAllListeners(ctx) # ── thread proc ───────────────────────────────────────────────────────── @@ -217,7 +217,7 @@ proc storageThreadMain(arg: ptr StorageThreadArg) {.thread.} = except CatchableError as e: error "storage thread loop crashed", err = e.msg - clearProviders(arg.ctx) + waitFor clearProviders(arg.ctx) backend.close() # ── lifecycle ─────────────────────────────────────────────────────────── diff --git a/logos_delivery/waku/persistency/persistency.nim b/logos_delivery/waku/persistency/persistency.nim index fca6ad34d..d6ac28a1e 100644 --- a/logos_delivery/waku/persistency/persistency.nim +++ b/logos_delivery/waku/persistency/persistency.nim @@ -272,7 +272,7 @@ proc hasJob*(p: Persistency, jobId: string): bool {.inline.} = proc persist*(t: Job, ops: seq[TxOp]): Future[void] {.async.} = ## Emit a batched persist event. The handler treats >1 ops as a single ## BEGIN IMMEDIATE/COMMIT transaction (see backend_sqlite.applyOps). - await PersistEvent.emit(t.context, PersistEvent(ops: ops)) + PersistEvent.emit(t.context, PersistEvent(ops: ops)) proc persist*(t: Job, op: TxOp): Future[void] {.async.} = await persist(t, @[op]) diff --git a/logos_delivery/waku/requests/health_requests.nim b/logos_delivery/waku/requests/health_requests.nim index 1c9ed4d70..366cdf875 100644 --- a/logos_delivery/waku/requests/health_requests.nim +++ b/logos_delivery/waku/requests/health_requests.nim @@ -1,6 +1,6 @@ import brokers/request_broker -import logos_delivery/waku/api/types +import logos_delivery/api/types import logos_delivery/waku/node/health_monitor/[protocol_health, topic_health, health_report] import logos_delivery/waku/waku_core/topics diff --git a/logos_delivery/waku/rest_api/endpoint/builder.nim b/logos_delivery/waku/rest_api/endpoint/builder.nim index 4610720ee..f9bc1da89 100644 --- a/logos_delivery/waku/rest_api/endpoint/builder.nim +++ b/logos_delivery/waku/rest_api/endpoint/builder.nim @@ -41,7 +41,7 @@ type RestServerConf* = object relayCacheCapacity*: uint32 proc startRestServerEssentials*( - nodeHealthMonitor: NodeHealthMonitor, conf: RestServerConf, portsShift: uint16 + nodeHealthMonitor: NodeHealthMonitor, conf: RestServerConf ): Result[WakuRestServerRef, string] = if restServerNotInstalledTab.isNil: restServerNotInstalledTab = newTable[string, string]() @@ -87,7 +87,7 @@ proc startRestServerEssentials*( none(string) let address = conf.listenAddress - let port = Port(conf.port.uint16 + portsShift) + let port = conf.port let server = ?newRestHttpServer( address, port, diff --git a/logos_delivery/waku/rest_api/endpoint/health/types.nim b/logos_delivery/waku/rest_api/endpoint/health/types.nim index 331460e7a..c32a0c778 100644 --- a/logos_delivery/waku/rest_api/endpoint/health/types.nim +++ b/logos_delivery/waku/rest_api/endpoint/health/types.nim @@ -4,7 +4,8 @@ import logos_delivery/waku/compat/option_valueor import results import chronicles, json_serialization, json_serialization/std/options import ../serdes -import logos_delivery/waku/[waku_node, api/types, node/health_monitor] +import logos_delivery/api/types +import logos_delivery/waku/[waku_node, node/health_monitor] #### Serialization and deserialization diff --git a/logos_delivery/waku/rest_api/endpoint/relay/handlers.nim b/logos_delivery/waku/rest_api/endpoint/relay/handlers.nim index 9075c61df..6b1fdcc69 100644 --- a/logos_delivery/waku/rest_api/endpoint/relay/handlers.nim +++ b/logos_delivery/waku/rest_api/endpoint/relay/handlers.nim @@ -13,7 +13,7 @@ import import ../../../waku_node, ../../../waku_relay/protocol, - ../../../waku_rln_relay, + ../../../rln, ../../../node/waku_node, ../../message_cache, ../../handlers, @@ -167,11 +167,11 @@ proc installRelayApiHandlers*( return RestApiResponse.badRequest($error) # if RLN is mounted, append the proof to the message - if not node.wakuRlnRelay.isNil(): + if not node.rln.isNil(): # append the proof to the message message.proof = ( - await node.wakuRlnRelay.generateRLNProof( + await node.rln.generateRLNProof( message.toRLNSignal(), float64(getTime().toUnix()) ) ).valueOr: @@ -186,8 +186,7 @@ proc installRelayApiHandlers*( logMessageInfo(node.wakuRelay, "rest", pubsubTopic, "none", message, onRecv = true) # if we reach here its either a non-RLN message or a RLN message with a valid proof - info "Publishing message", - pubSubTopic = pubSubTopic, rln = not node.wakuRlnRelay.isNil() + info "Publishing message", pubSubTopic = pubSubTopic, rln = not node.rln.isNil() if not (waitFor node.publish(some(pubSubTopic), message).withTimeout(futTimeout)): error "Failed to publish message to topic", pubSubTopic = pubSubTopic return RestApiResponse.internalServerError("Failed to publish: timedout") @@ -299,9 +298,9 @@ proc installRelayApiHandlers*( return RestApiResponse.badRequest("Failed to publish. " & msg) # if RLN is mounted, append the proof to the message - if not node.wakuRlnRelay.isNil(): + if not node.rln.isNil(): message.proof = ( - await node.wakuRlnRelay.generateRLNProof( + await node.rln.generateRLNProof( message.toRLNSignal(), float64(getTime().toUnix()) ) ).valueOr: @@ -317,7 +316,7 @@ proc installRelayApiHandlers*( # if we reach here its either a non-RLN message or a RLN message with a valid proof info "Publishing message", - contentTopic = message.contentTopic, rln = not node.wakuRlnRelay.isNil() + contentTopic = message.contentTopic, rln = not node.rln.isNil() var publishFut = node.publish(some($pubsubTopic), message) if not await publishFut.withTimeout(futTimeout): diff --git a/logos_delivery/waku/rln.nim b/logos_delivery/waku/rln.nim new file mode 100644 index 000000000..c8aa98857 --- /dev/null +++ b/logos_delivery/waku/rln.nim @@ -0,0 +1,3 @@ +import ./rln/group_manager, ./rln/conversion_utils, ./rln/rln, ./rln/contract + +export group_manager, conversion_utils, rln, contract diff --git a/logos_delivery/waku/waku_rln_relay/rln.nim b/logos_delivery/waku/rln/bindings.nim similarity index 100% rename from logos_delivery/waku/waku_rln_relay/rln.nim rename to logos_delivery/waku/rln/bindings.nim diff --git a/logos_delivery/waku/rln/config.nim b/logos_delivery/waku/rln/config.nim new file mode 100644 index 000000000..4d449f392 --- /dev/null +++ b/logos_delivery/waku/rln/config.nim @@ -0,0 +1,26 @@ +{.push raises: [].} + +import std/options, stint + +import logos_delivery/waku/common/error_handling + +type RlnCreds* {.requiresInit.} = object + path*: string + password*: string + +type RlnConf* = object of RootObj + # TODO: severals parameters are only needed when it's dynamic + # change the config to either nest or use enum/type variant so it's obvious + # and then it can be set to `requiresInit` + dynamic*: bool + credIndex*: Option[uint] + ethContractAddress*: string + ethClientUrls*: seq[string] + chainId*: UInt256 + creds*: Option[RlnCreds] + epochSizeSec*: uint64 + userMessageLimit*: uint64 + ethPrivateKey*: Option[string] + +type WakuRlnConfig* = object of RlnConf + onFatalErrorAction*: OnFatalErrorHandler diff --git a/logos_delivery/waku/waku_rln_relay/constants.nim b/logos_delivery/waku/rln/constants.nim similarity index 98% rename from logos_delivery/waku/waku_rln_relay/constants.nim rename to logos_delivery/waku/rln/constants.nim index 7ede78d43..22454b22b 100644 --- a/logos_delivery/waku/waku_rln_relay/constants.nim +++ b/logos_delivery/waku/rln/constants.nim @@ -23,7 +23,7 @@ const PathCheckMinInterval* = 30.seconds const RlnCredentialsFilename* = "rlnCredentials.txt" # inputs of the membership contract constructor -# TODO may be able to make these constants private and put them inside the waku_rln_relay_utils +# TODO may be able to make these constants private and put them inside the waku_rln_utils const # in wei MembershipFee* = 0.u256 diff --git a/logos_delivery/waku/waku_rln_relay/contract.nim b/logos_delivery/waku/rln/contract.nim similarity index 100% rename from logos_delivery/waku/waku_rln_relay/contract.nim rename to logos_delivery/waku/rln/contract.nim diff --git a/logos_delivery/waku/waku_rln_relay/conversion_utils.nim b/logos_delivery/waku/rln/conversion_utils.nim similarity index 98% rename from logos_delivery/waku/waku_rln_relay/conversion_utils.nim rename to logos_delivery/waku/rln/conversion_utils.nim index fc130621b..2b2ad626f 100644 --- a/logos_delivery/waku/waku_rln_relay/conversion_utils.nim +++ b/logos_delivery/waku/rln/conversion_utils.nim @@ -12,7 +12,7 @@ import ../waku_keystore export web3, chronicles, stint, constants, endians2 logScope: - topics = "waku rln_relay conversion_utils" + topics = "waku rln conversion_utils" proc inHex*( value: diff --git a/logos_delivery/waku/waku_rln_relay/group_manager.nim b/logos_delivery/waku/rln/group_manager.nim similarity index 100% rename from logos_delivery/waku/waku_rln_relay/group_manager.nim rename to logos_delivery/waku/rln/group_manager.nim diff --git a/logos_delivery/waku/waku_rln_relay/group_manager/group_manager_base.nim b/logos_delivery/waku/rln/group_manager/group_manager_base.nim similarity index 99% rename from logos_delivery/waku/waku_rln_relay/group_manager/group_manager_base.nim rename to logos_delivery/waku/rln/group_manager/group_manager_base.nim index 365227a14..21571aa52 100644 --- a/logos_delivery/waku/waku_rln_relay/group_manager/group_manager_base.nim +++ b/logos_delivery/waku/rln/group_manager/group_manager_base.nim @@ -23,7 +23,7 @@ type GroupManager* = ref object of RootObj membershipIndex*: Option[MembershipIndex] registerCb*: Option[OnRegisterCallback] withdrawCb*: Option[OnWithdrawCallback] - rlnInstance*: ptr RLN + rlnInstance*: ptr RlnRaw initialized*: bool latestIndex*: MembershipIndex validRoots*: Deque[MerkleNode] diff --git a/logos_delivery/waku/waku_rln_relay/group_manager/on_chain.nim b/logos_delivery/waku/rln/group_manager/on_chain.nim similarity index 100% rename from logos_delivery/waku/waku_rln_relay/group_manager/on_chain.nim rename to logos_delivery/waku/rln/group_manager/on_chain.nim diff --git a/logos_delivery/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim b/logos_delivery/waku/rln/group_manager/on_chain/group_manager.nim similarity index 99% rename from logos_delivery/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim rename to logos_delivery/waku/rln/group_manager/on_chain/group_manager.nim index 66edcf93d..e316de85b 100644 --- a/logos_delivery/waku/waku_rln_relay/group_manager/on_chain/group_manager.nim +++ b/logos_delivery/waku/rln/group_manager/on_chain/group_manager.nim @@ -17,7 +17,7 @@ import import ../../../waku_keystore, - ../../rln, + ../../bindings, ../../rln/rln_interface, ../../conversion_utils, ../group_manager_base, @@ -27,7 +27,7 @@ import export group_manager_base logScope: - topics = "waku rln_relay onchain_group_manager" + topics = "waku rln onchain_group_manager" type WakuRlnContractWithSender = Sender[WakuRlnContract] diff --git a/logos_delivery/waku/waku_rln_relay/group_manager/on_chain/retry_wrapper.nim b/logos_delivery/waku/rln/group_manager/on_chain/retry_wrapper.nim similarity index 100% rename from logos_delivery/waku/waku_rln_relay/group_manager/on_chain/retry_wrapper.nim rename to logos_delivery/waku/rln/group_manager/on_chain/retry_wrapper.nim diff --git a/logos_delivery/waku/waku_rln_relay/group_manager/on_chain/rpc_wrapper.nim b/logos_delivery/waku/rln/group_manager/on_chain/rpc_wrapper.nim similarity index 80% rename from logos_delivery/waku/waku_rln_relay/group_manager/on_chain/rpc_wrapper.nim rename to logos_delivery/waku/rln/group_manager/on_chain/rpc_wrapper.nim index a82356af8..c5fbc4a01 100644 --- a/logos_delivery/waku/waku_rln_relay/group_manager/on_chain/rpc_wrapper.nim +++ b/logos_delivery/waku/rln/group_manager/on_chain/rpc_wrapper.nim @@ -18,7 +18,7 @@ import ../group_manager_base logScope: - topics = "waku rln_relay onchain rpc_wrapper" + topics = "waku rln onchain rpc_wrapper" # using the when predicate does not work within the contract macro, hence need to dupe contract(WakuRlnContract): @@ -68,9 +68,22 @@ proc sendEthCallWithoutParams*( tx.data = Opt.some(byteutils.hexToSeqByte(dataSignature)) tx.chainId = Opt.some(chainId) - let resultBytes = await ethRpc.provider.eth_call(tx, "latest") + # A revert / rate-limit / dropped connection raises here; return err so RLN + # setup fails gracefully instead of crashing the node. + let resultBytes = + try: + await ethRpc.provider.eth_call(tx, "latest") + except CatchableError as e: + return err("eth_call failed for " & functionSignature & ": " & e.msg) + if resultBytes.len == 0: return err("No result returned for function call: " & functionSignature) + # UInt256.fromBytesBE panics (Defect) on < 32 bytes; reject short responses. + if resultBytes.len != 32: + return err( + "Unexpected result length (" & $resultBytes.len & " bytes) for function call: " & + functionSignature + ) return ok(UInt256.fromBytesBE(resultBytes)) proc sendEthCallWithParams*( @@ -94,5 +107,10 @@ proc sendEthCallWithParams*( tx.data = Opt.some(callData) tx.chainId = Opt.some(chainId) - let resultBytes = await ethRpc.provider.eth_call(tx, "latest") + let resultBytes = + try: + await ethRpc.provider.eth_call(tx, "latest") + except CatchableError as e: + return err("eth_call failed for " & functionSignature & ": " & e.msg) + return ok(resultBytes) diff --git a/logos_delivery/waku/waku_rln_relay/nonce_manager.nim b/logos_delivery/waku/rln/nonce_manager.nim similarity index 100% rename from logos_delivery/waku/waku_rln_relay/nonce_manager.nim rename to logos_delivery/waku/rln/nonce_manager.nim diff --git a/logos_delivery/waku/rln/nullifier_log.nim b/logos_delivery/waku/rln/nullifier_log.nim new file mode 100644 index 000000000..8a1a3cd6c --- /dev/null +++ b/logos_delivery/waku/rln/nullifier_log.nim @@ -0,0 +1,75 @@ +{.push raises: [].} + +import std/tables +import chronicles, results + +import ./types, ./protocol_types, ./conversion_utils, ./proof + +logScope: + topics = "waku rln nullifier_log" + +proc hasDuplicate*( + rlnPeer: Rln, epoch: Epoch, proofMetadata: ProofMetadata +): Result[bool, string] = + ## returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same + ## epoch and nullifier as `proofMetadata`'s epoch and nullifier + ## otherwise, returns false + ## Returns an error if it cannot check for duplicates + + # check if the epoch exists + let nullifier = proofMetadata.nullifier + if not rlnPeer.nullifierLog.hasKey(epoch): + return ok(false) + try: + if rlnPeer.nullifierLog[epoch].hasKey(nullifier): + # there is an identical record, mark it as spam + return ok(true) + + # there is no duplicate + return ok(false) + except KeyError: + return err("the epoch was not found: " & getCurrentExceptionMsg()) + +proc updateLog*( + rlnPeer: Rln, epoch: Epoch, proofMetadata: ProofMetadata +): Result[void, string] = + ## saves supplied proofMetadata `proofMetadata` + ## in the `nullifierLog` of the `rlnPeer` + ## Returns an error if it cannot update the log + + # check if the epoch exists + if not rlnPeer.nullifierLog.hasKeyOrPut( + epoch, {proofMetadata.nullifier: proofMetadata}.toTable() + ): + return ok() + + try: + # check if an identical record exists + if rlnPeer.nullifierLog[epoch].hasKeyOrPut(proofMetadata.nullifier, proofMetadata): + # the above condition could be `discarded` but it is kept for clarity, that slashing will + # be implemented here + # TODO: slashing logic + return ok() + return ok() + except KeyError: + return + err("the epoch was not found: " & getCurrentExceptionMsg()) # should never happen + +proc clearNullifierLog*(rlnPeer: Rln) = + # clear the first MaxEpochGap epochs of the nullifer log + # if more than MaxEpochGap epochs are in the log + let currentEpoch = fromEpoch(rlnPeer.getCurrentEpoch()) + + var epochsToRemove: seq[Epoch] = @[] + for epoch in rlnPeer.nullifierLog.keys(): + let epochInt = fromEpoch(epoch) + + # clean all epochs that are +- rlnMaxEpochGap from the current epoch + if (currentEpoch + rlnPeer.rlnMaxEpochGap) <= epochInt or + epochInt <= (currentEpoch - rlnPeer.rlnMaxEpochGap): + epochsToRemove.add(epoch) + + for epochRemove in epochsToRemove: + trace "clearing epochs from the nullifier log", + currentEpoch = currentEpoch, cleanedEpoch = fromEpoch(epochRemove) + rlnPeer.nullifierLog.del(epochRemove) diff --git a/logos_delivery/waku/rln/proof.nim b/logos_delivery/waku/rln/proof.nim new file mode 100644 index 000000000..fbbe2292e --- /dev/null +++ b/logos_delivery/waku/rln/proof.nim @@ -0,0 +1,85 @@ +{.push raises: [].} + +import std/[options, times, sequtils] +import chronos, chronicles, results, stew/byteutils + +import ./types, ./protocol_types, ./conversion_utils, ./group_manager, ./nonce_manager + +import logos_delivery/waku/waku_core + +proc calcEpoch*(rln: Rln, t: float64): Epoch = + ## gets time `t` as `flaot64` with subseconds resolution in the fractional part + ## and returns its corresponding rln `Epoch` value + + let e = uint64(t / rln.rlnEpochSizeSec.float64) + return toEpoch(e) + +proc nextEpoch*(rln: Rln, time: float64): float64 = + let + currentEpoch = uint64(time / rln.rlnEpochSizeSec.float64) + nextEpochTime = float64(currentEpoch + 1) * rln.rlnEpochSizeSec.float64 + currentTime = epochTime() + + # Ensure we always return a future time + if nextEpochTime > currentTime: + return nextEpochTime + else: + return epochTime() + +proc getCurrentEpoch*(rln: Rln): Epoch = + return rln.calcEpoch(epochTime()) + +proc absDiff*(e1, e2: Epoch): uint64 = + ## returns the absolute difference between the two rln `Epoch`s `e1` and `e2` + ## i.e., e1 - e2 + + # convert epochs to their corresponding unsigned numerical values + let + epoch1 = fromEpoch(e1) + epoch2 = fromEpoch(e2) + + # Manually perform an `abs` calculation + if epoch1 > epoch2: + return epoch1 - epoch2 + else: + return epoch2 - epoch1 + +proc toRLNSignal*(wakumessage: WakuMessage): seq[byte] = + ## it is a utility proc that prepares the `data` parameter of the proof generation procedure i.e., `proofGen` that resides in the current module + ## it extracts the `contentTopic`, `timestamp` and the `payload` of the supplied `wakumessage` and serializes them into a byte sequence + + let + contentTopicBytes = toBytes(wakumessage.contentTopic) + timestampBytes = toBytes(wakumessage.timestamp.uint64) + output = concat(wakumessage.payload, contentTopicBytes, @(timestampBytes)) + return output + +proc generateRLNProof*( + rln: Rln, input: seq[byte], senderEpochTime: float64 +): Future[Result[seq[byte], string]] {.async.} = + let epoch = rln.calcEpoch(senderEpochTime) + let nonce = rln.nonceManager.getNonce().valueOr: + return err("could not get new message id to generate an rln proof: " & $error) + let proof = (await rln.groupManager.generateProof(input, epoch, nonce)).valueOr: + return err("could not generate rln-v2 proof: " & $error) + return ok(proof.encode().buffer) + +proc checkAndGenerateRLNProof*( + rln: Option[Rln], message: WakuMessage +): Future[Result[WakuMessage, string]] {.async.} = + if message.proof.len > 0: + return ok(message) + + if rln.isNone(): + notice "Publishing message without RLN proof" + return ok(message) + + let + time = getTime().toUnix() + senderEpochTime = float64(time) + var msgWithProof = message + msgWithProof.proof = ( + await rln.get().generateRLNProof(msgWithProof.toRLNSignal(), senderEpochTime) + ).valueOr: + return err("error in checkAndGenerateRLNProof: " & $error) + return ok(msgWithProof) diff --git a/logos_delivery/waku/waku_rln_relay/protocol_metrics.nim b/logos_delivery/waku/rln/protocol_metrics.nim similarity index 99% rename from logos_delivery/waku/waku_rln_relay/protocol_metrics.nim rename to logos_delivery/waku/rln/protocol_metrics.nim index 2cea329fe..fa034aa8e 100644 --- a/logos_delivery/waku/waku_rln_relay/protocol_metrics.nim +++ b/logos_delivery/waku/rln/protocol_metrics.nim @@ -5,7 +5,7 @@ import chronicles, metrics, metrics/chronos_httpserver, ../utils/collector export metrics logScope: - topics = "waku rln_relay" + topics = "waku rln" func generateBucketsForHistogram*(length: int): seq[float64] = ## Generate a custom set of 5 buckets for a given length diff --git a/logos_delivery/waku/waku_rln_relay/protocol_types.nim b/logos_delivery/waku/rln/protocol_types.nim similarity index 92% rename from logos_delivery/waku/waku_rln_relay/protocol_types.nim rename to logos_delivery/waku/rln/protocol_types.nim index 867878886..66aa72f53 100644 --- a/logos_delivery/waku/waku_rln_relay/protocol_types.nim +++ b/logos_delivery/waku/rln/protocol_types.nim @@ -5,11 +5,8 @@ import ../waku_core, ../waku_keystore, ../common/protobuf export waku_keystore, waku_core -type RlnRelayResult*[T] = Result[T, string] - ## RLN is a Nim wrapper for the data types used in zerokit RLN -type RLN* {.incompleteStruct.} = object -type RLNResult* = RlnRelayResult[ptr RLN] +type RlnRaw* {.incompleteStruct.} = object type MerkleNode* = array[32, byte] @@ -72,14 +69,10 @@ type ProofMetadata* = object shareY*: MerkleNode externalNullifier*: Nullifier -type - MessageValidationResult* {.pure.} = enum - Valid - Invalid - Spam - - MerkleNodeResult* = RlnRelayResult[MerkleNode] - RateLimitProofResult* = RlnRelayResult[RateLimitProof] +type MessageValidationResult* {.pure.} = enum + Valid + Invalid + Spam # Protobufs enc and init proc init*(T: type RateLimitProof, buffer: seq[byte]): ProtoResult[T] = diff --git a/logos_delivery/waku/rln/rln.nim b/logos_delivery/waku/rln/rln.nim new file mode 100644 index 000000000..ec20cf0c8 --- /dev/null +++ b/logos_delivery/waku/rln/rln.nim @@ -0,0 +1,268 @@ +import logos_delivery/waku/compat/option_valueor +{.push raises: [].} + +import + std/[sequtils, tables, times, deques], + chronicles, + options, + chronos, + stint, + web3, + json, + web3/eth_api_types, + eth/keys, + results, + stew/[byteutils, arrayops], + brokers/broker_context + +import + ./group_manager, + ./bindings, + ./conversion_utils, + ./constants, + ./protocol_types, + ./protocol_metrics, + ./nonce_manager, + ./types, + ./config, + ./proof, + ./nullifier_log + +import + logos_delivery/waku/ + [common/error_handling, waku_core, requests/rln_requests, waku_keystore] + +# Re-export the submodules so existing `import rln` +# (and `import rln/rln`) callers see the moved symbols +# (Rln, WakuRlnConfig, generateRLNProof, etc.). +export types, config, proof, nullifier_log + +logScope: + topics = "waku rln" + +proc stop*(rlnPeer: Rln) {.async: (raises: [Exception]).} = + ## stops the rln-relay protocol + ## Throws an error if it cannot stop the rln-relay protocol + + # stop the group sync, and flush data to tree db + info "stopping rln-relay" + RequestGenerateRlnProof.clearProvider(rlnPeer.brokerCtx) + await rlnPeer.groupManager.stop() + +proc validateMessage*( + rlnPeer: Rln, msg: WakuMessage +): Future[MessageValidationResult] {.async.} = + ## validate the supplied `msg` based on the waku-rln-relay routing protocol i.e., + ## the `msg`'s epoch is within MaxEpochGap of the current epoch + ## the `msg` has valid rate limit proof + ## the `msg` does not violate the rate limit + ## `timeOption` indicates Unix epoch time (fractional part holds sub-seconds) + ## if `timeOption` is supplied, then the current epoch is calculated based on that + + let proof = RateLimitProof.init(msg.proof).valueOr: + return MessageValidationResult.Invalid + + # track message count for metrics + waku_rln_messages_total.inc() + + # checks if the message's timestamp is within acceptable range + let currentTime = getTime().toUnixFloat() + let messageTime = msg.timestamp.float64 / 1e9 + + let timeDiff = uint64(abs(currentTime - messageTime)) + + info "time info", + currentTime = currentTime, messageTime = messageTime, msgHash = msg.hash + + if timeDiff > rlnPeer.rlnMaxTimestampGap: + warn "invalid message: timestamp difference exceeds threshold", + timeDiff = timeDiff, + maxTimestampGap = rlnPeer.rlnMaxTimestampGap, + contentTopic = msg.contentTopic + waku_rln_invalid_messages_total.inc(labelValues = ["invalid_timestamp"]) + return MessageValidationResult.Invalid + + let computedEpoch = rlnPeer.calcEpoch(messageTime) + if proof.epoch != computedEpoch: + warn "invalid message: timestamp mismatches epoch", + proofEpoch = fromEpoch(proof.epoch), + computedEpoch = fromEpoch(computedEpoch), + contentTopic = msg.contentTopic + waku_rln_invalid_messages_total.inc(labelValues = ["timestamp_mismatch"]) + return MessageValidationResult.Invalid + + let rootValidationRes = await rlnPeer.groupManager.validateRoot(proof.merkleRoot) + if not rootValidationRes: + warn "invalid message: provided root does not belong to acceptable window of roots", + provided = proof.merkleRoot.inHex(), + validRoots = rlnPeer.groupManager.validRoots.mapIt(it.inHex()), + contentTopic = msg.contentTopic + waku_rln_invalid_messages_total.inc(labelValues = ["invalid_root"]) + return MessageValidationResult.Invalid + + # verify the proof + let + contentTopicBytes = toBytes(msg.contentTopic) + timestampBytes = toBytes(msg.timestamp.uint64) + input = concat(msg.payload, contentTopicBytes, @(timestampBytes)) + + waku_rln_proof_verification_total.inc() + waku_rln_proof_verification_duration_seconds.nanosecondTime: + let proofVerificationRes = + rlnPeer.groupManager.verifyProof(msg.toRLNSignal(), proof) + + proofVerificationRes.isOkOr: + waku_rln_errors_total.inc(labelValues = ["proof_verification"]) + warn "invalid message: proof verification failed", + payloadLen = msg.payload.len, contentTopic = msg.contentTopic + return MessageValidationResult.Invalid + + if not proofVerificationRes.value(): + # invalid proof + warn "invalid message: invalid proof", + payloadLen = msg.payload.len, contentTopic = msg.contentTopic + waku_rln_invalid_messages_total.inc(labelValues = ["invalid_proof"]) + return MessageValidationResult.Invalid + + # check if double messaging has happened + let proofMetadata = proof.extractMetadata().valueOr: + waku_rln_errors_total.inc(labelValues = ["proof_metadata_extraction"]) + return MessageValidationResult.Invalid + + let msgEpoch = proof.epoch + let hasDup = rlnPeer.hasDuplicate(msgEpoch, proofMetadata) + if hasDup.isErr(): + waku_rln_errors_total.inc(labelValues = ["duplicate_check"]) + elif hasDup.value == true: + trace "invalid message: message is spam", + payloadLen = msg.payload.len, contentTopic = msg.contentTopic + waku_rln_spam_messages_total.inc() + return MessageValidationResult.Spam + + trace "message is valid", + payloadLen = msg.payload.len, contentTopic = msg.contentTopic + # Metric increment moved to validator to include shard label + return MessageValidationResult.Valid + +proc validateMessageAndUpdateLog*( + rlnPeer: Rln, msg: WakuMessage +): Future[MessageValidationResult] {.async.} = + ## validates the message and updates the log to prevent double messaging + ## in future messages + + let isValidMessage = await rlnPeer.validateMessage(msg) + + let msgProof = RateLimitProof.init(msg.proof).valueOr: + return MessageValidationResult.Invalid + + let proofMetadata = msgProof.extractMetadata().valueOr: + return MessageValidationResult.Invalid + + # insert the message to the log (never errors) only if the + # message is valid. + if isValidMessage == MessageValidationResult.Valid: + discard rlnPeer.updateLog(msgProof.epoch, proofMetadata) + + return isValidMessage + +proc monitorEpochs(rln: Rln) {.async.} = + while true: + try: + if rln.groupManager.userMessageLimit.isSome(): + waku_rln_remaining_proofs_per_epoch.set( + rln.groupManager.userMessageLimit.get().float64 + ) + else: + error "userMessageLimit is not set in monitorEpochs" + except CatchableError: + error "Error in epoch monitoring", error = getCurrentExceptionMsg() + + let nextEpochTime = rln.nextEpoch(epochTime()) + let sleepDuration = int((nextEpochTime - epochTime()) * 1000) + await sleepAsync(sleepDuration) + +proc mount( + conf: WakuRlnConfig, registrationHandler = none(RegistrationHandler) +): Future[Result[Rln, string]] {.async.} = + var + groupManager: GroupManager + rln: Rln + # create an RLN instance + let rlnInstance = createRLNInstance().valueOr: + return err("could not create RLN instance: " & $error) + + let (rlnRelayCredPath, rlnRelayCredPassword) = + if conf.creds.isSome: + (some(conf.creds.get().path), some(conf.creds.get().password)) + else: + (none(string), none(string)) + + groupManager = OnchainGroupManager( + userMessageLimit: some(conf.userMessageLimit), + ethClientUrls: conf.ethClientUrls, + ethContractAddress: $conf.ethContractAddress, + chainId: conf.chainId, + rlnInstance: rlnInstance, + registrationHandler: registrationHandler, + keystorePath: rlnRelayCredPath, + keystorePassword: rlnRelayCredPassword, + ethPrivateKey: conf.ethPrivateKey, + membershipIndex: conf.credIndex, + onFatalErrorAction: conf.onFatalErrorAction, + ) + + # Initialize the groupManager + (await groupManager.init()).isOkOr: + return err("could not initialize the group manager: " & $error) + + rln = Rln( + groupManager: groupManager, + nonceManager: NonceManager.init(conf.userMessageLimit, conf.epochSizeSec.float), + rlnEpochSizeSec: conf.epochSizeSec, + rlnMaxEpochGap: max(uint64(MaxClockGapSeconds / float64(conf.epochSizeSec)), 1), + rlnMaxTimestampGap: uint64(MaxClockGapSeconds), + onFatalErrorAction: conf.onFatalErrorAction, + brokerCtx: globalBrokerContext(), + ) + + # Start epoch monitoring in the background + rln.epochMonitorFuture = monitorEpochs(rln) + + RequestGenerateRlnProof.setProvider( + rln.brokerCtx, + proc( + msg: WakuMessage, senderEpochTime: float64 + ): Future[Result[RequestGenerateRlnProof, string]] {.async.} = + let proof = (await rln.generateRLNProof(msg.toRLNSignal(), senderEpochTime)).valueOr: + return err("Could not create RLN proof: " & error) + + return ok(RequestGenerateRlnProof(proof: proof)), + ).isOkOr: + return err("Proof generator provider cannot be set: " & $error) + + return ok(rln) + +proc isReady*(rlnPeer: Rln): Future[bool] {.async.} = + ## returns true if the rln-relay protocol is ready to relay messages + ## returns false otherwise + + # could be nil during startup + if rlnPeer.groupManager == nil: + return false + try: + return await rlnPeer.groupManager.isReady() + except CatchableError: + error "could not check if the rln-relay protocol is ready", + err = getCurrentExceptionMsg() + return false + +proc new*( + T: type Rln, conf: WakuRlnConfig, registrationHandler = none(RegistrationHandler) +): Future[Result[Rln, string]] {.async.} = + ## Mounts the rln-relay protocol on the node. + ## The rln-relay protocol can be mounted in two modes: on-chain and off-chain. + ## Returns an error if the rln-relay protocol could not be mounted. + try: + return await mount(conf, registrationHandler) + except CatchableError: + return err("could not mount the rln-relay protocol: " & getCurrentExceptionMsg()) diff --git a/logos_delivery/waku/waku_rln_relay/rln/rln_interface.nim b/logos_delivery/waku/rln/rln/rln_interface.nim similarity index 94% rename from logos_delivery/waku/waku_rln_relay/rln/rln_interface.nim rename to logos_delivery/waku/rln/rln/rln_interface.nim index 00d1fc0ba..d9b2fc903 100644 --- a/logos_delivery/waku/waku_rln_relay/rln/rln_interface.nim +++ b/logos_delivery/waku/rln/rln/rln_interface.nim @@ -51,7 +51,7 @@ type err*: Vec_uint8 CResultRLNPtrVecU8* = object - ok*: ptr RLN + ok*: ptr RlnRaw err*: Vec_uint8 CResultCFrPtrVecU8* = object @@ -99,7 +99,7 @@ proc ffi_rln_new_with_params*( zkey_data: ptr Vec_uint8, graph_data: ptr Vec_uint8 ): CResultRLNPtrVecU8 {.importc: "ffi_rln_new_with_params", cdecl.} -proc ffi_rln_free*(rln: ptr RLN) {.importc: "ffi_rln_free", cdecl.} +proc ffi_rln_free*(rln: ptr RlnRaw) {.importc: "ffi_rln_free", cdecl.} # --- Keygen --------------------------------------------------------------- @@ -143,15 +143,15 @@ proc ffi_rln_partial_witness_input_free*( # pass `addr handle` where `handle` is `ptr T`. proc ffi_generate_rln_proof*( - rln: ptr ptr RLN, witness: ptr ptr FFI_RLNWitnessInput + rln: ptr ptr RlnRaw, witness: ptr ptr FFI_RLNWitnessInput ): CResultProofPtrVecU8 {.importc: "ffi_generate_rln_proof", cdecl.} proc ffi_generate_partial_zk_proof*( - rln: ptr ptr RLN, partial_witness: ptr ptr FFI_RLNPartialWitnessInput + rln: ptr ptr RlnRaw, partial_witness: ptr ptr FFI_RLNPartialWitnessInput ): CResultPartialProofPtrVecU8 {.importc: "ffi_generate_partial_zk_proof", cdecl.} proc ffi_finish_rln_proof*( - rln: ptr ptr RLN, + rln: ptr ptr RlnRaw, partial_proof: ptr ptr FFI_RLNPartialProof, witness: ptr ptr FFI_RLNWitnessInput, ): CResultProofPtrVecU8 {.importc: "ffi_finish_rln_proof", cdecl.} @@ -159,7 +159,7 @@ proc ffi_finish_rln_proof*( # --- Verification --------------------------------------------------------- proc ffi_verify_with_roots*( - rln: ptr ptr RLN, proof: ptr ptr FFI_RLNProof, roots: ptr Vec_CFr, x: ptr CFr + rln: ptr ptr RlnRaw, proof: ptr ptr FFI_RLNProof, roots: ptr Vec_CFr, x: ptr CFr ): CBoolResult {.importc: "ffi_verify_with_roots", cdecl.} # --- Proof serialization -------------------------------------------------- @@ -318,14 +318,14 @@ proc vecToSeq*(data: Vec_uint8): seq[byte] = if result.len > 0: copyMem(addr result[0], data.dataPtr, result.len) -proc seqToFixed32*(data: openArray[byte]): RlnRelayResult[array[32, byte]] = +proc seqToFixed32*(data: openArray[byte]): Result[array[32, byte], string] = if data.len != FieldElementSize: return err("Expected 32 bytes, got " & $data.len) var output: array[32, byte] copyMem(addr output[0], unsafeAddr data[0], FieldElementSize) ok(output) -proc cfrToBytesLe*(cfr: ptr CFr): RlnRelayResult[array[32, byte]] = +proc cfrToBytesLe*(cfr: ptr CFr): Result[array[32, byte], string] = let bytes = ffi_cfr_to_bytes_le(cfr) defer: ffi_vec_u8_free(bytes) @@ -333,7 +333,7 @@ proc cfrToBytesLe*(cfr: ptr CFr): RlnRelayResult[array[32, byte]] = return err("Invalid field byte length: " & $bytes.len) seqToFixed32(vecToSeq(bytes)) -proc bytesToCfrLe*(data: openArray[byte]): RlnRelayResult[ptr CFr] = +proc bytesToCfrLe*(data: openArray[byte]): Result[ptr CFr, string] = ## Allocate a ptr CFr from raw bytes. Caller MUST ffi_cfr_free(x). var vec = toVecUint8(data) let res = ffi_bytes_le_to_cfr(addr vec) @@ -343,7 +343,7 @@ proc bytesToCfrLe*(data: openArray[byte]): RlnRelayResult[ptr CFr] = proc cfrResultToBytes*( res: CResultCFrPtrVecU8, prefix: string -): RlnRelayResult[array[32, byte]] = +): Result[array[32, byte], string] = ## Consume a CResultCFrPtrVecU8: read bytes if ok, free the CFr, or ## propagate the error (also freeing the error string). if res.ok.isNil: @@ -352,7 +352,7 @@ proc cfrResultToBytes*( ffi_cfr_free(res.ok) cfrToBytesLe(res.ok) -proc hashToFieldLe*(data: openArray[byte]): RlnRelayResult[ptr CFr] = +proc hashToFieldLe*(data: openArray[byte]): Result[ptr CFr, string] = ## Caller MUST ffi_cfr_free the returned ptr. var vec = toVecUint8(data) let cfr = ffi_hash_to_field_le(addr vec) @@ -360,7 +360,7 @@ proc hashToFieldLe*(data: openArray[byte]): RlnRelayResult[ptr CFr] = return err("Failed to hash to field") ok(cfr) -proc poseidonPairLe*(a, b: openArray[byte]): RlnRelayResult[array[32, byte]] = +proc poseidonPairLe*(a, b: openArray[byte]): Result[array[32, byte], string] = ## Poseidon hash of exactly two 32-byte field elements (little-endian). ## zerokit v2 FFI only exposes pair-input Poseidon; unary is not supported. let aPtr = bytesToCfrLe(a).valueOr: diff --git a/logos_delivery/waku/waku_rln_relay/rln/wrappers.nim b/logos_delivery/waku/rln/rln/wrappers.nim similarity index 92% rename from logos_delivery/waku/waku_rln_relay/rln/wrappers.nim rename to logos_delivery/waku/rln/rln/wrappers.nim index 611deeb5d..4f46fc164 100644 --- a/logos_delivery/waku/waku_rln_relay/rln/wrappers.nim +++ b/logos_delivery/waku/rln/rln/wrappers.nim @@ -7,14 +7,14 @@ import ../../waku_core, ../../waku_keystore {.push raises: [], gcsafe.} logScope: - topics = "waku rln_relay ffi" + topics = "waku rln ffi" # Forward decl; body defined below. proc generateExternalNullifier*( epoch: Epoch, rlnIdentifier: RlnIdentifier -): RlnRelayResult[ExternalNullifier] +): Result[ExternalNullifier, string] -proc toRootVec(validRoots: seq[MerkleNode]): RlnRelayResult[Vec_CFr] = +proc toRootVec(validRoots: seq[MerkleNode]): Result[Vec_CFr, string] = ## Caller MUST ffi_vec_cfr_free the returned Vec_CFr. var roots = ffi_vec_cfr_new(csize_t(validRoots.len)) for root in validRoots: @@ -27,7 +27,7 @@ proc toRootVec(validRoots: seq[MerkleNode]): RlnRelayResult[Vec_CFr] = proc proofPtrToRateLimitProof( proofPtr: ptr FFI_RLNProof, epoch: Epoch, rlnIdentifier: RlnIdentifier -): RlnRelayResult[RateLimitProof] = +): Result[RateLimitProof, string] = var proofHandle = proofPtr let proofBytesRes = ffi_rln_proof_to_bytes_le(addr proofHandle) if hasError(proofBytesRes.err): @@ -93,7 +93,7 @@ proc proofPtrToRateLimitProof( ok(output) -proc parseCredentialVec(vec: var Vec_CFr): RlnRelayResult[IdentityCredential] = +proc parseCredentialVec(vec: var Vec_CFr): Result[IdentityCredential, string] = ## Vec_CFr order: idTrapdoor, idNullifier, idSecretHash, idCommitment. if int(ffi_vec_cfr_len(addr vec)) != 4: return err("Unexpected credential element count") @@ -120,13 +120,13 @@ proc parseCredentialVec(vec: var Vec_CFr): RlnRelayResult[IdentityCredential] = ) ) -proc membershipKeyGen*(): RlnRelayResult[IdentityCredential] = +proc membershipKeyGen*(): Result[IdentityCredential, string] = var vec = ffi_extended_key_gen() defer: ffi_vec_cfr_free(vec) parseCredentialVec(vec) -proc createRLNInstanceLocal(): RLNResult = +proc createRLNInstanceLocal(): Result[ptr RlnRaw, string] = ## Creates a stateless RLN instance (no local Merkle tree). let res = ffi_rln_new() if res.ok.isNil(): @@ -135,18 +135,18 @@ proc createRLNInstanceLocal(): RLNResult = return err(msg) ok(res.ok) -proc createRLNInstance*(): RLNResult = +proc createRLNInstance*(): Result[ptr RlnRaw, string] = ## Wraps createRLNInstanceLocal with metrics timing. - var res: RLNResult + var res: Result[ptr RlnRaw, string] waku_rln_instance_creation_duration_seconds.nanosecondTime: res = createRLNInstanceLocal() return res -proc poseidon*(left, right: seq[byte]): RlnRelayResult[array[32, byte]] = +proc poseidon*(left, right: seq[byte]): Result[array[32, byte], string] = ## Poseidon hash of exactly 2 inputs; zerokit v2 FFI only exposes the pair variant. poseidonPairLe(left, right) -proc toLeaf*(rateCommitment: RateCommitment): RlnRelayResult[seq[byte]] = +proc toLeaf*(rateCommitment: RateCommitment): Result[seq[byte], string] = let idCommitment = rateCommitment.idCommitment var userMessageLimit: array[32, byte] try: @@ -164,7 +164,7 @@ proc toLeaf*(rateCommitment: RateCommitment): RlnRelayResult[seq[byte]] = retLeaf[i] = leaf[i] return ok(retLeaf) -proc toLeaves*(rateCommitments: seq[RateCommitment]): RlnRelayResult[seq[seq[byte]]] = +proc toLeaves*(rateCommitments: seq[RateCommitment]): Result[seq[seq[byte]], string] = var leaves = newSeq[seq[byte]]() for rateCommitment in rateCommitments: let leaf = toLeaf(rateCommitment).valueOr: @@ -174,7 +174,7 @@ proc toLeaves*(rateCommitments: seq[RateCommitment]): RlnRelayResult[seq[seq[byt proc generateExternalNullifier*( epoch: Epoch, rlnIdentifier: RlnIdentifier -): RlnRelayResult[ExternalNullifier] = +): Result[ExternalNullifier, string] = ## externalNullifier = Poseidon(H(epoch), H(rlnIdentifier)); H = ffi_hash_to_field_le. let epochFr = hashToFieldLe(@epoch).valueOr: return err("Failed to hash epoch to field: " & error) @@ -194,7 +194,7 @@ proc generateExternalNullifier*( "Failed to serialize external nullifier: " & e ) -proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] = +proc extractMetadata*(proof: RateLimitProof): Result[ProofMetadata, string] = let externalNullifier = generateExternalNullifier(proof.epoch, proof.rlnIdentifier).valueOr: return err("Failed to compute external nullifier: " & error) return ok( @@ -208,7 +208,7 @@ proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] = proc buildPathElementsVec( pathElements: seq[byte], depth: int -): RlnRelayResult[Vec_CFr] = +): Result[Vec_CFr, string] = ## Caller MUST ffi_vec_cfr_free the returned Vec_CFr. var vec = ffi_vec_cfr_new(csize_t(depth)) for i in 0 ..< depth: @@ -226,7 +226,7 @@ proc buildPathElementsVec( proc buildWitnessInput( witness: RLNWitnessInput -): RlnRelayResult[ptr FFI_RLNWitnessInput] = +): Result[ptr FFI_RLNWitnessInput, string] = ## ffi_rln_witness_input_new copies all inputs, so the intermediate CFrs/vecs ## are freed here. Caller MUST ffi_rln_witness_input_free the returned handle. let depth = witness.identity_path_index.len @@ -287,11 +287,11 @@ proc buildWitnessInput( return ok(witnessRes.ok) proc generateRlnProofWithWitness*( - rlnInstance: ptr RLN, + rlnInstance: ptr RlnRaw, witness: RLNWitnessInput, epoch: Epoch, rlnIdentifier: RlnIdentifier, -): RlnRelayResult[RateLimitProof] = +): Result[RateLimitProof, string] = let witnessHandle = buildWitnessInput(witness).valueOr: return err("failed call to buildWitnessInput in generateRlnProofWithWitness: " & error) @@ -310,7 +310,7 @@ proc generateRlnProofWithWitness*( proc buildRlnProof( proof: RateLimitProof, externalNullifier: ExternalNullifier -): RlnRelayResult[ptr FFI_RLNProof] = +): Result[ptr FFI_RLNProof, string] = ## ffi_rln_proof_new copies all inputs, so the intermediate CFrs are freed ## here. Caller MUST ffi_rln_proof_free the returned handle. var groth16Vec = toVecUint8(proof.proof) @@ -345,11 +345,11 @@ proc buildRlnProof( return ok(proofRes.ok) proc verifyRlnProof*( - rlnInstance: ptr RLN, + rlnInstance: ptr RlnRaw, proof: RateLimitProof, signal: openArray[byte], validRoots: seq[MerkleNode], -): RlnRelayResult[bool] = +): Result[bool, string] = if validRoots.len == 0: return err("verifyRlnProof requires at least one valid root (stateless mode)") diff --git a/logos_delivery/waku/rln/types.nim b/logos_delivery/waku/rln/types.nim new file mode 100644 index 000000000..f89d10c76 --- /dev/null +++ b/logos_delivery/waku/rln/types.nim @@ -0,0 +1,22 @@ +{.push raises: [].} + +import std/tables, chronos, results +import brokers/broker_context + +import ./group_manager, ./nonce_manager, ./protocol_types + +import logos_delivery/waku/common/error_handling + +type Rln* = ref object of RootObj + # the log of nullifiers and Shamir shares of the past messages grouped per epoch + nullifierLog*: OrderedTable[Epoch, Table[Nullifier, ProofMetadata]] + lastEpoch*: Epoch # the epoch of the last published rln message + rlnEpochSizeSec*: uint64 + rlnMaxTimestampGap*: uint64 + rlnMaxEpochGap*: uint64 + groupManager*: GroupManager + onFatalErrorAction*: OnFatalErrorHandler + nonceManager*: NonceManager + epochMonitorFuture*: Future[void] + rootChangesFuture*: Future[Result[void, string]] + brokerCtx*: BrokerContext diff --git a/logos_delivery/waku/factory/waku.nim b/logos_delivery/waku/waku.nim similarity index 83% rename from logos_delivery/waku/factory/waku.nim rename to logos_delivery/waku/waku.nim index e15a92fd0..29290483f 100644 --- a/logos_delivery/waku/factory/waku.nim +++ b/logos_delivery/waku/waku.nim @@ -8,9 +8,9 @@ import chronos, libp2p/protocols/connectivity/relay/relay, libp2p/protocols/connectivity/relay/client, - libp2p/wire, libp2p/crypto/crypto, libp2p/protocols/pubsub/gossipsub, + libp2p/protocols/ping, libp2p/services/autorelayservice, libp2p/services/hpservice, libp2p/peerid, @@ -20,17 +20,18 @@ import metrics, metrics/chronos_httpserver, brokers/broker_context, + logos_delivery/api/types, + logos_delivery/api/kernel_api, logos_delivery/waku/[ waku_core, waku_node, waku_archive, - waku_rln_relay, + rln, waku_store, waku_filter_v2, waku_relay/protocol, waku_enr/sharding, waku_enr/multiaddr, - api/types, common/logging, node/peer_manager, node/health_monitor, @@ -48,11 +49,17 @@ import factory/internal_config, factory/app_callbacks, persistency/persistency, + factory/validator_signed, + waku_lightpush/client, + waku_lightpush_legacy/client, + waku_store/client, ], - logos_delivery/channels/reliable_channel_manager, - logos_delivery/messaging/messaging_client, - ./waku_conf, - ./waku_state_info + ./factory/waku_conf, + ./factory/waku_state_info + +# Surfaces the Kernel API interface to consumers of the Waku layer. +# `MessageSeenEvent` now lives in `events/kernel_events` (surfaced by the concentrator). +export kernel_api logScope: topics = "wakunode waku" @@ -60,7 +67,7 @@ logScope: # Git version in git describe format (defined at compile time) const git_version* {.strdefine.} = "n/a" -type Waku* = ref object +type Waku* = ref object ## Implements `KernelApi` (ops in `waku/api/*`). stateInfo*: WakuStateInfo conf*: WakuConf rng*: crypto.Rng @@ -76,10 +83,6 @@ type Waku* = ref object healthMonitor*: NodeHealthMonitor - messagingClient*: MessagingClient - - reliableChannelManager*: ReliableChannelManager - restServer*: WakuRestServerRef metricsServer*: MetricsHttpServerRef appCallbacks*: AppCallbacks @@ -210,7 +213,7 @@ proc new*( let restServer: WakuRestServerRef = if wakuConf.restServerConf.isSome(): let restServer = startRestServerEssentials( - healthMonitor, wakuConf.restServerConf.get(), wakuConf.portsShift + healthMonitor, wakuConf.restServerConf.get() ).valueOr: error "Starting essential REST server failed", error = $error return err("Failed to start essential REST server in Waku.new: " & $error) @@ -247,29 +250,6 @@ proc new*( ok(waku) -proc getPorts( - listenAddrs: seq[MultiAddress] -): Result[tuple[tcpPort, websocketPort, quicPort: Option[Port]], string] = - var tcpPort, websocketPort, quicPort = none(Port) - - for a in listenAddrs: - if a.isWsAddress(): - if websocketPort.isNone(): - let wsAddress = initTAddress(a).valueOr: - return err("getPorts wsAddr error:" & $error) - websocketPort = some(wsAddress.port) - elif a.isQuicAddress(): - if quicPort.isNone(): - let quicAddress = initTAddress(a).valueOr: - return err("getPorts quicAddr error:" & $error) - quicPort = some(quicAddress.port) - elif tcpPort.isNone(): - let tcpAddress = initTAddress(a).valueOr: - return err("getPorts tcpAddr error:" & $error) - tcpPort = some(tcpAddress.port) - - return ok((tcpPort: tcpPort, websocketPort: websocketPort, quicPort: quicPort)) - proc getRunningNetConfig(waku: Waku): Future[Result[NetConfig, string]] {.async.} = let conf = waku.conf let (tcpPort, websocketPort, quicPort) = getPorts( @@ -286,11 +266,11 @@ proc getRunningNetConfig(waku: Waku): Future[Result[NetConfig, string]] {.async. if quicPort.isSome() and conf.quicConf.isSome(): conf.quicConf.get().port = quicPort.get() - # Rebuild NetConfig with bound port values + # Rebuild NetConfig from the bound ports already read back into `conf`. let netConf = ( await networkConfiguration( conf.clusterId, conf.endpointConf, conf.discv5Conf, conf.webSocketConf, - conf.quicConf, conf.wakuFlags, conf.dnsAddrsNameServers, conf.portsShift, clientId, + conf.quicConf, conf.wakuFlags, conf.dnsAddrsNameServers, clientId, ) ).valueOr: return err("Could not update NetConfig: " & error) @@ -384,35 +364,6 @@ proc startDnsDiscoveryRetryLoop(waku: Waku): Future[void] {.async.} = error "failed to connect to dynamic bootstrap nodes: " & getCurrentExceptionMsg() return -proc mountMessagingClient*(waku: Waku): Result[void, string] = - if not waku.messagingClient.isNil(): - return err("messaging client already mounted") - if waku.node.started: - return err("cannot mount messaging client on a started node") - waku.messagingClient = MessagingClient.new(waku.conf.p2pReliability, waku.node).valueOr: - return err("could not create messaging client: " & $error) - return ok() - -proc mountReliableChannelManager*(waku: Waku): Result[void, string] = - if not waku.reliableChannelManager.isNil(): - return err("reliable channel manager already mounted") - if waku.messagingClient.isNil(): - return err("reliable channel manager requires a mounted messaging client") - if waku.node.started: - return err("cannot mount reliable channel manager on a started node") - - let messagingClient = waku.messagingClient - let defaultSendHandler: SendHandler = proc( - envelope: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - return await messagingClient.send(envelope) - - waku.reliableChannelManager = ReliableChannelManager.new( - messagingClient, defaultSendHandler, waku.brokerCtx - ).valueOr: - return err("could not create reliable channel manager: " & $error) - return ok() - proc start*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = if waku.node.started: warn "start: waku node already started" @@ -468,7 +419,6 @@ proc start*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = waku.rng, conf.nodeKey, conf.endpointConf.p2pListenAddress, - conf.portsShift, ) ).valueOr: return err("failed to start waku discovery v5: " & error) @@ -550,9 +500,7 @@ proc start*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = if conf.metricsServerConf.isSome(): try: let (server, port) = ( - await waku_metrics.startMetricsServerAndLogging( - conf.metricsServerConf.get(), conf.portsShift - ) + await waku_metrics.startMetricsServerAndLogging(conf.metricsServerConf.get()) ).valueOr: return err("Starting monitoring and external interfaces failed: " & error) waku.metricsServer = server @@ -565,14 +513,6 @@ proc start*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = ) waku.healthMonitor.setOverallHealth(HealthStatus.READY) - if not waku.messagingClient.isNil(): - waku.messagingClient.start().isOkOr: - return err("failed to start messaging client: " & $error) - - if not waku.reliableChannelManager.isNil(): - waku.reliableChannelManager.start().isOkOr: - return err("failed to start reliable channel manager: " & $error) - return ok() proc stop*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = @@ -590,12 +530,6 @@ proc stop*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = if not waku.wakuDiscv5.isNil(): await waku.wakuDiscv5.stop() - if not waku.reliableChannelManager.isNil(): - await waku.reliableChannelManager.stop() - - if not waku.messagingClient.isNil(): - await waku.messagingClient.stop() - if not waku.node.isNil(): await waku.node.stop() @@ -605,8 +539,10 @@ proc stop*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = if not waku.healthMonitor.isNil(): await waku.healthMonitor.stopHealthMonitor() - ## Clear RequestConnectionStatus provider + ## Clear all providers registered in start() so a later start() can re-set them. RequestConnectionStatus.clearProvider(waku.brokerCtx) + RequestProtocolHealth.clearProvider(waku.brokerCtx) + RequestHealthReport.clearProvider(waku.brokerCtx) if not waku.restServer.isNil(): await waku.restServer.stop() @@ -616,12 +552,4 @@ proc stop*(waku: Waku): Future[Result[void, string]] {.async: (raises: []).} = return ok() -proc isModeCoreAvailable*(waku: Waku): bool = - return not waku.node.wakuRelay.isNil() - -proc isModeEdgeAvailable*(waku: Waku): bool = - return - waku.node.wakuRelay.isNil() and not waku.node.wakuStoreClient.isNil() and - not waku.node.wakuFilterClient.isNil() and not waku.node.wakuLightPushClient.isNil() - {.pop.} diff --git a/logos_delivery/waku/waku_core/message/message.nim b/logos_delivery/waku/waku_core/message/message.nim index acd7055a0..f6e6c9336 100644 --- a/logos_delivery/waku/waku_core/message/message.nim +++ b/logos_delivery/waku/waku_core/message/message.nim @@ -27,3 +27,8 @@ type WakuMessage* = object # Data payload transmitted. # The proof attribute indicates that the message is not spam. This # attribute will be used in the rln-relay protocol. proof*: seq[byte] + +proc ensureTimestampSet*(message: WakuMessage): WakuMessage = + result = message + if result.timestamp == 0: + result.timestamp = getNowInNanosecondTime() diff --git a/logos_delivery/waku/waku_filter_v2/client.nim b/logos_delivery/waku/waku_filter_v2/client.nim index ddae363a3..51ed18af3 100644 --- a/logos_delivery/waku/waku_filter_v2/client.nim +++ b/logos_delivery/waku/waku_filter_v2/client.nim @@ -14,7 +14,7 @@ import brokers/broker_context import - logos_delivery/waku/[node/peer_manager, waku_core, events/delivery_events], + logos_delivery/waku/[node/peer_manager, waku_core, api/events/filter_subscribe_events], ./common, ./protocol_metrics, ./rpc_codec, diff --git a/logos_delivery/waku/waku_keystore/protocol_types.nim b/logos_delivery/waku/waku_keystore/protocol_types.nim index 0f50c66ee..3bf748ef9 100644 --- a/logos_delivery/waku/waku_keystore/protocol_types.nim +++ b/logos_delivery/waku/waku_keystore/protocol_types.nim @@ -77,7 +77,7 @@ proc toIdentityCredentials*( proc serialize*(idComms: seq[IDCommitment]): seq[byte] = ## serializes a seq of IDCommitments to a byte seq - ## the serialization is based on https://github.com/status-im/nwaku/blob/37bd29fbc37ce5cf636734e7dd410b1ed27b88c8/waku/v2/protocol/waku_rln_relay/rln.nim#L142 + ## the serialization is based on https://github.com/status-im/nwaku/blob/37bd29fbc37ce5cf636734e7dd410b1ed27b88c8/waku/v2/protocol/waku_rln/rln.nim#L142 ## the order of serialization is |id_commitment_len<8>|id_commitment| var idCommsBytes = newSeq[byte]() diff --git a/logos_delivery/waku/waku_lightpush/callbacks.nim b/logos_delivery/waku/waku_lightpush/callbacks.nim index dd910326e..80d9c2439 100644 --- a/logos_delivery/waku/waku_lightpush/callbacks.nim +++ b/logos_delivery/waku/waku_lightpush/callbacks.nim @@ -3,56 +3,24 @@ import logos_delivery/waku/compat/option_valueor import results -import - ../waku_core, - ../waku_relay, - ./common, - ../waku_rln_relay, - ../waku_rln_relay/protocol_types +import ../waku_core, ../waku_relay, ./common, ../rln, ../rln/protocol_types import std/times, libp2p/peerid, stew/byteutils -proc checkAndGenerateRLNProof*( - rlnPeer: Option[WakuRLNRelay], message: WakuMessage -): Future[Result[WakuMessage, string]] {.async.} = - # check if the message already has RLN proof - if message.proof.len > 0: - return ok(message) - - if rlnPeer.isNone(): - notice "Publishing message without RLN proof" - return ok(message) - # generate and append RLN proof - let - time = getTime().toUnix() - senderEpochTime = float64(time) - var msgWithProof = message - msgWithProof.proof = ( - await rlnPeer.get().generateRLNProof(msgWithProof.toRLNSignal, senderEpochTime) - ).valueOr: - return err($error) - return ok(msgWithProof) - proc getNilPushHandler*(): PushMessageHandler = return proc( pubsubTopic: string, message: WakuMessage ): Future[WakuLightPushResult] {.async.} = return lightpushResultInternalError("no waku relay found") -proc getRelayPushHandler*( - wakuRelay: WakuRelay, rlnPeer: Option[WakuRLNRelay] = none[WakuRLNRelay]() -): PushMessageHandler = +proc getRelayPushHandler*(wakuRelay: WakuRelay): PushMessageHandler = return proc( pubsubTopic: string, message: WakuMessage ): Future[WakuLightPushResult] {.async.} = - # append RLN proof - let msgWithProof = (await checkAndGenerateRLNProof(rlnPeer, message)).valueOr: - return lighpushErrorResult(LightPushErrorCode.OUT_OF_RLN_PROOF, error) - - (await wakuRelay.validateMessage(pubSubTopic, msgWithProof)).isOkOr: + (await wakuRelay.validateMessage(pubSubTopic, message)).isOkOr: return lighpushErrorResult(LightPushErrorCode.INVALID_MESSAGE, $error) - let publishedResult = (await wakuRelay.publish(pubsubTopic, msgWithProof)).valueOr: + let publishedResult = (await wakuRelay.publish(pubsubTopic, message)).valueOr: let msgHash = computeMessageHash(pubsubTopic, message).to0xHex() notice "Lightpush request has not been published to any peers", msg_hash = msgHash, reason = $error diff --git a/logos_delivery/waku/waku_lightpush/client.nim b/logos_delivery/waku/waku_lightpush/client.nim index 680970a51..9572b9323 100644 --- a/logos_delivery/waku/waku_lightpush/client.nim +++ b/logos_delivery/waku/waku_lightpush/client.nim @@ -24,10 +24,6 @@ type WakuLightPushClient* = ref object proc new*(T: type WakuLightPushClient, peerManager: PeerManager, rng: crypto.Rng): T = WakuLightPushClient(peerManager: peerManager, rng: rng) -proc ensureTimestampSet(message: var WakuMessage) = - if message.timestamp == 0: - message.timestamp = getNowInNanosecondTime() - ## Short log string for peer identifiers (overloads for convenience) func shortPeerId(peer: PeerId): string = shortLog(peer) @@ -82,8 +78,7 @@ proc publish*( wakuMessage: WakuMessage, dest: Connection | PeerId | RemotePeerInfo, ): Future[WakuLightPushResult] {.async, gcsafe.} = - var message = wakuMessage - ensureTimestampSet(message) + let message = ensureTimestampSet(wakuMessage) let msgHash = computeMessageHash(pubSubTopic.get(""), message).to0xHex() diff --git a/logos_delivery/waku/waku_lightpush_legacy/callbacks.nim b/logos_delivery/waku/waku_lightpush_legacy/callbacks.nim index af31ecf7d..fa6ec92d4 100644 --- a/logos_delivery/waku/waku_lightpush_legacy/callbacks.nim +++ b/logos_delivery/waku/waku_lightpush_legacy/callbacks.nim @@ -5,50 +5,24 @@ import ../waku_relay, ./common, ./protocol_metrics, - ../waku_rln_relay, - ../waku_rln_relay/protocol_types + ../rln, + ../rln/protocol_types import std/times, libp2p/peerid, stew/byteutils -proc checkAndGenerateRLNProof*( - rlnPeer: Option[WakuRLNRelay], message: WakuMessage -): Future[Result[WakuMessage, string]] {.async.} = - # check if the message already has RLN proof - if message.proof.len > 0: - return ok(message) - - if rlnPeer.isNone(): - notice "Publishing message without RLN proof" - return ok(message) - # generate and append RLN proof - let - time = getTime().toUnix() - senderEpochTime = float64(time) - var msgWithProof = message - msgWithProof.proof = ( - await rlnPeer.get().generateRLNProof(msgWithProof.toRLNSignal(), senderEpochTime) - ).valueOr: - return err($error) - return ok(msgWithProof) - proc getNilPushHandler*(): PushMessageHandler = return proc( pubsubTopic: string, message: WakuMessage ): Future[WakuLightPushResult[void]] {.async.} = return err("no waku relay found") -proc getRelayPushHandler*( - wakuRelay: WakuRelay, rlnPeer: Option[WakuRLNRelay] = none[WakuRLNRelay]() -): PushMessageHandler = +proc getRelayPushHandler*(wakuRelay: WakuRelay): PushMessageHandler = return proc( pubsubTopic: string, message: WakuMessage ): Future[WakuLightPushResult[void]] {.async.} = - # append RLN proof - let msgWithProof = ?(await checkAndGenerateRLNProof(rlnPeer, message)) + ?(await wakuRelay.validateMessage(pubSubTopic, message)) - ?(await wakuRelay.validateMessage(pubSubTopic, msgWithProof)) - - (await wakuRelay.publish(pubsubTopic, msgWithProof)).isOkOr: + (await wakuRelay.publish(pubsubTopic, message)).isOkOr: ## Agreed change expected to the lightpush protocol to better handle such case. https://github.com/waku-org/pm/issues/93 let msgHash = computeMessageHash(pubsubTopic, message).to0xHex() notice "Lightpush request has not been published to any peers", diff --git a/logos_delivery/waku/waku_lightpush_legacy/client.nim b/logos_delivery/waku/waku_lightpush_legacy/client.nim index 511c3f543..0ed5ca823 100644 --- a/logos_delivery/waku/waku_lightpush_legacy/client.nim +++ b/logos_delivery/waku/waku_lightpush_legacy/client.nim @@ -73,9 +73,7 @@ proc publish*( ): Future[WakuLightPushResult[string]] {.async, gcsafe.} = ## On success, returns the msg_hash of the published message - var message = wakuMessage - if message.timestamp == 0: - message.timestamp = getNowInNanosecondTime() + let message = ensureTimestampSet(wakuMessage) let msg_hash_hex_str = computeMessageHash(pubsubTopic, message).to0xHex() let pushRequest = PushRequest(pubSubTopic: pubSubTopic, message: message) diff --git a/logos_delivery/waku/waku_peer_exchange/common.nim b/logos_delivery/waku/waku_peer_exchange/common.nim index 85d7398b1..91f1ac647 100644 --- a/logos_delivery/waku/waku_peer_exchange/common.nim +++ b/logos_delivery/waku/waku_peer_exchange/common.nim @@ -17,5 +17,6 @@ const decodeRpcFailure* = "decode_rpc_failure" retrievePeersDiscv5Error* = "retrieve_peers_discv5_failure" pxFailure* = "px_failure" + streamClosedFailure* = "stream_closed_failure" type WakuPeerExchangeResult*[T] = Result[T, PeerExchangeResponseStatus] diff --git a/logos_delivery/waku/waku_peer_exchange/protocol.nim b/logos_delivery/waku/waku_peer_exchange/protocol.nim index a389938f8..ce1f0cd04 100644 --- a/logos_delivery/waku/waku_peer_exchange/protocol.nim +++ b/logos_delivery/waku/waku_peer_exchange/protocol.nim @@ -6,6 +6,7 @@ import chronos, metrics, libp2p/protocols/protocol, + libp2p/stream/connection, libp2p/crypto/crypto, eth/p2p/discoveryv5/enr import @@ -38,18 +39,20 @@ type WakuPeerExchange* = ref object of LPProtocol proc respond( wpx: WakuPeerExchange, enrs: seq[enr.Record], conn: Connection -): Future[WakuPeerExchangeResult[void]] {.async, gcsafe.} = +): Future[WakuPeerExchangeResult[void]] {.async: (raises: [CancelledError]), gcsafe.} = let rpc = PeerExchangeRpc.makeResponse(enrs.mapIt(PeerExchangePeerInfo(enr: it.raw))) try: await conn.writeLP(rpc.encode().buffer) - except CatchableError as exc: - error "exception when trying to send a respond", error = getCurrentExceptionMsg() - waku_px_errors.inc(labelValues = [exc.msg]) + except LPStreamError as exc: + # Remote closed the stream before we responded - expected during peer churn. + debug "peer exchange response not delivered: stream closed", + peerId = conn.peerId, error = exc.msg + waku_px_errors.inc(labelValues = [streamClosedFailure]) return err( ( status_code: PeerExchangeResponseStatusCode.DIAL_FAILURE, - status_desc: some("exception dialing peer: " & exc.msg), + status_desc: some("stream closed before response: " & exc.msg), ) ) @@ -60,18 +63,20 @@ proc respondError( status_code: PeerExchangeResponseStatusCode, status_desc: Option[string], conn: Connection, -): Future[WakuPeerExchangeResult[void]] {.async, gcsafe.} = +): Future[WakuPeerExchangeResult[void]] {.async: (raises: [CancelledError]), gcsafe.} = let rpc = PeerExchangeRpc.makeErrorResponse(status_code, status_desc) try: await conn.writeLP(rpc.encode().buffer) - except CatchableError as exc: - error "exception when trying to send a respond", error = getCurrentExceptionMsg() - waku_px_errors.inc(labelValues = [exc.msg]) + except LPStreamError as exc: + # Remote closed the stream before we responded - expected during peer churn. + debug "peer exchange error response not delivered: stream closed", + peerId = conn.peerId, error = exc.msg + waku_px_errors.inc(labelValues = [streamClosedFailure]) return err( ( status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE, - status_desc: some("exception dialing peer: " & exc.msg), + status_desc: some("stream closed before response: " & exc.msg), ) ) @@ -128,20 +133,12 @@ proc initProtocolHandler(wpx: WakuPeerExchange) = wpx.requestRateLimiter.checkUsageLimit(WakuPeerExchangeCodec, conn): try: buffer = await conn.readLp(DefaultMaxRpcSize.int) - except CatchableError as exc: - error "exception when handling px request", error = getCurrentExceptionMsg() - waku_px_errors.inc(labelValues = [exc.msg]) - - ( - try: - await wpx.respondError( - PeerExchangeResponseStatusCode.BAD_REQUEST, some(exc.msg), conn - ) - except CatchableError: - error "could not send error response", error = getCurrentExceptionMsg() - return - ).isOkOr: - error "Failed to respond with BAD_REQUEST:", error = $error + except LPStreamError as exc: + # Remote disconnected before sending a full request - expected churn; + # no point responding on a dead stream. + debug "peer exchange request not received: stream closed", + peerId = conn.peerId, error = exc.msg + waku_px_errors.inc(labelValues = [streamClosedFailure]) return let decBuf = PeerExchangeRpc.decode(buffer).valueOr: @@ -149,42 +146,30 @@ proc initProtocolHandler(wpx: WakuPeerExchange) = error "Failed to decode PeerExchange request", error = $error ( - try: - await wpx.respondError( - PeerExchangeResponseStatusCode.BAD_REQUEST, some($error), conn - ) - except CatchableError: - error "could not send error response decode", - error = getCurrentExceptionMsg() - return + await wpx.respondError( + PeerExchangeResponseStatusCode.BAD_REQUEST, some($error), conn + ) ).isOkOr: - error "Failed to respond with BAD_REQUEST:", error = $error + error "Failed to respond with BAD_REQUEST", error = $error return let enrs = wpx.getEnrsFromStore(decBuf.request.numPeers) info "peer exchange request received" trace "px enrs to respond", enrs = $enrs - try: - (await wpx.respond(enrs, conn)).isErrOr: - waku_px_peers_sent.inc(enrs.len().int64()) - except CatchableError: - error "could not send response", error = getCurrentExceptionMsg() + (await wpx.respond(enrs, conn)).isErrOr: + waku_px_peers_sent.inc(enrs.len().int64()) do: defer: # close, no data is expected await conn.closeWithEof() - try: - ( - await wpx.respondError( - PeerExchangeResponseStatusCode.TOO_MANY_REQUESTS, none(string), conn - ) - ).isOkOr: - error "Failed to respond with TOO_MANY_REQUESTS:", error = $error - except CatchableError: - error "could not send error response", error = getCurrentExceptionMsg() - return + ( + await wpx.respondError( + PeerExchangeResponseStatusCode.TOO_MANY_REQUESTS, none(string), conn + ) + ).isOkOr: + error "Failed to respond with TOO_MANY_REQUESTS", error = $error wpx.handler = handler wpx.codec = WakuPeerExchangeCodec diff --git a/logos_delivery/waku/waku_relay/protocol.nim b/logos_delivery/waku/waku_relay/protocol.nim index e677ec5a0..409b02929 100644 --- a/logos_delivery/waku/waku_relay/protocol.nim +++ b/logos_delivery/waku/waku_relay/protocol.nim @@ -24,9 +24,9 @@ import logos_delivery/waku/waku_core, logos_delivery/waku/node/health_monitor/topic_health, logos_delivery/waku/requests/health_requests, - logos_delivery/waku/events/health_events, + logos_delivery/waku/api/events/health_events, ./message_id, - logos_delivery/waku/events/peer_events + logos_delivery/waku/api/events/peer_events from logos_delivery/waku/waku_core/codecs import WakuRelayCodec export WakuRelayCodec diff --git a/logos_delivery/waku/waku_rln_relay.nim b/logos_delivery/waku/waku_rln_relay.nim deleted file mode 100644 index ce79f98d8..000000000 --- a/logos_delivery/waku/waku_rln_relay.nim +++ /dev/null @@ -1,7 +0,0 @@ -import - ./waku_rln_relay/group_manager, - ./waku_rln_relay/conversion_utils, - ./waku_rln_relay/rln_relay, - ./waku_rln_relay/contract - -export group_manager, conversion_utils, rln_relay, contract diff --git a/logos_delivery/waku/waku_rln_relay/rln_relay.nim b/logos_delivery/waku/waku_rln_relay/rln_relay.nim deleted file mode 100644 index 7c325d3d5..000000000 --- a/logos_delivery/waku/waku_rln_relay/rln_relay.nim +++ /dev/null @@ -1,487 +0,0 @@ -import logos_delivery/waku/compat/option_valueor -{.push raises: [].} - -import - std/[sequtils, tables, times, deques], - chronicles, - options, - chronos, - stint, - web3, - json, - web3/eth_api_types, - eth/keys, - libp2p/protocols/pubsub/rpc/messages, - libp2p/protocols/pubsub/pubsub, - results, - stew/[byteutils, arrayops], - brokers/broker_context - -import - ./group_manager, - ./rln, - ./conversion_utils, - ./constants, - ./protocol_types, - ./protocol_metrics, - ./nonce_manager - -import - logos_delivery/waku/[ - common/error_handling, - waku_relay, # for WakuRelayHandler - waku_core, - requests/rln_requests, - waku_keystore, - ] - -logScope: - topics = "waku rln_relay" - -type RlnRelayCreds* {.requiresInit.} = object - path*: string - password*: string - -type RlnRelayConf* = object of RootObj - # TODO: severals parameters are only needed when it's dynamic - # change the config to either nest or use enum/type variant so it's obvious - # and then it can be set to `requiresInit` - dynamic*: bool - credIndex*: Option[uint] - ethContractAddress*: string - ethClientUrls*: seq[string] - chainId*: UInt256 - creds*: Option[RlnRelayCreds] - epochSizeSec*: uint64 - userMessageLimit*: uint64 - ethPrivateKey*: Option[string] - -type WakuRlnConfig* = object of RlnRelayConf - onFatalErrorAction*: OnFatalErrorHandler - -type WakuRLNRelay* = ref object of RootObj - # the log of nullifiers and Shamir shares of the past messages grouped per epoch - nullifierLog*: OrderedTable[Epoch, Table[Nullifier, ProofMetadata]] - lastEpoch*: Epoch # the epoch of the last published rln message - rlnEpochSizeSec*: uint64 - rlnMaxTimestampGap*: uint64 - rlnMaxEpochGap*: uint64 - groupManager*: GroupManager - onFatalErrorAction*: OnFatalErrorHandler - nonceManager*: NonceManager - epochMonitorFuture*: Future[void] - rootChangesFuture*: Future[Result[void, string]] - brokerCtx*: BrokerContext - -proc calcEpoch*(rlnPeer: WakuRLNRelay, t: float64): Epoch = - ## gets time `t` as `flaot64` with subseconds resolution in the fractional part - ## and returns its corresponding rln `Epoch` value - - let e = uint64(t / rlnPeer.rlnEpochSizeSec.float64) - return toEpoch(e) - -proc nextEpoch*(rlnPeer: WakuRLNRelay, time: float64): float64 = - let - currentEpoch = uint64(time / rlnPeer.rlnEpochSizeSec.float64) - nextEpochTime = float64(currentEpoch + 1) * rlnPeer.rlnEpochSizeSec.float64 - currentTime = epochTime() - - # Ensure we always return a future time - if nextEpochTime > currentTime: - return nextEpochTime - else: - return epochTime() - -proc stop*(rlnPeer: WakuRLNRelay) {.async: (raises: [Exception]).} = - ## stops the rln-relay protocol - ## Throws an error if it cannot stop the rln-relay protocol - - # stop the group sync, and flush data to tree db - info "stopping rln-relay" - RequestGenerateRlnProof.clearProvider(rlnPeer.brokerCtx) - await rlnPeer.groupManager.stop() - -proc hasDuplicate*( - rlnPeer: WakuRLNRelay, epoch: Epoch, proofMetadata: ProofMetadata -): RlnRelayResult[bool] = - ## returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same - ## epoch and nullifier as `proofMetadata`'s epoch and nullifier - ## otherwise, returns false - ## Returns an error if it cannot check for duplicates - - # check if the epoch exists - let nullifier = proofMetadata.nullifier - if not rlnPeer.nullifierLog.hasKey(epoch): - return ok(false) - try: - if rlnPeer.nullifierLog[epoch].hasKey(nullifier): - # there is an identical record, mark it as spam - return ok(true) - - # there is no duplicate - return ok(false) - except KeyError: - return err("the epoch was not found: " & getCurrentExceptionMsg()) - -proc updateLog*( - rlnPeer: WakuRLNRelay, epoch: Epoch, proofMetadata: ProofMetadata -): RlnRelayResult[void] = - ## saves supplied proofMetadata `proofMetadata` - ## in the `nullifierLog` of the `rlnPeer` - ## Returns an error if it cannot update the log - - # check if the epoch exists - if not rlnPeer.nullifierLog.hasKeyOrPut( - epoch, {proofMetadata.nullifier: proofMetadata}.toTable() - ): - return ok() - - try: - # check if an identical record exists - if rlnPeer.nullifierLog[epoch].hasKeyOrPut(proofMetadata.nullifier, proofMetadata): - # the above condition could be `discarded` but it is kept for clarity, that slashing will - # be implemented here - # TODO: slashing logic - return ok() - return ok() - except KeyError: - return - err("the epoch was not found: " & getCurrentExceptionMsg()) # should never happen - -proc getCurrentEpoch*(rlnPeer: WakuRLNRelay): Epoch = - return rlnPeer.calcEpoch(epochTime()) - -proc absDiff*(e1, e2: Epoch): uint64 = - ## returns the absolute difference between the two rln `Epoch`s `e1` and `e2` - ## i.e., e1 - e2 - - # convert epochs to their corresponding unsigned numerical values - let - epoch1 = fromEpoch(e1) - epoch2 = fromEpoch(e2) - - # Manually perform an `abs` calculation - if epoch1 > epoch2: - return epoch1 - epoch2 - else: - return epoch2 - epoch1 - -proc toRLNSignal*(wakumessage: WakuMessage): seq[byte] = - ## it is a utility proc that prepares the `data` parameter of the proof generation procedure i.e., `proofGen` that resides in the current module - ## it extracts the `contentTopic`, `timestamp` and the `payload` of the supplied `wakumessage` and serializes them into a byte sequence - - let - contentTopicBytes = toBytes(wakumessage.contentTopic) - timestampBytes = toBytes(wakumessage.timestamp.uint64) - output = concat(wakumessage.payload, contentTopicBytes, @(timestampBytes)) - return output - -proc validateMessage*( - rlnPeer: WakuRLNRelay, msg: WakuMessage -): Future[MessageValidationResult] {.async.} = - ## validate the supplied `msg` based on the waku-rln-relay routing protocol i.e., - ## the `msg`'s epoch is within MaxEpochGap of the current epoch - ## the `msg` has valid rate limit proof - ## the `msg` does not violate the rate limit - ## `timeOption` indicates Unix epoch time (fractional part holds sub-seconds) - ## if `timeOption` is supplied, then the current epoch is calculated based on that - - let proof = RateLimitProof.init(msg.proof).valueOr: - return MessageValidationResult.Invalid - - # track message count for metrics - waku_rln_messages_total.inc() - - # checks if the message's timestamp is within acceptable range - let currentTime = getTime().toUnixFloat() - let messageTime = msg.timestamp.float64 / 1e9 - - let timeDiff = uint64(abs(currentTime - messageTime)) - - info "time info", - currentTime = currentTime, messageTime = messageTime, msgHash = msg.hash - - if timeDiff > rlnPeer.rlnMaxTimestampGap: - warn "invalid message: timestamp difference exceeds threshold", - timeDiff = timeDiff, - maxTimestampGap = rlnPeer.rlnMaxTimestampGap, - contentTopic = msg.contentTopic - waku_rln_invalid_messages_total.inc(labelValues = ["invalid_timestamp"]) - return MessageValidationResult.Invalid - - let computedEpoch = rlnPeer.calcEpoch(messageTime) - if proof.epoch != computedEpoch: - warn "invalid message: timestamp mismatches epoch", - proofEpoch = fromEpoch(proof.epoch), - computedEpoch = fromEpoch(computedEpoch), - contentTopic = msg.contentTopic - waku_rln_invalid_messages_total.inc(labelValues = ["timestamp_mismatch"]) - return MessageValidationResult.Invalid - - let rootValidationRes = await rlnPeer.groupManager.validateRoot(proof.merkleRoot) - if not rootValidationRes: - warn "invalid message: provided root does not belong to acceptable window of roots", - provided = proof.merkleRoot.inHex(), - validRoots = rlnPeer.groupManager.validRoots.mapIt(it.inHex()), - contentTopic = msg.contentTopic - waku_rln_invalid_messages_total.inc(labelValues = ["invalid_root"]) - return MessageValidationResult.Invalid - - # verify the proof - let - contentTopicBytes = toBytes(msg.contentTopic) - timestampBytes = toBytes(msg.timestamp.uint64) - input = concat(msg.payload, contentTopicBytes, @(timestampBytes)) - - waku_rln_proof_verification_total.inc() - waku_rln_proof_verification_duration_seconds.nanosecondTime: - let proofVerificationRes = - rlnPeer.groupManager.verifyProof(msg.toRLNSignal(), proof) - - proofVerificationRes.isOkOr: - waku_rln_errors_total.inc(labelValues = ["proof_verification"]) - warn "invalid message: proof verification failed", - payloadLen = msg.payload.len, contentTopic = msg.contentTopic - return MessageValidationResult.Invalid - - if not proofVerificationRes.value(): - # invalid proof - warn "invalid message: invalid proof", - payloadLen = msg.payload.len, contentTopic = msg.contentTopic - waku_rln_invalid_messages_total.inc(labelValues = ["invalid_proof"]) - return MessageValidationResult.Invalid - - # check if double messaging has happened - let proofMetadata = proof.extractMetadata().valueOr: - waku_rln_errors_total.inc(labelValues = ["proof_metadata_extraction"]) - return MessageValidationResult.Invalid - - let msgEpoch = proof.epoch - let hasDup = rlnPeer.hasDuplicate(msgEpoch, proofMetadata) - if hasDup.isErr(): - waku_rln_errors_total.inc(labelValues = ["duplicate_check"]) - elif hasDup.value == true: - trace "invalid message: message is spam", - payloadLen = msg.payload.len, contentTopic = msg.contentTopic - waku_rln_spam_messages_total.inc() - return MessageValidationResult.Spam - - trace "message is valid", - payloadLen = msg.payload.len, contentTopic = msg.contentTopic - # Metric increment moved to validator to include shard label - return MessageValidationResult.Valid - -proc validateMessageAndUpdateLog*( - rlnPeer: WakuRLNRelay, msg: WakuMessage -): Future[MessageValidationResult] {.async.} = - ## validates the message and updates the log to prevent double messaging - ## in future messages - - let isValidMessage = await rlnPeer.validateMessage(msg) - - let msgProof = RateLimitProof.init(msg.proof).valueOr: - return MessageValidationResult.Invalid - - let proofMetadata = msgProof.extractMetadata().valueOr: - return MessageValidationResult.Invalid - - # insert the message to the log (never errors) only if the - # message is valid. - if isValidMessage == MessageValidationResult.Valid: - discard rlnPeer.updateLog(msgProof.epoch, proofMetadata) - - return isValidMessage - -proc generateRLNProof*( - rlnPeer: WakuRLNRelay, input: seq[byte], senderEpochTime: float64 -): Future[RlnRelayResult[seq[byte]]] {.async.} = - let epoch = rlnPeer.calcEpoch(senderEpochTime) - let nonce = rlnPeer.nonceManager.getNonce().valueOr: - return err("could not get new message id to generate an rln proof: " & $error) - let proof = (await rlnPeer.groupManager.generateProof(input, epoch, nonce)).valueOr: - return err("could not generate rln-v2 proof: " & $error) - return ok(proof.encode().buffer) - -proc clearNullifierLog*(rlnPeer: WakuRlnRelay) = - # clear the first MaxEpochGap epochs of the nullifer log - # if more than MaxEpochGap epochs are in the log - let currentEpoch = fromEpoch(rlnPeer.getCurrentEpoch()) - - var epochsToRemove: seq[Epoch] = @[] - for epoch in rlnPeer.nullifierLog.keys(): - let epochInt = fromEpoch(epoch) - - # clean all epochs that are +- rlnMaxEpochGap from the current epoch - if (currentEpoch + rlnPeer.rlnMaxEpochGap) <= epochInt or - epochInt <= (currentEpoch - rlnPeer.rlnMaxEpochGap): - epochsToRemove.add(epoch) - - for epochRemove in epochsToRemove: - trace "clearing epochs from the nullifier log", - currentEpoch = currentEpoch, cleanedEpoch = fromEpoch(epochRemove) - rlnPeer.nullifierLog.del(epochRemove) - -proc generateRlnValidator*( - wakuRlnRelay: WakuRLNRelay, spamHandler = none(SpamHandler) -): WakuValidatorHandler = - ## this procedure is a thin wrapper for the pubsub addValidator method - ## it sets a validator for waku messages, acting in the registered pubsub topic - ## the message validation logic is according to https://rfc.vac.dev/spec/17/ - proc validator( - topic: string, message: WakuMessage - ): Future[pubsub.ValidationResult] {.async.} = - trace "rln-relay topic validator is called" - wakuRlnRelay.clearNullifierLog() - - let msgProof = RateLimitProof.init(message.proof).valueOr: - trace "generateRlnValidator reject", error = error - return pubsub.ValidationResult.Reject - - # validate the message and update log - let validationRes = await wakuRlnRelay.validateMessageAndUpdateLog(message) - - let - proof = byteutils.toHex(msgProof.proof) - epoch = fromEpoch(msgProof.epoch) - root = inHex(msgProof.merkleRoot) - shareX = inHex(msgProof.shareX) - shareY = inHex(msgProof.shareY) - nullifier = inHex(msgProof.nullifier) - payload = string.fromBytes(message.payload) - case validationRes - of Valid: - trace "message validity is verified, relaying", - proof = proof, - root = root, - shareX = shareX, - shareY = shareY, - nullifier = nullifier - waku_rln_valid_messages_total.inc(labelValues = [topic]) - return pubsub.ValidationResult.Accept - of Invalid: - trace "message validity could not be verified, discarding", - proof = proof, - root = root, - shareX = shareX, - shareY = shareY, - nullifier = nullifier - return pubsub.ValidationResult.Reject - of Spam: - trace "A spam message is found! yay! discarding:", - proof = proof, - root = root, - shareX = shareX, - shareY = shareY, - nullifier = nullifier - if spamHandler.isSome(): - let handler = spamHandler.get() - handler(message) - return pubsub.ValidationResult.Reject - - return validator - -proc monitorEpochs(wakuRlnRelay: WakuRLNRelay) {.async.} = - while true: - try: - if wakuRlnRelay.groupManager.userMessageLimit.isSome(): - waku_rln_remaining_proofs_per_epoch.set( - wakuRlnRelay.groupManager.userMessageLimit.get().float64 - ) - else: - error "userMessageLimit is not set in monitorEpochs" - except CatchableError: - error "Error in epoch monitoring", error = getCurrentExceptionMsg() - - let nextEpochTime = wakuRlnRelay.nextEpoch(epochTime()) - let sleepDuration = int((nextEpochTime - epochTime()) * 1000) - await sleepAsync(sleepDuration) - -proc mount( - conf: WakuRlnConfig, registrationHandler = none(RegistrationHandler) -): Future[RlnRelayResult[WakuRlnRelay]] {.async.} = - var - groupManager: GroupManager - wakuRlnRelay: WakuRLNRelay - # create an RLN instance - let rlnInstance = createRLNInstance().valueOr: - return err("could not create RLN instance: " & $error) - - let (rlnRelayCredPath, rlnRelayCredPassword) = - if conf.creds.isSome: - (some(conf.creds.get().path), some(conf.creds.get().password)) - else: - (none(string), none(string)) - - groupManager = OnchainGroupManager( - userMessageLimit: some(conf.userMessageLimit), - ethClientUrls: conf.ethClientUrls, - ethContractAddress: $conf.ethContractAddress, - chainId: conf.chainId, - rlnInstance: rlnInstance, - registrationHandler: registrationHandler, - keystorePath: rlnRelayCredPath, - keystorePassword: rlnRelayCredPassword, - ethPrivateKey: conf.ethPrivateKey, - membershipIndex: conf.credIndex, - onFatalErrorAction: conf.onFatalErrorAction, - ) - - # Initialize the groupManager - (await groupManager.init()).isOkOr: - return err("could not initialize the group manager: " & $error) - - wakuRlnRelay = WakuRLNRelay( - groupManager: groupManager, - nonceManager: NonceManager.init(conf.userMessageLimit, conf.epochSizeSec.float), - rlnEpochSizeSec: conf.epochSizeSec, - rlnMaxEpochGap: max(uint64(MaxClockGapSeconds / float64(conf.epochSizeSec)), 1), - rlnMaxTimestampGap: uint64(MaxClockGapSeconds), - onFatalErrorAction: conf.onFatalErrorAction, - brokerCtx: globalBrokerContext(), - ) - - # Start epoch monitoring in the background - wakuRlnRelay.epochMonitorFuture = monitorEpochs(wakuRlnRelay) - - RequestGenerateRlnProof.setProvider( - wakuRlnRelay.brokerCtx, - proc( - msg: WakuMessage, senderEpochTime: float64 - ): Future[Result[RequestGenerateRlnProof, string]] {.async.} = - let proof = ( - await wakuRlnRelay.generateRLNProof(msg.toRLNSignal(), senderEpochTime) - ).valueOr: - return err("Could not create RLN proof: " & error) - - return ok(RequestGenerateRlnProof(proof: proof)), - ).isOkOr: - return err("Proof generator provider cannot be set: " & $error) - - return ok(wakuRlnRelay) - -proc isReady*(rlnPeer: WakuRLNRelay): Future[bool] {.async.} = - ## returns true if the rln-relay protocol is ready to relay messages - ## returns false otherwise - - # could be nil during startup - if rlnPeer.groupManager == nil: - return false - try: - return await rlnPeer.groupManager.isReady() - except CatchableError: - error "could not check if the rln-relay protocol is ready", - err = getCurrentExceptionMsg() - return false - -proc new*( - T: type WakuRlnRelay, - conf: WakuRlnConfig, - registrationHandler = none(RegistrationHandler), -): Future[RlnRelayResult[WakuRlnRelay]] {.async.} = - ## Mounts the rln-relay protocol on the node. - ## The rln-relay protocol can be mounted in two modes: on-chain and off-chain. - ## Returns an error if the rln-relay protocol could not be mounted. - try: - return await mount(conf, registrationHandler) - except CatchableError: - return err("could not mount the rln-relay protocol: " & getCurrentExceptionMsg()) diff --git a/nimble.lock b/nimble.lock index 18ebde258..03bb7a434 100644 --- a/nimble.lock +++ b/nimble.lock @@ -328,8 +328,8 @@ } }, "brokers": { - "version": "#v3.1.1", - "vcsRevision": "a7316a35f1b62e3497ae8ee0fc1aace74df0beb2", + "version": "#v3.1.4", + "vcsRevision": "8ae9e963b0b4478c93e6f888be6a46654da787de", "url": "https://github.com/NagyZoltanPeter/nim-brokers.git", "downloadMethod": "git", "dependencies": [ @@ -341,7 +341,7 @@ "cbor_serialization" ], "checksums": { - "sha1": "4447d7c1f9da14ae439afb23aee45116ce2ecb40" + "sha1": "2949398033c1b3a2586f0afd7e92f46b9a0a412e" } }, "stint": { diff --git a/nix/deps.nix b/nix/deps.nix index 00dea27c2..63b01f8af 100644 --- a/nix/deps.nix +++ b/nix/deps.nix @@ -159,8 +159,8 @@ brokers = pkgs.fetchgit { url = "https://github.com/NagyZoltanPeter/nim-brokers.git"; - rev = "a7316a35f1b62e3497ae8ee0fc1aace74df0beb2"; - sha256 = "1990270n88jm0i48g07zr4vq2nn79g7gymf28f3g5ak42g33l7rm"; + rev = "8ae9e963b0b4478c93e6f888be6a46654da787de"; + sha256 = "0r3vrc6h9jiwhd3p4sy3j8gx3k5174s77qcp2wrswclvfvbskw5y"; fetchSubmodules = true; }; @@ -277,7 +277,6 @@ }; sds = pkgs.fetchgit { - # Keep in sync with the nim-sds pin in nimble.lock. url = "https://github.com/logos-messaging/nim-sds.git"; rev = "b12f5ee07c5b764303b51fb948b32a4ade1de3b5"; sha256 = "1z8f0v1ww7y6zssdacjxfs6s4862dwckw25df3yn1v0qnz40rpc8"; diff --git a/tests/api/test_all.nim b/tests/api/test_all.nim index 56be19c27..108551bc2 100644 --- a/tests/api/test_all.nim +++ b/tests/api/test_all.nim @@ -3,6 +3,7 @@ import ./test_entry_nodes, ./test_node_conf, + ./test_conf, ./test_api_send, ./test_api_subscription, ./test_api_receive, diff --git a/tests/api/test_api_health.nim b/tests/api/test_api_health.nim index d8a7eabf2..7b493964a 100644 --- a/tests/api/test_api_health.nim +++ b/tests/api/test_api_health.nim @@ -12,7 +12,7 @@ import [topic_health, health_status, protocol_health, health_report], logos_delivery/waku/requests/health_requests, logos_delivery/waku/requests/node_requests, - logos_delivery/waku/events/health_events, + logos_delivery/waku/api/events/health_events, logos_delivery/waku/common/waku_protocol, logos_delivery/waku/factory/waku_conf import tools/confutils/cli_args @@ -74,13 +74,12 @@ proc waitForShardHealthy( suite "LM API health checking": var serviceNode {.threadvar.}: WakuNode - client {.threadvar.}: Waku + client {.threadvar.}: LogosDelivery servicePeerInfo {.threadvar.}: RemotePeerInfo asyncSetup: lockNewGlobalBrokerContext: - serviceNode = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + serviceNode = newTestWakuNode(generateSecp256k1Key()) (await serviceNode.mountRelay()).isOkOr: raiseAssert error serviceNode.mountMetadata(3, @[0'u16]).isOkOr: @@ -92,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().toWakuNodeConf(Core).valueOr: + raiseAssert error conf.listenAddress = parseIpAddress("0.0.0.0") conf.tcpPort = Port(0) conf.discv5UdpPort = Port(0) @@ -102,9 +100,7 @@ suite "LM API health checking": conf.numShardsInNetwork = 1 conf.rest = false - client = (await createNode(conf)).valueOr: - raiseAssert error - client.mountMessagingClient().isOkOr: + client = (await LogosDelivery.new(conf)).valueOr: raiseAssert error (await client.start()).isOkOr: raiseAssert error @@ -114,13 +110,13 @@ suite "LM API health checking": await serviceNode.stop() asyncTest "RequestShardTopicsHealth, check PubsubTopic health": - client.node.wakuRelay.subscribe(DefaultShard, dummyHandler) - await client.node.connectToNodes(@[servicePeerInfo]) + client.waku.node.wakuRelay.subscribe(DefaultShard, dummyHandler) + await client.waku.node.connectToNodes(@[servicePeerInfo]) var isHealthy = false let start = Moment.now() while Moment.now() - start < TestTimeout: - let req = RequestShardTopicsHealth.request(client.brokerCtx, @[DefaultShard]).valueOr: + let req = RequestShardTopicsHealth.request(client.waku.brokerCtx, @[DefaultShard]).valueOr: raiseAssert "RequestShardTopicsHealth failed" if req.topicHealth.len > 0: @@ -134,22 +130,22 @@ suite "LM API health checking": asyncTest "RequestShardTopicsHealth, check disconnected PubsubTopic": const GhostShard = PubsubTopic("/waku/2/rs/1/666") - client.node.wakuRelay.subscribe(GhostShard, dummyHandler) + client.waku.node.wakuRelay.subscribe(GhostShard, dummyHandler) - let req = RequestShardTopicsHealth.request(client.brokerCtx, @[GhostShard]).valueOr: + let req = RequestShardTopicsHealth.request(client.waku.brokerCtx, @[GhostShard]).valueOr: raiseAssert "Request failed" check req.topicHealth.len > 0 check req.topicHealth[0].health == TopicHealth.UNHEALTHY asyncTest "RequestProtocolHealth, check relay status": - await client.node.connectToNodes(@[servicePeerInfo]) + await client.waku.node.connectToNodes(@[servicePeerInfo]) var isReady = false let start = Moment.now() while Moment.now() - start < TestTimeout: let relayReq = await RequestProtocolHealth.request( - client.brokerCtx, WakuProtocol.RelayProtocol + client.waku.brokerCtx, WakuProtocol.RelayProtocol ) if relayReq.isOk() and relayReq.get().healthStatus.health == HealthStatus.READY: isReady = true @@ -158,14 +154,16 @@ suite "LM API health checking": check isReady == true - let storeReq = - await RequestProtocolHealth.request(client.brokerCtx, WakuProtocol.StoreProtocol) + let storeReq = await RequestProtocolHealth.request( + client.waku.brokerCtx, WakuProtocol.StoreProtocol + ) if storeReq.isOk(): check storeReq.get().healthStatus.health != HealthStatus.READY asyncTest "RequestProtocolHealth, check unmounted protocol": - let req = - await RequestProtocolHealth.request(client.brokerCtx, WakuProtocol.StoreProtocol) + let req = await RequestProtocolHealth.request( + client.waku.brokerCtx, WakuProtocol.StoreProtocol + ) check req.isOk() let status = req.get().healthStatus @@ -173,16 +171,16 @@ suite "LM API health checking": check status.desc.isNone() asyncTest "RequestConnectionStatus, check connectivity state": - let initialReq = RequestConnectionStatus.request(client.brokerCtx).valueOr: + let initialReq = RequestConnectionStatus.request(client.waku.brokerCtx).valueOr: raiseAssert "RequestConnectionStatus failed" check initialReq.connectionStatus == ConnectionStatus.Disconnected - await client.node.connectToNodes(@[servicePeerInfo]) + await client.waku.node.connectToNodes(@[servicePeerInfo]) var isConnected = false let start = Moment.now() while Moment.now() - start < TestTimeout: - let req = RequestConnectionStatus.request(client.brokerCtx).valueOr: + let req = RequestConnectionStatus.request(client.waku.brokerCtx).valueOr: raiseAssert "RequestConnectionStatus failed" if req.connectionStatus == ConnectionStatus.PartiallyConnected or @@ -194,29 +192,30 @@ suite "LM API health checking": check isConnected == true asyncTest "EventConnectionStatusChange, detect connect and disconnect": - let connectFuture = - waitForConnectionStatus(client.brokerCtx, ConnectionStatus.PartiallyConnected) + let connectFuture = waitForConnectionStatus( + client.waku.brokerCtx, ConnectionStatus.PartiallyConnected + ) - await client.node.connectToNodes(@[servicePeerInfo]) + await client.waku.node.connectToNodes(@[servicePeerInfo]) await connectFuture let disconnectFuture = - waitForConnectionStatus(client.brokerCtx, ConnectionStatus.Disconnected) - await client.node.disconnectNode(servicePeerInfo) + waitForConnectionStatus(client.waku.brokerCtx, ConnectionStatus.Disconnected) + await client.waku.node.disconnectNode(servicePeerInfo) await disconnectFuture asyncTest "EventShardTopicHealthChange, detect health improvement": - client.node.wakuRelay.subscribe(DefaultShard, dummyHandler) + client.waku.node.wakuRelay.subscribe(DefaultShard, dummyHandler) - let healthEventFuture = waitForShardHealthy(client.brokerCtx) + let healthEventFuture = waitForShardHealthy(client.waku.brokerCtx) - await client.node.connectToNodes(@[servicePeerInfo]) + await client.waku.node.connectToNodes(@[servicePeerInfo]) let event = await healthEventFuture check event.topic == DefaultShard asyncTest "RequestHealthReport, check aggregate report": - let req = await RequestHealthReport.request(client.brokerCtx) + let req = await RequestHealthReport.request(client.waku.brokerCtx) check req.isOk() @@ -228,7 +227,8 @@ suite "LM API health checking": asyncTest "RequestContentTopicsHealth, smoke test": let fictionalTopic = ContentTopic("/waku/2/this-does-not-exist/proto") - let req = RequestContentTopicsHealth.request(client.brokerCtx, @[fictionalTopic]) + let req = + RequestContentTopicsHealth.request(client.waku.brokerCtx, @[fictionalTopic]) check req.isOk() @@ -241,20 +241,20 @@ suite "LM API health checking": let cTopic = ContentTopic("/waku/2/my-content-topic/proto") let shardReq = - RequestRelayShard.request(client.brokerCtx, none(PubsubTopic), cTopic) + RequestRelayShard.request(client.waku.brokerCtx, none(PubsubTopic), cTopic) check shardReq.isOk() let targetShard = $shardReq.get().relayShard - client.node.wakuRelay.subscribe(targetShard, dummyHandler) + client.waku.node.wakuRelay.subscribe(targetShard, dummyHandler) serviceNode.wakuRelay.subscribe(targetShard, dummyHandler) - await client.node.connectToNodes(@[servicePeerInfo]) + await client.waku.node.connectToNodes(@[servicePeerInfo]) var isHealthy = false let start = Moment.now() while Moment.now() - start < TestTimeout: - let req = RequestContentTopicsHealth.request(client.brokerCtx, @[cTopic]).valueOr: + let req = RequestContentTopicsHealth.request(client.waku.brokerCtx, @[cTopic]).valueOr: raiseAssert "Request failed" if req.contentTopicHealth.len > 0: @@ -268,12 +268,11 @@ suite "LM API health checking": check isHealthy == true asyncTest "RequestProtocolHealth, edge mode smoke test": - var edgeWaku: Waku + var edgeWaku: LogosDelivery lockNewGlobalBrokerContext: - var edgeConf = defaultWakuNodeConf().valueOr: - raiseAssert error - edgeConf.mode = Edge + var edgeConf = MessagingClientConf().toWakuNodeConf(Edge).valueOr: + raiseAssert error edgeConf.listenAddress = parseIpAddress("0.0.0.0") edgeConf.tcpPort = Port(0) edgeConf.discv5UdpPort = Port(0) @@ -281,20 +280,18 @@ suite "LM API health checking": edgeConf.maxMessageSize = "150 KiB" edgeConf.rest = false - edgeWaku = (await createNode(edgeConf)).valueOr: + edgeWaku = (await LogosDelivery.new(edgeConf)).valueOr: raiseAssert "Failed to create edge node: " & error - edgeWaku.mountMessagingClient().isOkOr: - raiseAssert "Failed to mount edge messaging: " & error (await edgeWaku.start()).isOkOr: raiseAssert "Failed to start edge waku: " & error let relayReq = await RequestProtocolHealth.request( - edgeWaku.brokerCtx, WakuProtocol.RelayProtocol + edgeWaku.waku.brokerCtx, WakuProtocol.RelayProtocol ) check relayReq.isOk() check relayReq.get().healthStatus.health == HealthStatus.NOT_MOUNTED - check not edgeWaku.node.wakuFilterClient.isNil() + check not edgeWaku.waku.node.wakuFilterClient.isNil() discard await edgeWaku.stop() diff --git a/tests/api/test_api_receive.nim b/tests/api/test_api_receive.nim index 362ee1847..870f85860 100644 --- a/tests/api/test_api_receive.nim +++ b/tests/api/test_api_receive.nim @@ -14,14 +14,14 @@ import logos_delivery/waku/[ waku_node, waku_core, - events/message_events, - events/health_events, + api/events/health_events, waku_relay/protocol, waku_archive, waku_archive/common as archive_common, ] import logos_delivery/waku/factory/waku_conf import tools/confutils/cli_args +import logos_delivery/api/conf/messaging_conf const TestTimeout = chronos.seconds(60) @@ -83,22 +83,21 @@ 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() + .toWakuNodeConf(messaging_conf.LogosDeliveryMode.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 type TestNetwork = ref object storeNode: WakuNode publisher: WakuNode - subscriber: Waku + subscriber: LogosDelivery storeNodePeerInfo: RemotePeerInfo missedPayload: seq[byte] @@ -114,8 +113,7 @@ proc setupNetwork(testTopic: ContentTopic): Future[TestNetwork] {.async.} = # store node: archive + store + relay, subscribed to the shard var storeNode: WakuNode lockNewGlobalBrokerContext: - storeNode = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + storeNode = newTestWakuNode(generateSecp256k1Key()) storeNode.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on storeNode" ) @@ -133,8 +131,7 @@ proc setupNetwork(testTopic: ContentTopic): Future[TestNetwork] {.async.} = # publisher: relay, connected to the store so its messages get archived var publisher: WakuNode lockNewGlobalBrokerContext: - publisher = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + publisher = newTestWakuNode(generateSecp256k1Key()) publisher.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on publisher" ) @@ -158,12 +155,11 @@ proc setupNetwork(testTopic: ContentTopic): Future[TestNetwork] {.async.} = # subscriber: created before the publish so the message timestamp lands after # its RecvService startTimeToCheck watermark - var subscriber: Waku + var subscriber: LogosDelivery lockNewGlobalBrokerContext: - subscriber = (await createNode(createApiNodeConf(numShards))).expect( + subscriber = (await LogosDelivery.new(createApiNodeConf(numShards))).expect( "Failed to create subscriber" ) - subscriber.mountMessagingClient().expect("Failed to mount messaging") (await subscriber.start()).expect("Failed to start subscriber") # publish while the subscriber is offline: the message reaches the archive but @@ -188,7 +184,7 @@ proc setupNetwork(testTopic: ContentTopic): Future[TestNetwork] {.async.} = raiseAssert "Message was not archived in time" # subscribe to the content topic; with no peers yet the subscriber stays offline - (await subscriber.subscribe(testTopic)).expect("Failed to subscribe") + (await subscriber.messagingClient.subscribe(testTopic)).expect("Failed to subscribe") return TestNetwork( storeNode: storeNode, @@ -217,11 +213,11 @@ suite "Messaging API, Receive Service (store recovery)": defer: await net.teardown() - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() - await net.subscriber.node.connectToNodes(@[net.storeNodePeerInfo]) + await net.subscriber.waku.node.connectToNodes(@[net.storeNodePeerInfo]) await net.subscriber.messagingClient.recvService.checkStore() check await eventManager.waitForEvents(TestTimeout) @@ -236,15 +232,15 @@ suite "Messaging API, Receive Service (store recovery)": defer: await net.teardown() - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() # sync on coming online (the transition that fires the backfill) before asserting let onlineFut = waitForConnectionStatus( - net.subscriber.brokerCtx, ConnectionStatus.PartiallyConnected + net.subscriber.waku.brokerCtx, ConnectionStatus.PartiallyConnected ) - await net.subscriber.node.connectToNodes(@[net.storeNodePeerInfo]) + await net.subscriber.waku.node.connectToNodes(@[net.storeNodePeerInfo]) await onlineFut check await eventManager.waitForEvents(TestTimeout) diff --git a/tests/api/test_api_send.nim b/tests/api/test_api_send.nim index e51473b17..8df564b0d 100644 --- a/tests/api/test_api_send.nim +++ b/tests/api/test_api_send.nim @@ -8,6 +8,7 @@ import ../waku_archive/archive_utils import logos_delivery, logos_delivery/waku/[waku_node, waku_core, waku_relay/protocol] import logos_delivery/waku/factory/waku_conf import tools/confutils/cli_args +import logos_delivery/api/conf/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.LogosDeliveryMode = messaging_conf.LogosDeliveryMode.Core +): WakuNodeConf = + var conf = MessagingClientConf().toWakuNodeConf(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 @@ -152,8 +153,7 @@ suite "Waku API - Send": asyncSetup: lockNewGlobalBrokerContext: - relayNode1 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + relayNode1 = newTestWakuNode(generateSecp256k1Key()) relayNode1.mountMetadata(3, @[0'u16]).isOkOr: raiseAssert "Failed to mount metadata: " & error (await relayNode1.mountRelay()).isOkOr: @@ -162,8 +162,7 @@ suite "Waku API - Send": await relayNode1.start() lockNewGlobalBrokerContext: - relayNode2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + relayNode2 = newTestWakuNode(generateSecp256k1Key()) relayNode2.mountMetadata(3, @[0'u16]).isOkOr: raiseAssert "Failed to mount metadata: " & error (await relayNode2.mountRelay()).isOkOr: @@ -172,8 +171,7 @@ suite "Waku API - Send": await relayNode2.start() lockNewGlobalBrokerContext: - lightpushNode = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + lightpushNode = newTestWakuNode(generateSecp256k1Key()) lightpushNode.mountMetadata(3, @[0'u16]).isOkOr: raiseAssert "Failed to mount metadata: " & error (await lightpushNode.mountRelay()).isOkOr: @@ -184,8 +182,7 @@ suite "Waku API - Send": await lightpushNode.start() lockNewGlobalBrokerContext: - storeNode = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + storeNode = newTestWakuNode(generateSecp256k1Key()) storeNode.mountMetadata(3, @[0'u16]).isOkOr: raiseAssert "Failed to mount metadata: " & error (await storeNode.mountRelay()).isOkOr: @@ -237,12 +234,10 @@ suite "Waku API - Send": ) asyncTest "Check API availability (unhealthy node)": - var node: Waku + var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await createNode(createApiNodeConf())).valueOr: + node = (await LogosDelivery.new(createApiNodeConf())).valueOr: raiseAssert error - node.mountMessagingClient().isOkOr: - raiseAssert "Failed to mount messaging: " & error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error # node is not connected ! @@ -251,7 +246,7 @@ suite "Waku API - Send": ContentTopic("/waku/2/default-content/proto"), "test payload" ) - let sendResult = await node.send(envelope) + let sendResult = await node.messagingClient.send(envelope) # TODO: The API is not enforcing a health check before the send, # so currently this test cannot successfully fail to send. @@ -261,20 +256,18 @@ suite "Waku API - Send": raiseAssert "Failed to stop node: " & error asyncTest "Send fully validated": - var node: Waku + var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await createNode(createApiNodeConf())).valueOr: + node = (await LogosDelivery.new(createApiNodeConf())).valueOr: raiseAssert error - node.mountMessagingClient().isOkOr: - raiseAssert "Failed to mount messaging: " & error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error - await node.node.connectToNodes( + await node.waku.node.connectToNodes( @[relayNode1PeerInfo, lightpushNodePeerInfo, storeNodePeerInfo] ) - let eventManager = newSendEventListenerManager(node.brokerCtx) + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) defer: await eventManager.teardown() @@ -282,7 +275,7 @@ suite "Waku API - Send": ContentTopic("/waku/2/default-content/proto"), "test payload" ) - let requestId = (await node.send(envelope)).valueOr: + let requestId = (await node.messagingClient.send(envelope)).valueOr: raiseAssert error # Wait for events with timeout @@ -297,18 +290,16 @@ suite "Waku API - Send": raiseAssert "Failed to stop node: " & error asyncTest "Send only propagates": - var node: Waku + var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await createNode(createApiNodeConf())).valueOr: + node = (await LogosDelivery.new(createApiNodeConf())).valueOr: raiseAssert error - node.mountMessagingClient().isOkOr: - raiseAssert "Failed to mount messaging: " & error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error - await node.node.connectToNodes(@[relayNode1PeerInfo]) + await node.waku.node.connectToNodes(@[relayNode1PeerInfo]) - let eventManager = newSendEventListenerManager(node.brokerCtx) + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) defer: await eventManager.teardown() @@ -316,7 +307,7 @@ suite "Waku API - Send": ContentTopic("/waku/2/default-content/proto"), "test payload" ) - let requestId = (await node.send(envelope)).valueOr: + let requestId = (await node.messagingClient.send(envelope)).valueOr: raiseAssert error # Wait for events with timeout @@ -329,18 +320,16 @@ suite "Waku API - Send": raiseAssert "Failed to stop node: " & error asyncTest "Send only propagates fallback to lightpush": - var node: Waku + var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await createNode(createApiNodeConf())).valueOr: + node = (await LogosDelivery.new(createApiNodeConf())).valueOr: raiseAssert error - node.mountMessagingClient().isOkOr: - raiseAssert "Failed to mount messaging: " & error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error - await node.node.connectToNodes(@[lightpushNodePeerInfo]) + await node.waku.node.connectToNodes(@[lightpushNodePeerInfo]) - let eventManager = newSendEventListenerManager(node.brokerCtx) + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) defer: await eventManager.teardown() @@ -348,7 +337,7 @@ suite "Waku API - Send": ContentTopic("/waku/2/default-content/proto"), "test payload" ) - let requestId = (await node.send(envelope)).valueOr: + let requestId = (await node.messagingClient.send(envelope)).valueOr: raiseAssert error # Wait for events with timeout @@ -360,19 +349,27 @@ suite "Waku API - Send": (await node.stop()).isOkOr: raiseAssert "Failed to stop node: " & error - asyncTest "Send fully validates fallback to lightpush": - var node: Waku + asyncTest "Edge sender delivers via lightpush (no relay)": + ## Reproduces issue #3847: an Edge node (no relay mounted) that is only + ## connected to a lightpush-capable peer must deliver through lightpush. + var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await createNode(createApiNodeConf())).valueOr: + node = ( + await LogosDelivery.new( + createApiNodeConf(messaging_conf.LogosDeliveryMode.Edge) + ) + ).valueOr: raiseAssert error - node.mountMessagingClient().isOkOr: - raiseAssert "Failed to mount messaging: " & error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error - await node.node.connectToNodes(@[lightpushNodePeerInfo, storeNodePeerInfo]) + # Edge node has no relay; its only path to the network is the + # lightpush peer it is connected to. + await node.waku.node.connectToNodes(@[lightpushNodePeerInfo]) - let eventManager = newSendEventListenerManager(node.brokerCtx) + check node.waku.node.wakuRelay.isNil() + + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) defer: await eventManager.teardown() @@ -380,7 +377,80 @@ suite "Waku API - Send": ContentTopic("/waku/2/default-content/proto"), "test payload" ) - let requestId = (await node.send(envelope)).valueOr: + let requestId = (await node.messagingClient.send(envelope)).valueOr: + raiseAssert error + + const eventTimeout = 10.seconds + discard await eventManager.waitForEvents(eventTimeout) + + eventManager.validate({SendEventOutcome.Propagated}, requestId) + + (await node.stop()).isOkOr: + raiseAssert "Failed to stop node: " & error + + asyncTest "S16 - isolated sender recovers when lightpush peer appears later": + ## Edge sender starts with no peer; a lightpush peer appears mid-send and a + ## later retry must deliver the queued message. + var node: LogosDelivery + lockNewGlobalBrokerContext: + node = ( + await LogosDelivery.new( + createApiNodeConf(messaging_conf.LogosDeliveryMode.Edge) + ) + ).valueOr: + raiseAssert error + (await node.start()).isOkOr: + raiseAssert "Failed to start Waku node: " & error + # No connectToNodes: the sender has no reachable peer at T0. + + check node.waku.node.wakuRelay.isNil() + + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) + defer: + await eventManager.teardown() + + let envelope = MessageEnvelope.init( + ContentTopic("/waku/2/default-content/proto"), "test payload" + ) + + # send() with no peer must still succeed; the message is queued and retried. + let requestId = (await node.messagingClient.send(envelope)).valueOr: + raiseAssert error + + # Nothing should propagate while isolated (retry interval is 1s). + await sleepAsync(5.seconds) + check eventManager.propagatedCount == 0 + + # The lightpush peer appears; a later retry must now deliver. + await node.waku.node.connectToNodes(@[lightpushNodePeerInfo]) + + const eventTimeout = 10.seconds + discard await eventManager.waitForEvents(eventTimeout) + + eventManager.validate({SendEventOutcome.Propagated}, requestId) + + (await node.stop()).isOkOr: + raiseAssert "Failed to stop node: " & error + + asyncTest "Send fully validates fallback to lightpush": + var node: LogosDelivery + lockNewGlobalBrokerContext: + node = (await LogosDelivery.new(createApiNodeConf())).valueOr: + raiseAssert error + (await node.start()).isOkOr: + raiseAssert "Failed to start Waku node: " & error + + await node.waku.node.connectToNodes(@[lightpushNodePeerInfo, storeNodePeerInfo]) + + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) + defer: + await eventManager.teardown() + + let envelope = MessageEnvelope.init( + ContentTopic("/waku/2/default-content/proto"), "test payload" + ) + + let requestId = (await node.messagingClient.send(envelope)).valueOr: raiseAssert error # Wait for events with timeout @@ -396,8 +466,7 @@ suite "Waku API - Send": asyncTest "Send fails with event": var fakeLightpushNode: WakuNode lockNewGlobalBrokerContext: - fakeLightpushNode = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + fakeLightpushNode = newTestWakuNode(generateSecp256k1Key()) fakeLightpushNode.mountMetadata(3, @[0'u16]).isOkOr: raiseAssert "Failed to mount metadata: " & error (await fakeLightpushNode.mountRelay()).isOkOr: @@ -417,18 +486,20 @@ suite "Waku API - Send": ).isOkOr: raiseAssert "Failed to subscribe fakeLightpushNode: " & error - var node: Waku + var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await createNode(createApiNodeConf(cli_args.WakuMode.Edge))).valueOr: + node = ( + await LogosDelivery.new( + createApiNodeConf(messaging_conf.LogosDeliveryMode.Edge) + ) + ).valueOr: raiseAssert error - node.mountMessagingClient().isOkOr: - raiseAssert "Failed to mount messaging: " & error (await node.start()).isOkOr: raiseAssert "Failed to start Waku node: " & error - await node.node.connectToNodes(@[fakeLightpushNodePeerInfo]) + await node.waku.node.connectToNodes(@[fakeLightpushNodePeerInfo]) - let eventManager = newSendEventListenerManager(node.brokerCtx) + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) defer: await eventManager.teardown() @@ -436,7 +507,7 @@ suite "Waku API - Send": ContentTopic("/waku/2/default-content/proto"), "test payload" ) - let requestId = (await node.send(envelope)).valueOr: + let requestId = (await node.messagingClient.send(envelope)).valueOr: raiseAssert error echo "Sent message with requestId=", requestId @@ -447,3 +518,59 @@ suite "Waku API - Send": eventManager.validate({SendEventOutcome.Error}, requestId) (await node.stop()).isOkOr: raiseAssert "Failed to stop node: " & error + + asyncTest "Store validation times out without event": + ## The message propagates successfully, but the only reachable store peer never + ## receives/archives it (it is outside the relay propagation path), so store + ## validation never confirms. After MaxTimeInCache the task must be dropped with a + ## warn log and NO app event: Propagated fires, but neither Sent nor Error - the + ## missing Sent event is the signal that delivery could not be validated. + var isolatedStoreNode: WakuNode + lockNewGlobalBrokerContext: + isolatedStoreNode = newTestWakuNode(generateSecp256k1Key()) + isolatedStoreNode.mountMetadata(3, @[0'u16]).isOkOr: + raiseAssert "Failed to mount metadata: " & error + (await isolatedStoreNode.mountRelay()).isOkOr: + raiseAssert "Failed to mount relay" + let archiveDriver = newSqliteArchiveDriver() + isolatedStoreNode.mountArchive(archiveDriver).isOkOr: + raiseAssert "Failed to mount archive: " & error + await isolatedStoreNode.mountStore() + await isolatedStoreNode.mountLibp2pPing() + await isolatedStoreNode.start() + # Deliberately NOT subscribed to the topic and NOT wired into the relay mesh, so + # it can answer store queries but never holds the published message. + let isolatedStoreNodePeerInfo = isolatedStoreNode.peerInfo.toRemotePeerInfo() + + var node: LogosDelivery + lockNewGlobalBrokerContext: + node = (await LogosDelivery.new(createApiNodeConf())).valueOr: + raiseAssert error + (await node.start()).isOkOr: + raiseAssert "Failed to start Waku node: " & error + + # Propagate via relayNode1; store queries can only reach the isolated store node. + await node.waku.node.connectToNodes( + @[relayNode1PeerInfo, isolatedStoreNodePeerInfo] + ) + + let eventManager = newSendEventListenerManager(node.waku.brokerCtx) + defer: + await eventManager.teardown() + + let envelope = MessageEnvelope.init( + ContentTopic("/waku/2/default-content/proto"), "test payload" + ) + + let requestId = (await node.messagingClient.send(envelope)).valueOr: + raiseAssert error + + # Must outlive MaxTimeInCache (1 min) so the store-validation timeout drop fires. + const eventTimeout = 65.seconds + discard await eventManager.waitForEvents(eventTimeout) + + eventManager.validate({SendEventOutcome.Propagated}, requestId) + + await isolatedStoreNode.stop() + (await node.stop()).isOkOr: + raiseAssert "Failed to stop node: " & error diff --git a/tests/api/test_api_subscription.nim b/tests/api/test_api_subscription.nim index 2a6ce36ee..90d160bc3 100644 --- a/tests/api/test_api_subscription.nim +++ b/tests/api/test_api_subscription.nim @@ -12,13 +12,13 @@ import logos_delivery/waku/[ waku_node, waku_core, - events/message_events, waku_relay/protocol, node/waku_node/filter, node/subscription_manager, ] import logos_delivery/waku/factory/waku_conf import tools/confutils/cli_args +import logos_delivery/api/conf/messaging_conf const TestTimeout = chronos.seconds(10) const NegativeTestTimeout = chronos.seconds(2) @@ -64,46 +64,44 @@ proc waitForEvents( type TestNetwork = ref object publisher: WakuNode # Relay node that publishes messages in tests. meshBuddy: WakuNode # Extra relay peer for publisher's mesh (Edge tests only). - subscriber: Waku + subscriber: LogosDelivery # The receiver node in tests. Edge node in edge tests, Core node in relay tests. publisherPeerInfo: RemotePeerInfo proc createApiNodeConf( - mode: cli_args.WakuMode = cli_args.WakuMode.Core, numShards: uint16 = 1 + mode: messaging_conf.LogosDeliveryMode = messaging_conf.LogosDeliveryMode.Core, + numShards: uint16 = 1, ): WakuNodeConf = - var conf = defaultWakuNodeConf().valueOr: - raiseAssert error - conf.mode = mode + var conf = MessagingClientConf().toWakuNodeConf(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 -proc setupSubscriberNode(conf: WakuNodeConf): Future[Waku] {.async.} = - var node: Waku +proc setupSubscriberNode(conf: WakuNodeConf): Future[LogosDelivery] {.async.} = + var node: LogosDelivery lockNewGlobalBrokerContext: - node = (await createNode(conf)).expect("Failed to create subscriber node") - node.mountMessagingClient().expect("Failed to mount messaging") + node = (await LogosDelivery.new(conf)).expect("Failed to create subscriber node") (await node.start()).expect("Failed to start subscriber node") return node proc setupNetwork( - numShards: uint16 = 1, mode: cli_args.WakuMode = cli_args.WakuMode.Core + numShards: uint16 = 1, + mode: messaging_conf.LogosDeliveryMode = messaging_conf.LogosDeliveryMode.Core, ): Future[TestNetwork] {.async.} = var net = TestNetwork() lockNewGlobalBrokerContext: - net.publisher = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + net.publisher = newTestWakuNode(generateSecp256k1Key()) net.publisher.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata" ) (await net.publisher.mountRelay()).expect("Failed to mount relay") - if mode == cli_args.WakuMode.Edge: + if mode == messaging_conf.LogosDeliveryMode.Edge: await net.publisher.mountFilter() await net.publisher.mountLibp2pPing() await net.publisher.start() @@ -122,10 +120,9 @@ proc setupNetwork( "Failed to sub publisher" ) - if mode == cli_args.WakuMode.Edge: + if mode == messaging_conf.LogosDeliveryMode.Edge: lockNewGlobalBrokerContext: - net.meshBuddy = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + net.meshBuddy = newTestWakuNode(generateSecp256k1Key()) net.meshBuddy.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on meshBuddy" ) @@ -141,7 +138,7 @@ proc setupNetwork( net.subscriber = await setupSubscriberNode(createApiNodeConf(mode, numShards)) - await net.subscriber.node.connectToNodes(@[net.publisherPeerInfo]) + await net.subscriber.waku.node.connectToNodes(@[net.publisherPeerInfo]) return net @@ -171,28 +168,30 @@ proc waitForMesh(node: WakuNode, shard: PubsubTopic) {.async.} = await sleepAsync(100.milliseconds) raise newException(ValueError, "GossipSub Mesh failed to stabilize on " & shard) -proc waitForEdgeSubs(w: Waku, shard: PubsubTopic) {.async.} = +proc waitForEdgeSubs(w: LogosDelivery, shard: PubsubTopic) {.async.} = let deadline = Moment.now() + EdgeWaitTimeout while Moment.now() < deadline: - if w.node.subscriptionManager.edgeFilterPeerCount(shard) > 0: + if w.waku.node.subscriptionManager.edgeFilterPeerCount(shard) > 0: return await sleepAsync(100.milliseconds) raise newException(ValueError, "Edge filter subscription failed on " & shard) -proc edgePeersReached(w: Waku, shard: PubsubTopic, n: int): Future[bool] {.async.} = +proc edgePeersReached( + w: LogosDelivery, shard: PubsubTopic, n: int +): Future[bool] {.async.} = let deadline = Moment.now() + EdgeWaitTimeout while Moment.now() < deadline: - if w.node.subscriptionManager.edgeFilterPeerCount(shard) >= n: + if w.waku.node.subscriptionManager.edgeFilterPeerCount(shard) >= n: return true await sleepAsync(100.milliseconds) return false proc edgePeersDroppedBelow( - w: Waku, shard: PubsubTopic, n: int + w: LogosDelivery, shard: PubsubTopic, n: int ): Future[bool] {.async.} = let deadline = Moment.now() + EdgeWaitTimeout while Moment.now() < deadline: - if w.node.subscriptionManager.edgeFilterPeerCount(shard) < n: + if w.waku.node.subscriptionManager.edgeFilterPeerCount(shard) < n: return true await sleepAsync(100.milliseconds) return false @@ -201,7 +200,7 @@ proc publishToMesh( net: TestNetwork, contentTopic: ContentTopic, payload: seq[byte] ): Future[Result[int, string]] {.async.} = # Publishes a message from "publisher" via relay into the gossipsub mesh. - let shard = net.subscriber.node.getRelayShard(contentTopic) + let shard = net.subscriber.waku.node.getRelayShard(contentTopic) await waitForMesh(net.publisher, shard) let msg = WakuMessage( payload: payload, contentTopic: contentTopic, version: 0, timestamp: now() @@ -213,7 +212,7 @@ proc publishToMeshAfterEdgeReady( ): Future[Result[int, string]] {.async.} = # First, ensure "subscriber" node (an edge node) is subscribed and ready to receive. # Afterwards, "publisher" (relay node) sends the message in the gossipsub network. - let shard = net.subscriber.node.getRelayShard(contentTopic) + let shard = net.subscriber.waku.node.getRelayShard(contentTopic) await waitForEdgeSubs(net.subscriber, shard) return await net.publishToMesh(contentTopic, payload) @@ -224,11 +223,11 @@ suite "Messaging API, SubscriptionManager": await net.teardown() let testTopic = ContentTopic("/waku/2/test-content/proto") - (await net.subscriber.subscribe(testTopic)).expect( + (await net.subscriber.messagingClient.subscribe(testTopic)).expect( "subscriberNode failed to subscribe" ) - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() @@ -247,9 +246,11 @@ suite "Messaging API, SubscriptionManager": let subbedTopic = ContentTopic("/waku/2/subbed-topic/proto") let ignoredTopic = ContentTopic("/waku/2/ignored-topic/proto") - (await net.subscriber.subscribe(subbedTopic)).expect("failed to subscribe") + (await net.subscriber.messagingClient.subscribe(subbedTopic)).expect( + "failed to subscribe" + ) - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() @@ -267,10 +268,14 @@ suite "Messaging API, SubscriptionManager": let testTopic = ContentTopic("/waku/2/unsub-test/proto") - (await net.subscriber.subscribe(testTopic)).expect("failed to subscribe") - net.subscriber.unsubscribe(testTopic).expect("failed to unsubscribe") + (await net.subscriber.messagingClient.subscribe(testTopic)).expect( + "failed to subscribe" + ) + net.subscriber.messagingClient.unsubscribe(testTopic).expect( + "failed to unsubscribe" + ) - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() @@ -288,14 +293,14 @@ suite "Messaging API, SubscriptionManager": let topicA = ContentTopic("/waku/2/topic-a/proto") let topicB = ContentTopic("/waku/2/topic-b/proto") - (await net.subscriber.subscribe(topicA)).expect("failed to sub A") - (await net.subscriber.subscribe(topicB)).expect("failed to sub B") + (await net.subscriber.messagingClient.subscribe(topicA)).expect("failed to sub A") + (await net.subscriber.messagingClient.subscribe(topicB)).expect("failed to sub B") - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() - net.subscriber.unsubscribe(topicA).expect("failed to unsub A") + net.subscriber.messagingClient.unsubscribe(topicA).expect("failed to unsub A") discard (await net.publishToMesh(topicA, "Dropped Message".toBytes())).expect( "Publish A failed" @@ -314,11 +319,15 @@ suite "Messaging API, SubscriptionManager": let glitchTopic = ContentTopic("/waku/2/glitch/proto") - (await net.subscriber.subscribe(glitchTopic)).expect("failed to sub") - (await net.subscriber.subscribe(glitchTopic)).expect("failed to double sub") - net.subscriber.unsubscribe(glitchTopic).expect("failed to unsub") + (await net.subscriber.messagingClient.subscribe(glitchTopic)).expect( + "failed to sub" + ) + (await net.subscriber.messagingClient.subscribe(glitchTopic)).expect( + "failed to double sub" + ) + net.subscriber.messagingClient.unsubscribe(glitchTopic).expect("failed to unsub") - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() @@ -337,9 +346,11 @@ suite "Messaging API, SubscriptionManager": let testTopic = ContentTopic("/waku/2/resub-test/proto") # Subscribe - (await net.subscriber.subscribe(testTopic)).expect("Initial sub failed") + (await net.subscriber.messagingClient.subscribe(testTopic)).expect( + "Initial sub failed" + ) - var eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + var eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) discard (await net.publishToMesh(testTopic, "Msg 1".toBytes())).expect("Pub 1 failed") @@ -347,8 +358,8 @@ suite "Messaging API, SubscriptionManager": await eventManager.teardown() # Unsubscribe and verify teardown - net.subscriber.unsubscribe(testTopic).expect("Unsub failed") - eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + net.subscriber.messagingClient.unsubscribe(testTopic).expect("Unsub failed") + eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) discard (await net.publishToMesh(testTopic, "Ghost".toBytes())).expect("Ghost pub failed") @@ -357,8 +368,8 @@ suite "Messaging API, SubscriptionManager": await eventManager.teardown() # Resubscribe - (await net.subscriber.subscribe(testTopic)).expect("Resub failed") - eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + (await net.subscriber.messagingClient.subscribe(testTopic)).expect("Resub failed") + eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) discard (await net.publishToMesh(testTopic, "Msg 2".toBytes())).expect("Pub 2 failed") @@ -376,15 +387,15 @@ suite "Messaging API, SubscriptionManager": # generate two content topics that land in two different shards var i = 0 - while net.subscriber.node.getRelayShard(topicA) == - net.subscriber.node.getRelayShard(topicB): + while net.subscriber.waku.node.getRelayShard(topicA) == + net.subscriber.waku.node.getRelayShard(topicB): topicB = ContentTopic("/appB" & $i & "/2/shard-test-b/proto") inc i - (await net.subscriber.subscribe(topicA)).expect("failed to sub A") - (await net.subscriber.subscribe(topicB)).expect("failed to sub B") + (await net.subscriber.messagingClient.subscribe(topicA)).expect("failed to sub A") + (await net.subscriber.messagingClient.subscribe(topicB)).expect("failed to sub B") - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 2) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 2) defer: await eventManager.teardown() @@ -411,7 +422,7 @@ suite "Messaging API, SubscriptionManager": proc verifyNetworkState(expected: seq[ContentTopic]) {.async.} = let eventManager = - newReceiveEventListenerManager(net.subscriber.brokerCtx, expected.len) + newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, expected.len) for topic in allTopics: discard (await net.publishToMesh(topic, "Stress Payload".toBytes())).expect( @@ -439,7 +450,7 @@ suite "Messaging API, SubscriptionManager": # subscribe to all content topics we generated for t in allTopics: - (await net.subscriber.subscribe(t)).expect("sub failed") + (await net.subscriber.messagingClient.subscribe(t)).expect("sub failed") activeSubs.add(t) await verifyNetworkState(activeSubs) @@ -447,7 +458,7 @@ suite "Messaging API, SubscriptionManager": # unsubscribe from some content topics for i in 0 ..< 50: let t = allTopics[i] - net.subscriber.unsubscribe(t).expect("unsub failed") + net.subscriber.messagingClient.unsubscribe(t).expect("unsub failed") let idx = activeSubs.find(t) if idx >= 0: @@ -458,20 +469,22 @@ suite "Messaging API, SubscriptionManager": # re-subscribe to some content topics for i in 0 ..< 25: let t = allTopics[i] - (await net.subscriber.subscribe(t)).expect("resub failed") + (await net.subscriber.messagingClient.subscribe(t)).expect("resub failed") activeSubs.add(t) 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.LogosDeliveryMode.Edge) defer: await net.teardown() let testTopic = ContentTopic("/waku/2/test-content/proto") - (await net.subscriber.subscribe(testTopic)).expect("failed to subscribe") + (await net.subscriber.messagingClient.subscribe(testTopic)).expect( + "failed to subscribe" + ) - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() @@ -484,15 +497,17 @@ 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.LogosDeliveryMode.Edge) defer: await net.teardown() let subbedTopic = ContentTopic("/waku/2/subbed-topic/proto") let ignoredTopic = ContentTopic("/waku/2/ignored-topic/proto") - (await net.subscriber.subscribe(subbedTopic)).expect("failed to subscribe") + (await net.subscriber.messagingClient.subscribe(subbedTopic)).expect( + "failed to subscribe" + ) - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() @@ -504,16 +519,20 @@ 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.LogosDeliveryMode.Edge) defer: await net.teardown() let testTopic = ContentTopic("/waku/2/unsub-test/proto") - (await net.subscriber.subscribe(testTopic)).expect("failed to subscribe") - net.subscriber.unsubscribe(testTopic).expect("failed to unsubscribe") + (await net.subscriber.messagingClient.subscribe(testTopic)).expect( + "failed to subscribe" + ) + net.subscriber.messagingClient.unsubscribe(testTopic).expect( + "failed to unsubscribe" + ) - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() @@ -525,23 +544,23 @@ 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.LogosDeliveryMode.Edge) defer: await net.teardown() let topicA = ContentTopic("/waku/2/topic-a/proto") let topicB = ContentTopic("/waku/2/topic-b/proto") - (await net.subscriber.subscribe(topicA)).expect("failed to sub A") - (await net.subscriber.subscribe(topicB)).expect("failed to sub B") + (await net.subscriber.messagingClient.subscribe(topicA)).expect("failed to sub A") + (await net.subscriber.messagingClient.subscribe(topicB)).expect("failed to sub B") - let shard = net.subscriber.node.getRelayShard(topicA) + let shard = net.subscriber.waku.node.getRelayShard(topicA) await waitForEdgeSubs(net.subscriber, shard) - let eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + let eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) defer: await eventManager.teardown() - net.subscriber.unsubscribe(topicA).expect("failed to unsub A") + net.subscriber.messagingClient.unsubscribe(topicA).expect("failed to unsub A") discard (await net.publishToMesh(topicA, "Dropped Message".toBytes())).expect( "Publish A failed" @@ -554,15 +573,17 @@ 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.LogosDeliveryMode.Edge) defer: await net.teardown() let testTopic = ContentTopic("/waku/2/resub-test/proto") - (await net.subscriber.subscribe(testTopic)).expect("Initial sub failed") + (await net.subscriber.messagingClient.subscribe(testTopic)).expect( + "Initial sub failed" + ) - var eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + var eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) discard (await net.publishToMeshAfterEdgeReady(testTopic, "Msg 1".toBytes())).expect( "Pub 1 failed" ) @@ -570,8 +591,8 @@ suite "Messaging API, SubscriptionManager": require await eventManager.waitForEvents(TestTimeout) await eventManager.teardown() - net.subscriber.unsubscribe(testTopic).expect("Unsub failed") - eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + net.subscriber.messagingClient.unsubscribe(testTopic).expect("Unsub failed") + eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) discard (await net.publishToMesh(testTopic, "Ghost".toBytes())).expect("Ghost pub failed") @@ -579,8 +600,8 @@ suite "Messaging API, SubscriptionManager": check not await eventManager.waitForEvents(NegativeTestTimeout) await eventManager.teardown() - (await net.subscriber.subscribe(testTopic)).expect("Resub failed") - eventManager = newReceiveEventListenerManager(net.subscriber.brokerCtx, 1) + (await net.subscriber.messagingClient.subscribe(testTopic)).expect("Resub failed") + eventManager = newReceiveEventListenerManager(net.subscriber.waku.brokerCtx, 1) discard (await net.publishToMeshAfterEdgeReady(testTopic, "Msg 2".toBytes())).expect( "Pub 2 failed" @@ -601,8 +622,7 @@ suite "Messaging API, SubscriptionManager": var publisher: WakuNode lockNewGlobalBrokerContext: - publisher = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + publisher = newTestWakuNode(generateSecp256k1Key()) publisher.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on publisher" ) @@ -620,8 +640,7 @@ suite "Messaging API, SubscriptionManager": var meshBuddy: WakuNode lockNewGlobalBrokerContext: - meshBuddy = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + meshBuddy = newTestWakuNode(generateSecp256k1Key()) meshBuddy.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on meshBuddy" ) @@ -639,20 +658,22 @@ suite "Messaging API, SubscriptionManager": await meshBuddy.connectToNodes(@[publisherPeerInfo]) - let conf = createApiNodeConf(cli_args.WakuMode.Edge, numShards) - var subscriber: Waku + let conf = createApiNodeConf(messaging_conf.LogosDeliveryMode.Edge, numShards) + var subscriber: LogosDelivery lockNewGlobalBrokerContext: - subscriber = (await createNode(conf)).expect("Failed to create edge subscriber") - subscriber.mountMessagingClient().expect("Failed to mount messaging") + subscriber = + (await LogosDelivery.new(conf)).expect("Failed to create edge subscriber") (await subscriber.start()).expect("Failed to start edge subscriber") # Connect edge subscriber to both filter servers so selectPeers finds both - await subscriber.node.connectToNodes(@[publisherPeerInfo, meshBuddyPeerInfo]) + await subscriber.waku.node.connectToNodes(@[publisherPeerInfo, meshBuddyPeerInfo]) let testTopic = ContentTopic("/waku/2/failover-test/proto") - let shard = subscriber.node.getRelayShard(testTopic) + let shard = subscriber.waku.node.getRelayShard(testTopic) - (await subscriber.subscribe(testTopic)).expect("Failed to subscribe") + (await subscriber.messagingClient.subscribe(testTopic)).expect( + "Failed to subscribe" + ) # Wait for dialing both filter servers (HealthyThreshold = 2) check await edgePeersReached(subscriber, shard, 2) @@ -660,7 +681,7 @@ suite "Messaging API, SubscriptionManager": # Verify message delivery with both servers alive await waitForMesh(publisher, shard) - var eventManager = newReceiveEventListenerManager(subscriber.brokerCtx, 1) + var eventManager = newReceiveEventListenerManager(subscriber.waku.brokerCtx, 1) let msg1 = WakuMessage( payload: "Before failover".toBytes(), contentTopic: testTopic, @@ -674,14 +695,14 @@ suite "Messaging API, SubscriptionManager": await eventManager.teardown() # Disconnect meshBuddy from edge (keeps relay mesh alive for publishing) - await subscriber.node.disconnectNode(meshBuddyPeerInfo) + await subscriber.waku.node.disconnectNode(meshBuddyPeerInfo) # Wait for the dead peer to be pruned check await edgePeersDroppedBelow(subscriber, shard, 2) - check subscriber.node.subscriptionManager.edgeFilterPeerCount(shard) >= 1 + check subscriber.waku.node.subscriptionManager.edgeFilterPeerCount(shard) >= 1 # Verify messages still arrive through the surviving filter server (publisher) - eventManager = newReceiveEventListenerManager(subscriber.brokerCtx, 1) + eventManager = newReceiveEventListenerManager(subscriber.waku.brokerCtx, 1) let msg2 = WakuMessage( payload: "After failover".toBytes(), contentTopic: testTopic, @@ -710,8 +731,7 @@ suite "Messaging API, SubscriptionManager": var publisher: WakuNode lockNewGlobalBrokerContext: - publisher = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + publisher = newTestWakuNode(generateSecp256k1Key()) publisher.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on publisher" ) @@ -729,8 +749,7 @@ suite "Messaging API, SubscriptionManager": var meshBuddy: WakuNode lockNewGlobalBrokerContext: - meshBuddy = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + meshBuddy = newTestWakuNode(generateSecp256k1Key()) meshBuddy.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on meshBuddy" ) @@ -748,8 +767,7 @@ suite "Messaging API, SubscriptionManager": var sparePeer: WakuNode lockNewGlobalBrokerContext: - sparePeer = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + sparePeer = newTestWakuNode(generateSecp256k1Key()) sparePeer.mountMetadata(3, toSeq(0'u16 ..< numShards)).expect( "Failed to mount metadata on sparePeer" ) @@ -768,34 +786,36 @@ suite "Messaging API, SubscriptionManager": await meshBuddy.connectToNodes(@[publisherPeerInfo]) await sparePeer.connectToNodes(@[publisherPeerInfo]) - let conf = createApiNodeConf(cli_args.WakuMode.Edge, numShards) - var subscriber: Waku + let conf = createApiNodeConf(messaging_conf.LogosDeliveryMode.Edge, numShards) + var subscriber: LogosDelivery lockNewGlobalBrokerContext: - subscriber = (await createNode(conf)).expect("Failed to create edge subscriber") - subscriber.mountMessagingClient().expect("Failed to mount messaging") + subscriber = + (await LogosDelivery.new(conf)).expect("Failed to create edge subscriber") (await subscriber.start()).expect("Failed to start edge subscriber") - await subscriber.node.connectToNodes( + await subscriber.waku.node.connectToNodes( @[publisherPeerInfo, meshBuddyPeerInfo, sparePeerInfo] ) let testTopic = ContentTopic("/waku/2/replacement-test/proto") - let shard = subscriber.node.getRelayShard(testTopic) + let shard = subscriber.waku.node.getRelayShard(testTopic) - (await subscriber.subscribe(testTopic)).expect("Failed to subscribe") + (await subscriber.messagingClient.subscribe(testTopic)).expect( + "Failed to subscribe" + ) # Wait for 2 confirmed peers (HealthyThreshold). The 3rd is available but not dialed. check await edgePeersReached(subscriber, shard, 2) - require subscriber.node.subscriptionManager.edgeFilterPeerCount(shard) == 2 + require subscriber.waku.node.subscriptionManager.edgeFilterPeerCount(shard) == 2 - await subscriber.node.disconnectNode(meshBuddyPeerInfo) + await subscriber.waku.node.disconnectNode(meshBuddyPeerInfo) # Wait for the sub loop to detect the loss and dial a replacement check await edgePeersReached(subscriber, shard, 2) await waitForMesh(publisher, shard) - var eventManager = newReceiveEventListenerManager(subscriber.brokerCtx, 1) + var eventManager = newReceiveEventListenerManager(subscriber.waku.brokerCtx, 1) let msg = WakuMessage( payload: "After replacement".toBytes(), contentTopic: testTopic, diff --git a/tests/api/test_conf.nim b/tests/api/test_conf.nim new file mode 100644 index 000000000..705c00825 --- /dev/null +++ b/tests/api/test_conf.nim @@ -0,0 +1,422 @@ +{.used.} + +import std/[options, net], results, chronos, testutils/unittests +import brokers/broker_context +import logos_delivery +import logos_delivery/api/conf/logos_delivery_conf_json +import logos_delivery/waku/factory/[waku_conf, networks_config] +import logos_delivery/waku/common/logging + +suite "MessagingClientConf - mode expansion (toWakuNodeConf)": + test "Core mode enables relay + service protocols": + let kc = MessagingClientConf().toWakuNodeConf(LogosDeliveryMode.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().toWakuNodeConf(LogosDeliveryMode.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.toWakuNodeConf(LogosDeliveryMode.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().toWakuNodeConf(LogosDeliveryMode.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.toWakuNodeConf(LogosDeliveryMode.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 = toWakuNodeConf(merged, LogosDeliveryMode.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 resolves to a full Core node conf": + let lc = parseLogosDeliveryConf("{}").valueOr: + raiseAssert error + check: + lc.messagingConf.isSome() # full node: messaging + channels mounted + lc.channelsConf.isSome() + WakuNodeConf(lc.kernelConf).relay == true # Core enables relay + WakuNodeConf(lc.kernelConf).clusterId.isNone() # no cluster set + + test "mode + preset fold into the kernel conf": + let lc = parseLogosDeliveryConf("""{"mode": "Edge", "preset": "logostest"}""").valueOr: + raiseAssert error + let kc = WakuNodeConf(lc.kernelConf) + check: + kc.preset == "logostest" + kc.relay == false # Edge disables relay + + test "messaging overrides split: kernel fields to the kernel, reliability to the record": + let lc = parseLogosDeliveryConf( + """{"mode": "Core", "messagingOverrides": {"clusterId": 7, "reliabilityEnabled": true}}""" + ).valueOr: + raiseAssert error + require lc.messagingConf.isSome() + check: + WakuNodeConf(lc.kernelConf).clusterId == some(7'u16) # kernel field + lc.messagingConf.get().reliabilityEnabled == some(true) # messaging-only + + test "messaging overrides are recorded verbatim, unset fields left none": + let lc = parseLogosDeliveryConf("""{"messagingOverrides": {"clusterId": 7}}""").valueOr: + raiseAssert error + require lc.messagingConf.isSome() + let overrides = lc.messagingConf.get() + check: + overrides.clusterId == some(7'u16) # the user's override, kept as given + overrides.reliabilityEnabled.isNone() # user didn't set it, so it stays none + + test "a preset resolves reliability into the messaging record": + let lc = parseLogosDeliveryConf("""{"preset": "twn"}""").valueOr: + raiseAssert error + # the user set no reliability override, but the preset supplies one + require lc.messagingConf.isSome() + check lc.messagingConf.get().reliabilityEnabled.isSome() + + test "channelsOverrides fold into the channel conf": + let lc = parseLogosDeliveryConf( + """{"channelsOverrides": {"rateLimitEnabled": true, "sdsMaxRetransmissions": 9}}""" + ).valueOr: + raiseAssert error + require lc.channelsConf.isSome() + let channels = lc.channelsConf.get() + check: + channels.rateLimitEnabled == some(true) + channels.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 "a truly unknown top-level key is rejected (flat walker rejects the field)": + # NB: a *known* WakuNodeConf field at top level (e.g. logLevel) is now accepted + # as a flat blob; only a field that is not a WakuNodeConf field is rejected. + check parseLogosDeliveryConf("""{"totallyBogusField": "x", "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 + let kc = WakuNodeConf(lc.kernelConf) + require lc.messagingConf.isSome() + check: + kc.clusterId == some(7'u16) + lc.messagingConf.get().reliabilityEnabled == some(true) # messaging-only + kc.tcpPort == 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 + let kc = WakuNodeConf(lc.kernelConf) + check: + kc.logLevel == logging.LogLevel.DEBUG + kc.logFormat == logging.LogFormat.JSON + kc.nodekey.isSome() + + test "a null value leaves the field unset": + let lc = parseLogosDeliveryConf( + """{"messagingOverrides": {"store": null, "clusterId": 7}}""" + ).valueOr: + raiseAssert error + let kc = WakuNodeConf(lc.kernelConf) + check: + kc.clusterId == some(7'u16) + kc.store == false # null left store unset; Core does not enable it + + 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 WakuNodeConf(lc.kernelConf).ethClientUrls.len == 1 + + test "store backend fields fold into the kernel conf": + 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 + let kc = WakuNodeConf(lc.kernelConf) + check: + kc.store == true + kc.storeMessageDbUrl == "sqlite://test.db" + kc.storeMessageRetentionPolicy == "time:3600" + kc.storeMaxNumDbConnections == 7 + + test "fleet mode parses a raw kernelConf": + let lc = parseLogosDeliveryConf( + """{"mode": "fleet", "kernelConf": {"relay": false, "maxMessageSize": "150KiB"}}""" + ).valueOr: + raiseAssert error + check: + lc.messagingConf.isNone() # kernel-only: no upper layers + lc.channelsConf.isNone() + let kc = WakuNodeConf(lc.kernelConf) + check: + kc.relay == false + kc.maxMessageSize == "150KiB" + + test "fleet mode requires a kernelConf": + check parseLogosDeliveryConf("""{"mode": "fleet"}""").isErr() + + test "fleet mode rejects anything besides kernelConf": + check parseLogosDeliveryConf( + """{"mode": "fleet", "kernelConf": {}, "messagingOverrides": {"clusterId": 1}}""" + ) + .isErr() + # a preset for a fleet node goes inside kernelConf (WakuNodeConf.preset); fleet + # treats kernelConf as a finished object and overlays nothing onto it + check parseLogosDeliveryConf( + """{"mode": "fleet", "kernelConf": {}, "preset": "twn"}""" + ) + .isErr() + + test "kernelConf is rejected outside fleet mode": + # kernelConf is a fleet-only wrapper. Under Core/Edge it is neither consumed by the + # structured path nor a flat kernel field, so it must surface as an unknown key. + check parseLogosDeliveryConf("""{"mode": "core", "kernelConf": {}}""").isErr() + +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( + LogosDeliveryMode.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( + LogosDeliveryMode.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)).toWakuNodeConf( + LogosDeliveryMode.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 + +suite "LogosDelivery.new - raw kernel construction": + asyncTest "a fleet node mounts the kernel only; start/stop tolerate the nil layers": + let kernel = MessagingClientConf(listenIpv4: some(parseIpAddress("0.0.0.0"))).toWakuNodeConf( + LogosDeliveryMode.Core + ).valueOr: + raiseAssert error + var node: LogosDelivery + lockNewGlobalBrokerContext: + node = (await LogosDelivery.new(KernelConf(kernel))).valueOr: + raiseAssert error + check: + not node.waku.isNil() + node.messagingClient.isNil() # no messaging client on a fleet node + node.reliableChannelManager.isNil() # no channel manager either + # start()/stop() must skip the nil messaging + channel layers, not deref them + (await node.start()).expect("start") + (await node.stop()).expect("stop") + +# [Legacy flat JSON config] These suites cover the legacy flat shape; delete them +# together with parseFlatConf when flat-shape support is dropped. +suite "parseLogosDeliveryConf - flat WakuNodeConf shape (interop compatibility)": + test "a flat blob of kernel fields is detected and parsed as a full stack": + let lc = parseLogosDeliveryConf( + """{"relay": true, "clusterId": 7, "store": true, "filter": false}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(lc.kernelConf).relay == true + WakuNodeConf(lc.kernelConf).clusterId == some(7'u16) + lc.messagingConf.isSome() # full stack + lc.channelsConf.isSome() + + test "flat blob carrying mode: mode expands to flags, explicit flags override": + let lc = parseLogosDeliveryConf( + """{"mode": "Edge", "relay": true, "clusterId": 7}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(lc.kernelConf).relay == true + # explicit flat field overrides the Edge default + WakuNodeConf(lc.kernelConf).filter == false # Edge default, not set explicitly + WakuNodeConf(lc.kernelConf).clusterId == some(7'u16) + + test "flat blob's reliabilityEnabled routes to the messaging conf, not the kernel": + let lc = parseLogosDeliveryConf("""{"relay": true, "reliabilityEnabled": true}""").valueOr: + raiseAssert error + check: + lc.messagingConf.get().reliabilityEnabled == some(true) + + test "an unknown key in a flat blob is rejected": + check parseLogosDeliveryConf("""{"relay": true, "bogusKey": 1}""").isErr() + + test "a flat blob's preset lifts reliability into the messaging record": + # twn defines a p2pReliability; the flat path must resolve it into the messaging + # conf (the kernel no longer carries reliability), matching master. + let lc = parseLogosDeliveryConf("""{"preset": "twn", "relay": true}""").valueOr: + raiseAssert error + let fromPreset = resolvePreset("twn").valueOr: + raiseAssert error + check lc.messagingConf.get().reliabilityEnabled == fromPreset.reliabilityEnabled + + test "port 0 (auto-allocate) is accepted in a flat blob": + let lc = parseLogosDeliveryConf( + """{"relay": true, "tcpPort": 0, "discv5UdpPort": 0}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(lc.kernelConf).tcpPort == Port(0) + WakuNodeConf(lc.kernelConf).discv5UdpPort == Port(0) + + test "port 0 is accepted in structured messagingOverrides": + let lc = parseLogosDeliveryConf("""{"messagingOverrides": {"tcp-port": 0}}""").valueOr: + raiseAssert error + check lc.messagingConf.get().p2pTcpPort == some(Port(0)) + + test "mode/preset with no bare field stays structured; a bare field flips it to flat": + # No bare kernel field -> structured: mode owns relay, no per-flag override. + let structured = parseLogosDeliveryConf( + """{"mode": "Edge", "preset": "logostest"}""" + ).valueOr: + raiseAssert error + check WakuNodeConf(structured.kernelConf).relay == false # Edge, structured + # Adding a bare kernel field (relay) flips to flat, where the explicit flag wins; + # relay == true is only reachable via the flat path, so it proves the routing. + let flat = parseLogosDeliveryConf( + """{"mode": "Edge", "preset": "logostest", "relay": true}""" + ).valueOr: + raiseAssert error + check: + WakuNodeConf(flat.kernelConf).relay == true + WakuNodeConf(flat.kernelConf).preset == "logostest" + + test "a wrapper key alongside a bare kernel field is rejected, not split": + # A wrapper key (messagingOverrides) marks the structured shape, so the blob does + # not flip to flat; the leftover bare field (relay) then has no structured home and + # is rejected. Guards against the discriminator silently splitting a mixed object. + check parseLogosDeliveryConf("""{"messagingOverrides": {}, "relay": true}""").isErr() diff --git a/tests/api/test_node_conf.nim b/tests/api/test_node_conf.nim index a5bff3906..6048a5951 100644 --- a/tests/api/test_node_conf.nim +++ b/tests/api/test_node_conf.nim @@ -1,196 +1,10 @@ {.used.} -import std/[options, strutils], results, stint, testutils/unittests -import json_serialization, confutils, confutils/std/net +import std/options, results, testutils/unittests import tools/confutils/cli_args, - tools/confutils/conf_from_json, - logos_delivery/waku/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": @@ -241,6 +55,44 @@ suite "WakuNodeConf - preset integration": check: wakuConf.clusterId == 2 + test "StatusProd preset applies StatusProdConf": + ## 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: + 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: @@ -252,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/waku/api/api_conf - -suite "NodeConfig (deprecated) - toWakuConf": - test "Minimal configuration": - let nodeConfig = NodeConfig.init(ethRpcEndpoints = @["http://someaddress"]) - let wakuConfRes = api_conf.toWakuConf(nodeConfig) - let wakuConf = wakuConfRes.valueOr: - raiseAssert error - wakuConf.validate().isOkOr: - raiseAssert error - check: - wakuConf.clusterId == 1 - wakuConf.shardingConf.numShardsInCluster == 8 - wakuConf.staticNodes.len == 0 - - test "Edge mode configuration": - let protocolsConfig = ProtocolsConfig.init(entryNodes = @[], clusterId = 1) - let nodeConfig = - NodeConfig.init(mode = api_conf.WakuMode.Edge, protocolsConfig = protocolsConfig) - let wakuConfRes = api_conf.toWakuConf(nodeConfig) - require wakuConfRes.isOk() - let wakuConf = wakuConfRes.get() - require wakuConf.validate().isOk() - check: - wakuConf.relay == false - wakuConf.lightPush == false - wakuConf.peerExchangeService == true - - test "Core mode configuration": - let protocolsConfig = ProtocolsConfig.init(entryNodes = @[], clusterId = 1) - let nodeConfig = - NodeConfig.init(mode = api_conf.WakuMode.Core, protocolsConfig = protocolsConfig) - let wakuConfRes = api_conf.toWakuConf(nodeConfig) - require wakuConfRes.isOk() - let wakuConf = wakuConfRes.get() - require wakuConf.validate().isOk() - check: - wakuConf.relay == true - wakuConf.lightPush == true - wakuConf.peerExchangeService == true - -{.pop.} - -suite "WakuConfBuilder - store retention policies": - test "Multiple retention policies": - ## Given - var b = WakuConfBuilder.init() - b.withP2pTcpPort(0'u16) - b.storeServiceConf.withEnabled(true) - b.storeServiceConf.withDbUrl("sqlite://test.db") - b.storeServiceConf.withRetentionPolicies( - "time:86400 ; capacity:10000; size : 50GB" - ) - - ## When - let wakuConf = b.build().valueOr: - raiseAssert error - - ## Then - require wakuConf.storeServiceConf.isSome() - let storeConf = wakuConf.storeServiceConf.get() - check storeConf.retentionPolicies == @["time:86400", "capacity:10000", "size:50GB"] - - test "Duplicated retention policies returns error": - ## Given - var b = WakuConfBuilder.init() - b.storeServiceConf.withEnabled(true) - b.storeServiceConf.withDbUrl("sqlite://test.db") - b.storeServiceConf.withRetentionPolicies("time:86400;time:800;capacity:10000") - - ## When - let wakuConfRes = b.build() - check wakuConfRes.isErr() - check wakuConfRes.error.contains("duplicated retention policy type") - - test "Incorrect retention policy type returns error": - ## Given - var b = WakuConfBuilder.init() - b.storeServiceConf.withEnabled(true) - b.storeServiceConf.withDbUrl("sqlite://test.db") - b.storeServiceConf.withRetentionPolicies("capaity:10000") - - ## When - let wakuConfRes = b.build() - - ## Then - check wakuConfRes.isErr() - check wakuConfRes.error.contains("unknown retention policy type") - - test "Store disabled - no retention policy applied": - ## Given - var b = WakuConfBuilder.init() - b.withP2pTcpPort(0'u16) - # storeServiceConf not enabled - - ## When - let wakuConf = b.build().valueOr: - raiseAssert error - - ## Then - check wakuConf.storeServiceConf.isNone() diff --git a/tests/channels/test_reliable_channel_send_receive.nim b/tests/channels/test_reliable_channel_send_receive.nim index ca6177e9d..b76851006 100644 --- a/tests/channels/test_reliable_channel_send_receive.nim +++ b/tests/channels/test_reliable_channel_send_receive.nim @@ -10,8 +10,10 @@ import ../testlib/[common, wakucore, wakunode, testasync] import logos_delivery import logos_delivery/waku/[waku_node, waku_core] import logos_delivery/waku/factory/waku_conf -import logos_delivery/waku/events/message_events as waku_message_events +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/conf/messaging_conf import logos_delivery/channels/reliable_channel_manager import logos_delivery/channels/encryption/noop_encryption @@ -26,15 +28,14 @@ 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() + .toWakuNodeConf(messaging_conf.LogosDeliveryMode.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 @@ -53,14 +54,12 @@ suite "Reliable Channel - ingress": contentTopic = ContentTopic("/reliable-channel/test/proto") let appPayload = "hello reliable channel".toBytes() - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager ## Noop encryption providers so the Encrypt/Decrypt brokers have @@ -121,14 +120,12 @@ suite "Reliable Channel - ingress": contentTopic = ContentTopic("/reliable-channel/test/proto") let appPayload = "foreign payload".toBytes() - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() @@ -169,7 +166,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 @@ -180,29 +177,27 @@ suite "Reliable Channel - send state machine": contentTopic = ContentTopic("/reliable-channel/test/sm-success") fakeMsgReqId = RequestId("fake-msg-req-1") - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() var sendCalls = 0 - let fakeSend: SendHandler = proc( - env: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - sendCalls.inc - return ok(fakeMsgReqId) + MessagingSend.replaceProvider( + brokerCtx, + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + sendCalls.inc + return ok(fakeMsgReqId), + ).isOkOr: + raiseAssert "replaceProvider failed: " & error discard manager - .createReliableChannel( - channelId, contentTopic, SdsParticipantID("local"), sendHandler = fakeSend - ) + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) .expect("createReliableChannel") let sentFut = newFuture[RequestId]("channel-sent") @@ -239,7 +234,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. @@ -247,30 +242,28 @@ suite "Reliable Channel - send state machine": channelId = ChannelId("sm-multi-channel") contentTopic = ContentTopic("/reliable-channel/test/sm-multi") - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() var msgReqIds: seq[RequestId] - let fakeSend: SendHandler = proc( - env: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - let id = RequestId("fake-msg-req-" & $(msgReqIds.len + 1)) - msgReqIds.add(id) - return ok(id) + MessagingSend.replaceProvider( + brokerCtx, + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + let id = RequestId("fake-msg-req-" & $(msgReqIds.len + 1)) + msgReqIds.add(id) + return ok(id), + ).isOkOr: + raiseAssert "replaceProvider failed: " & error discard manager - .createReliableChannel( - channelId, contentTopic, SdsParticipantID("local"), sendHandler = fakeSend - ) + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) .expect("createReliableChannel") let sentFut = newFuture[RequestId]("channel-sent") @@ -336,7 +329,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() @@ -350,42 +343,39 @@ suite "Reliable Channel - send state machine": channelId = ChannelId("sm-race-channel") contentTopic = ContentTopic("/reliable-channel/test/sm-race") - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() var msgReqIds: seq[RequestId] var sendsReturned = 0 - let fakeSend: SendHandler = proc( - env: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - ## Call 2 fires the first segment's terminal event and then - ## yields, so the listener task runs while the second segment - ## is still mid-`await` in `onReadyToSend` — the exact race - ## window the regression test targets. - let id = RequestId("race-msg-req-" & $(msgReqIds.len + 1)) - msgReqIds.add(id) - if msgReqIds.len == 2: - waku_message_events.MessageSentEvent.emit( - brokerCtx, - waku_message_events.MessageSentEvent(requestId: msgReqIds[0], messageHash: ""), - ) - await sleepAsync(50.milliseconds) - sendsReturned.inc() - return ok(id) + MessagingSend.replaceProvider( + brokerCtx, + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + ## Call 2 fires the first segment's terminal event and then + ## yields, so the listener task runs while the second segment + ## is still mid-`await` in `onReadyToSend` — the exact race + ## window the regression test targets. + let id = RequestId("race-msg-req-" & $(msgReqIds.len + 1)) + msgReqIds.add(id) + if msgReqIds.len == 2: + MessageSentEvent.emit( + brokerCtx, MessageSentEvent(requestId: msgReqIds[0], messageHash: "") + ) + await sleepAsync(50.milliseconds) + sendsReturned.inc() + return ok(id), + ).isOkOr: + raiseAssert "replaceProvider failed: " & error discard manager - .createReliableChannel( - channelId, contentTopic, SdsParticipantID("local"), sendHandler = fakeSend - ) + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) .expect("createReliableChannel") var finalisedReqIds: seq[RequestId] @@ -459,25 +449,23 @@ suite "Reliable Channel - SDS persistence": Persistency.reset() removeDir(root) - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager lockNewGlobalBrokerContext: - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() - let fakeSend: SendHandler = proc( - env: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - return ok(RequestId("persist-msg-req-1")) + MessagingSend.replaceProvider( + globalBrokerContext(), + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + return ok(RequestId("persist-msg-req-1")), + ).isOkOr: + raiseAssert "replaceProvider failed: " & error discard manager - .createReliableChannel( - channelId, contentTopic, SdsParticipantID("local"), sendHandler = fakeSend - ) + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) .expect("createReliableChannel") discard (await manager.send(channelId, "persist me".toBytes())).expect("send") @@ -529,14 +517,12 @@ suite "Reliable Channel - SDS lifecycle": let payload1 = "first message".toBytes() let payload2 = "second message".toBytes() - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() @@ -602,14 +588,12 @@ suite "Reliable Channel - SDS lifecycle": contentTopic = ContentTopic("/reliable-channel/test/dup") let appPayload = "deliver once".toBytes() - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() @@ -661,14 +645,12 @@ suite "Reliable Channel - SDS lifecycle": channelId = ChannelId("sds-foreign-channel") contentTopic = ContentTopic("/reliable-channel/test/foreign") - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() @@ -723,14 +705,12 @@ suite "Reliable Channel - SDS lifecycle": Persistency.reset() removeDir(root) - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() @@ -807,30 +787,28 @@ suite "Reliable Channel - SDS protocol semantics": channelId = ChannelId("sds-semantics-channel") contentTopic = ContentTopic("/reliable-channel/test/semantics") - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() var capturedWires: seq[seq[byte]] - let fakeSend: SendHandler = proc( - env: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - ## Noop encryption is identity, so the envelope payload IS the SDS wire. - capturedWires.add(env.payload) - return ok(RequestId("semantics-req-" & $capturedWires.len)) + MessagingSend.replaceProvider( + brokerCtx, + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + ## Noop encryption is identity, so the envelope payload IS the SDS wire. + capturedWires.add(envelope.payload) + return ok(RequestId("semantics-req-" & $capturedWires.len)), + ).isOkOr: + raiseAssert "replaceProvider failed: " & error discard manager - .createReliableChannel( - channelId, contentTopic, SdsParticipantID("local"), sendHandler = fakeSend - ) + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) .expect("createReliableChannel") let remotePeer = @@ -877,29 +855,28 @@ suite "Reliable Channel - SDS protocol semantics": Persistency.reset() removeDir(root) - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() var capturedWires: seq[seq[byte]] - let fakeSend: SendHandler = proc( - env: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - capturedWires.add(env.payload) - return ok(RequestId("ack-req-" & $capturedWires.len)) + MessagingSend.replaceProvider( + brokerCtx, + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + ## Noop encryption is identity, so the envelope payload IS the SDS wire. + capturedWires.add(envelope.payload) + return ok(RequestId("ack-req-" & $capturedWires.len)), + ).isOkOr: + raiseAssert "replaceProvider failed: " & error discard manager - .createReliableChannel( - channelId, contentTopic, SdsParticipantID("local"), sendHandler = fakeSend - ) + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) .expect("createReliableChannel") discard (await manager.send(channelId, "needs ack".toBytes())).expect("send") @@ -967,14 +944,12 @@ suite "Reliable Channel - SDS protocol semantics": let payloads = @["chain first".toBytes(), "chain second".toBytes(), "chain third".toBytes()] - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() @@ -1044,14 +1019,12 @@ suite "Reliable Channel - SDS protocol semantics": contentTopic = ContentTopic("/reliable-channel/test/sync") let appPayload = "real message".toBytes() - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() @@ -1119,29 +1092,28 @@ suite "Reliable Channel - SDS protocol semantics": contentTopic = ContentTopic("/reliable-channel/test/unique-id") let appPayload = "ok".toBytes() - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager var brokerCtx: BrokerContext lockNewGlobalBrokerContext: brokerCtx = globalBrokerContext() - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager setNoopEncryption() var capturedWires: seq[seq[byte]] - let fakeSend: SendHandler = proc( - env: MessageEnvelope - ): Future[Result[RequestId, string]] {.async: (raises: [CatchableError]), gcsafe.} = - capturedWires.add(env.payload) - return ok(RequestId("unique-req-" & $capturedWires.len)) + MessagingSend.replaceProvider( + brokerCtx, + proc(envelope: MessageEnvelope): Future[Result[RequestId, string]] {.async.} = + ## Noop encryption is identity, so the envelope payload IS the SDS wire. + capturedWires.add(envelope.payload) + return ok(RequestId("unique-req-" & $capturedWires.len)), + ).isOkOr: + raiseAssert "replaceProvider failed: " & error discard manager - .createReliableChannel( - channelId, contentTopic, SdsParticipantID("local"), sendHandler = fakeSend - ) + .createReliableChannel(channelId, contentTopic, SdsParticipantID("local")) .expect("createReliableChannel") var deliveries: seq[seq[byte]] @@ -1189,12 +1161,10 @@ suite "Reliable Channel - SDS protocol semantics": (await waku.stop()).expect("stop") asyncTest "manager rejects operations on unknown channels": - var waku: Waku + var waku: LogosDelivery var manager: ReliableChannelManager lockNewGlobalBrokerContext: - waku = (await createNode(createApiNodeConf())).expect("createNode") - waku.mountMessagingClient().expect("mountMessagingClient") - waku.mountReliableChannelManager().expect("mountReliableChannelManager") + waku = (await LogosDelivery.new(createApiNodeConf())).expect("LogosDelivery.new") manager = waku.reliableChannelManager check (await manager.send(ChannelId("no-such-channel"), "x".toBytes())).isErr() diff --git a/tests/factory/test_node_factory.nim b/tests/factory/test_node_factory.nim index 6a08ccfd1..b4e8429a3 100644 --- a/tests/factory/test_node_factory.nim +++ b/tests/factory/test_node_factory.nim @@ -156,10 +156,10 @@ suite "Auto-port retry": b.withHttpPort(port) b.build().value.get() - let failRes = await startMetricsServerAndLogging(buildMetricsConf(takenPort), 0'u16) + let failRes = await startMetricsServerAndLogging(buildMetricsConf(takenPort)) check failRes.isErr() - let okRes = await startMetricsServerAndLogging(buildMetricsConf(freePort), 0'u16) + let okRes = await startMetricsServerAndLogging(buildMetricsConf(freePort)) check okRes.isOk() if okRes.isOk(): await okRes.get().server.close() @@ -171,7 +171,7 @@ suite "Auto-port retry": discard let nodeKey = generateSecp256k1Key() - let node = newTestWakuNode(nodeKey, parseIpAddress("0.0.0.0"), Port(0)) + let node = newTestWakuNode(nodeKey) await node.start() defer: await node.stop() @@ -204,7 +204,6 @@ suite "Auto-port retry": node.rng, nodeKey, parseIpAddress("0.0.0.0"), - 0'u16, ) check failRes.isErr() @@ -217,7 +216,6 @@ suite "Auto-port retry": node.rng, nodeKey, parseIpAddress("0.0.0.0"), - 0'u16, ) check okRes.isOk() if okRes.isOk(): diff --git a/tests/node/test_all.nim b/tests/node/test_all.nim index 2846fdb7f..a53a83086 100644 --- a/tests/node/test_all.nim +++ b/tests/node/test_all.nim @@ -7,4 +7,5 @@ import ./test_wakunode_peer_exchange, ./test_wakunode_store, ./test_wakunode_peer_manager, - ./test_wakunode_health_monitor + ./test_wakunode_health_monitor, + ./test_wakunode_restart diff --git a/tests/node/test_wakunode_filter.nim b/tests/node/test_wakunode_filter.nim index 65c162c0a..dd92c0c37 100644 --- a/tests/node/test_wakunode_filter.nim +++ b/tests/node/test_wakunode_filter.nim @@ -68,12 +68,9 @@ suite "Waku Filter - End to End": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode( - serverKey, parseIpAddress("0.0.0.0"), Port(23450), maxConnections = 300 - ) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(23451)) - clientClone = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(23451)) - # Used for testing client restarts + server = newTestWakuNode(serverKey, maxConnections = 300) + client = newTestWakuNode(clientKey) + clientClone = newTestWakuNode(clientKey) # Used for testing client restarts await allFutures(server.start(), client.start()) @@ -157,7 +154,7 @@ suite "Waku Filter - End to End": # Given a server node with Relay without Filter let serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) await server.start() (await server.mountRelay()).isOkOr: @@ -192,7 +189,7 @@ suite "Waku Filter - End to End": ## This line above causes the test to fail. I think ConnManager ## is not prepare for restarts and maybe we don't need that restart feature. - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(23451)) + client = newTestWakuNode(clientKey) await client.start() # Mimic restart by starting the clone # pushHandlerFuture = newFuture[(string, WakuMessage)]() @@ -587,9 +584,7 @@ suite "Waku Filter - End to End": var peers = newSeq[WakuNode](MaxFilterPeers) for index in 0 ..< MaxFilterPeers: - peers[index] = newTestWakuNode( - generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(23551 + index) - ) + peers[index] = newTestWakuNode(generateSecp256k1Key()) await peers[index].start() await peers[index].mountFilterClient() @@ -740,15 +735,9 @@ suite "Waku Filter - End to End": peerManager = server.peerManager let - client1 = newTestWakuNode( - generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(23552) - ) - client2 = newTestWakuNode( - generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(23553) - ) - client3 = newTestWakuNode( - generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(23554) - ) + client1 = newTestWakuNode(generateSecp256k1Key()) + client2 = newTestWakuNode(generateSecp256k1Key()) + client3 = newTestWakuNode(generateSecp256k1Key()) filterSubscribeRequest = createRequest( filterSubscribeType = FilterSubscribeType.SUBSCRIBE, pubsubTopic = some(DefaultPubsubTopic), diff --git a/tests/node/test_wakunode_health_monitor.nim b/tests/node/test_wakunode_health_monitor.nim index 480cb235c..af90a9be0 100644 --- a/tests/node/test_wakunode_health_monitor.nim +++ b/tests/node/test_wakunode_health_monitor.nim @@ -19,14 +19,17 @@ import node/waku_node/store, node/waku_node/lightpush, node/waku_node/filter, - events/health_events, - events/peer_events, + api/events/health_events, + api/events/peer_events, waku_archive, ] import ../testlib/[wakunode, wakucore], ../waku_archive/archive_utils import logos_delivery/waku/node/subscription_manager +import logos_delivery/waku/waku +import logos_delivery/waku/factory/waku_state_info import logos_delivery/messaging/messaging_client +import logos_delivery/messaging/messaging_client_lifecycle const MockDLow = 4 # Mocked GossipSub DLow value @@ -228,8 +231,15 @@ suite "Health Monitor - events": nodeA.mountMetadata(1, @[0'u16]).expect("Node A failed to mount metadata") await nodeA.start() - let ds = - MessagingClient.new(false, nodeA).expect("Failed to create MessagingClient") + # MessagingClient now depends on the Waku kernel, not the raw node. Only + # `waku.node` is read on the messaging path; `conf`/`stateInfo` are supplied + # solely to satisfy Waku's {.requiresInit.} fields. + let waku = Waku( + node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA) + ) + let ds = MessagingClient + .new(MessagingClientConf(reliabilityEnabled: some(false)), waku) + .expect("Failed to create MessagingClient") ds.start().expect("Failed to start MessagingClient") let monitorA = NodeHealthMonitor.new(nodeA) @@ -332,8 +342,15 @@ suite "Health Monitor - events": nodeA.mountMetadata(1, @[0'u16]).expect("Node A failed to mount metadata") await nodeA.start() - let ds = - MessagingClient.new(false, nodeA).expect("Failed to create MessagingClient") + # MessagingClient now depends on the Waku kernel, not the raw node. Only + # `waku.node` is read on the messaging path; `conf`/`stateInfo` are supplied + # solely to satisfy Waku's {.requiresInit.} fields. + let waku = Waku( + node: nodeA, conf: defaultTestWakuConf(), stateInfo: WakuStateInfo.init(nodeA) + ) + let ds = MessagingClient + .new(MessagingClientConf(reliabilityEnabled: some(false)), waku) + .expect("Failed to create MessagingClient") ds.start().expect("Failed to start MessagingClient") let subMgr = nodeA.subscriptionManager diff --git a/tests/node/test_wakunode_legacy_lightpush.nim b/tests/node/test_wakunode_legacy_lightpush.nim index 4dfe21e9c..9301e9e01 100644 --- a/tests/node/test_wakunode_legacy_lightpush.nim +++ b/tests/node/test_wakunode_legacy_lightpush.nim @@ -15,7 +15,7 @@ import waku_lightpush_legacy, waku_lightpush_legacy/common, waku_lightpush_legacy/protocol_metrics, - waku_rln_relay, + rln, ], ../testlib/[wakucore, wakunode, testasync, futures, testutils], ../resources/payloads, @@ -36,8 +36,8 @@ suite "Waku Legacy Lightpush - End To End": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) await allFutures(server.start(), client.start()) await server.start() @@ -59,8 +59,7 @@ suite "Waku Legacy Lightpush - End To End": suite "Assessment of Message Relaying Mechanisms": asyncTest "Via 11/WAKU2-RELAY from Relay/Full Node": # Given a light lightpush client - let lightpushClient = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + let lightpushClient = newTestWakuNode(generateSecp256k1Key()) lightpushClient.mountLegacyLightpushClient() # When the client publishes a message @@ -100,6 +99,7 @@ suite "RLN Proofs as a Lightpush Service": client {.threadvar.}: WakuNode anvilProc {.threadvar.}: Process manager {.threadvar.}: OnchainGroupManager + wakuRlnConfig {.threadvar.}: WakuRlnConfig serverRemotePeerInfo {.threadvar.}: RemotePeerInfo pubsubTopic {.threadvar.}: PubsubTopic @@ -111,15 +111,15 @@ suite "RLN Proofs as a Lightpush Service": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) anvilProc = runAnvil(stateFile = some(DEFAULT_ANVIL_STATE_PATH)) manager = waitFor setupOnchainGroupManager(deployContracts = false) # mount rln-relay # match prod epoch window to reduce test flake - let wakuRlnConfig = getWakuRlnConfig( + wakuRlnConfig = getWakuRlnConfig( manager = manager, index = MembershipIndex(1), epochSizeSec = 600 ) @@ -128,11 +128,11 @@ suite "RLN Proofs as a Lightpush Service": (await server.mountRelay()).isOkOr: assert false, "Failed to mount relay" - await server.mountRlnRelay(wakuRlnConfig) + await server.setRlnValidator(wakuRlnConfig) check (await server.mountLegacyLightPush()).isOk() client.mountLegacyLightPushClient() - let manager1 = cast[OnchainGroupManager](server.wakuRlnRelay.groupManager) + let manager1 = cast[OnchainGroupManager](server.rln.groupManager) let idCredentials1 = generateCredentials() (waitFor manager1.register(idCredentials1, UserMessageLimit(20))).isOkOr: @@ -159,13 +159,18 @@ suite "RLN Proofs as a Lightpush Service": suite "Lightpush attaching RLN proofs": asyncTest "Message is published when RLN enabled": # Given a light lightpush client - let lightpushClient = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + let lightpushClient = newTestWakuNode(generateSecp256k1Key()) lightpushClient.mountLegacyLightPushClient() + # Attach the RLN proof. In production the client mounts RLN and generates the + # proof in legacyLightpushPublish; here we generate it using the server's RLN + # instance since both ends share group state via the in-memory manager. + let msgWithProof = + (await checkAndGenerateRLNProof(some(server.rln), message)).get() + # When the client publishes a message let publishResponse = await lightpushClient.legacyLightpushPublish( - some(pubsubTopic), message, serverRemotePeerInfo + some(pubsubTopic), msgWithProof, serverRemotePeerInfo ) if not publishResponse.isOk(): @@ -180,11 +185,11 @@ suite "Waku Legacy Lightpush message delivery": ## Setup let lightNodeKey = generateSecp256k1Key() - lightNode = newTestWakuNode(lightNodeKey, parseIpAddress("0.0.0.0"), Port(0)) + lightNode = newTestWakuNode(lightNodeKey) bridgeNodeKey = generateSecp256k1Key() - bridgeNode = newTestWakuNode(bridgeNodeKey, parseIpAddress("0.0.0.0"), Port(0)) + bridgeNode = newTestWakuNode(bridgeNodeKey) destNodeKey = generateSecp256k1Key() - destNode = newTestWakuNode(destNodeKey, parseIpAddress("0.0.0.0"), Port(0)) + destNode = newTestWakuNode(destNodeKey) await allFutures(destNode.start(), bridgeNode.start(), lightNode.start()) @@ -234,7 +239,7 @@ suite "Waku Legacy Lightpush mounting behavior": ## Given a node without Relay mounted let key = generateSecp256k1Key() - node = newTestWakuNode(key, parseIpAddress("0.0.0.0"), Port(0)) + node = newTestWakuNode(key) # Do not mount Relay on purpose check node.wakuRelay.isNil() diff --git a/tests/node/test_wakunode_lightpush.nim b/tests/node/test_wakunode_lightpush.nim index df6c7f8e0..6e87ab237 100644 --- a/tests/node/test_wakunode_lightpush.nim +++ b/tests/node/test_wakunode_lightpush.nim @@ -8,8 +8,7 @@ import libp2p/crypto/crypto import - logos_delivery/waku/ - [waku_core, node/peer_manager, waku_node, waku_lightpush, waku_rln_relay], + logos_delivery/waku/[waku_core, node/peer_manager, waku_node, waku_lightpush, rln], ../testlib/[wakucore, wakunode, testasync, futures], ../resources/payloads, ../waku_rln_relay/[rln/waku_rln_relay_utils, utils_onchain] @@ -34,8 +33,8 @@ suite "Waku Lightpush - End To End": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) await allFutures(server.start(), client.start()) await server.start() @@ -56,8 +55,7 @@ suite "Waku Lightpush - End To End": suite "Assessment of Message Relaying Mechanisms": asyncTest "Via 11/WAKU2-RELAY from Relay/Full Node": # Given a light lightpush client - let lightpushClient = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + let lightpushClient = newTestWakuNode(generateSecp256k1Key()) lightpushClient.mountLightpushClient() # When the client publishes a message @@ -98,6 +96,7 @@ suite "RLN Proofs as a Lightpush Service": client {.threadvar.}: WakuNode anvilProc {.threadvar.}: Process manager {.threadvar.}: OnchainGroupManager + wakuRlnConfig {.threadvar.}: WakuRlnConfig serverRemotePeerInfo {.threadvar.}: RemotePeerInfo pubsubTopic {.threadvar.}: PubsubTopic @@ -109,15 +108,15 @@ suite "RLN Proofs as a Lightpush Service": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) anvilProc = runAnvil(stateFile = some(DEFAULT_ANVIL_STATE_PATH)) manager = waitFor setupOnchainGroupManager(deployContracts = false) # mount rln-relay # match prod epoch window to reduce test flake - let wakuRlnConfig = getWakuRlnConfig( + wakuRlnConfig = getWakuRlnConfig( manager = manager, index = MembershipIndex(1), epochSizeSec = 600 ) @@ -126,11 +125,11 @@ suite "RLN Proofs as a Lightpush Service": (await server.mountRelay()).isOkOr: assert false, "Failed to mount relay" - await server.mountRlnRelay(wakuRlnConfig) + await server.setRlnValidator(wakuRlnConfig) check (await server.mountLightPush()).isOk() client.mountLightPushClient() - let manager1 = cast[OnchainGroupManager](server.wakuRlnRelay.groupManager) + let manager1 = cast[OnchainGroupManager](server.rln.groupManager) let idCredentials1 = generateCredentials() (waitFor manager1.register(idCredentials1, UserMessageLimit(20))).isOkOr: @@ -157,13 +156,18 @@ suite "RLN Proofs as a Lightpush Service": suite "Lightpush attaching RLN proofs": asyncTest "Message is published when RLN enabled": # Given a light lightpush client - let lightpushClient = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + let lightpushClient = newTestWakuNode(generateSecp256k1Key()) lightpushClient.mountLightPushClient() + # Attach the RLN proof. In production the client mounts RLN and generates the + # proof in lightpushPublish; here we generate it using the server's RLN instance + # since both ends share group state via the in-memory manager. + let msgWithProof = + (await checkAndGenerateRLNProof(some(server.rln), message)).get() + # When the client publishes a message let publishResponse = await lightpushClient.lightpushPublish( - some(pubsubTopic), message, some(serverRemotePeerInfo) + some(pubsubTopic), msgWithProof, some(serverRemotePeerInfo) ) if not publishResponse.isOk(): @@ -178,11 +182,11 @@ suite "Waku Lightpush message delivery": ## Setup let lightNodeKey = generateSecp256k1Key() - lightNode = newTestWakuNode(lightNodeKey, parseIpAddress("0.0.0.0"), Port(0)) + lightNode = newTestWakuNode(lightNodeKey) bridgeNodeKey = generateSecp256k1Key() - bridgeNode = newTestWakuNode(bridgeNodeKey, parseIpAddress("0.0.0.0"), Port(0)) + bridgeNode = newTestWakuNode(bridgeNodeKey) destNodeKey = generateSecp256k1Key() - destNode = newTestWakuNode(destNodeKey, parseIpAddress("0.0.0.0"), Port(0)) + destNode = newTestWakuNode(destNodeKey) await allFutures(destNode.start(), bridgeNode.start(), lightNode.start()) @@ -234,7 +238,7 @@ suite "Waku Lightpush mounting behavior": ## Given a node without Relay mounted let key = generateSecp256k1Key() - node = newTestWakuNode(key, parseIpAddress("0.0.0.0"), Port(0)) + node = newTestWakuNode(key) # Do not mount Relay on purpose check node.wakuRelay.isNil() diff --git a/tests/node/test_wakunode_peer_exchange.nim b/tests/node/test_wakunode_peer_exchange.nim index 47bd5b8af..e70abadc9 100644 --- a/tests/node/test_wakunode_peer_exchange.nim +++ b/tests/node/test_wakunode_peer_exchange.nim @@ -187,74 +187,28 @@ suite "Waku Peer Exchange with discv5": lightpush = false, filter = false, store = false, relay = true ) bindIp = parseIpAddress("0.0.0.0") - extIp = parseIpAddress("127.0.0.1") nodeKey1 = generateSecp256k1Key() - nodeTcpPort1 = Port(64010) - nodeUdpPort1 = Port(9000) - node1 = newTestWakuNode( - nodeKey1, - bindIp, - nodeTcpPort1, - some(extIp), - wakuFlags = some(flags), - discv5UdpPort = some(nodeUdpPort1), - ) + node1 = newTestWakuNode(nodeKey1, bindIp, Port(0), wakuFlags = some(flags)) nodeKey2 = generateSecp256k1Key() - nodeTcpPort2 = Port(64012) - nodeUdpPort2 = Port(9002) - node2 = newTestWakuNode( - nodeKey2, - bindIp, - nodeTcpPort2, - some(extIp), - wakuFlags = some(flags), - discv5UdpPort = some(nodeUdpPort2), - ) + node2 = newTestWakuNode(nodeKey2, bindIp, Port(0), wakuFlags = some(flags)) nodeKey3 = generateSecp256k1Key() - nodeTcpPort3 = Port(64014) - nodeUdpPort3 = Port(9004) - node3 = newTestWakuNode( - nodeKey3, - bindIp, - nodeTcpPort3, - some(extIp), - wakuFlags = some(flags), - discv5UdpPort = some(nodeUdpPort3), - ) - - # discv5 - let conf1 = WakuDiscoveryV5Config( - discv5Config: none(DiscoveryConfig), - address: bindIp, - port: nodeUdpPort1, - privateKey: keys.PrivateKey(nodeKey1.skkey), - bootstrapRecords: @[], - autoupdateRecord: true, - ) - - let disc1 = - WakuDiscoveryV5.new(node1.rng, conf1, some(node1.enr), some(node1.peerManager)) - - let conf2 = WakuDiscoveryV5Config( - discv5Config: none(DiscoveryConfig), - address: bindIp, - port: nodeUdpPort2, - privateKey: keys.PrivateKey(nodeKey2.skkey), - bootstrapRecords: @[disc1.protocol.getRecord()], - autoupdateRecord: true, - ) - - let disc2 = - WakuDiscoveryV5.new(node2.rng, conf2, some(node2.enr), some(node2.peerManager)) + node3 = newTestWakuNode(nodeKey3, bindIp, Port(0), wakuFlags = some(flags)) await allFutures(node1.start(), node2.start(), node3.start()) - let resultDisc1StartRes = await disc1.start() - assert resultDisc1StartRes.isOk(), resultDisc1StartRes.error - let resultDisc2StartRes = await disc2.start() - assert resultDisc2StartRes.isOk(), resultDisc2StartRes.error + + let disc1 = ( + await startDiscv5WithAutoPort(node1, keys.PrivateKey(nodeKey1.skkey), bindIp) + ).valueOr: + raiseAssert "disc1: " & error + let disc2 = ( + await startDiscv5WithAutoPort( + node2, keys.PrivateKey(nodeKey2.skkey), bindIp, @[disc1.protocol.getRecord()] + ) + ).valueOr: + raiseAssert "disc2: " & error ## When var attempts = 10 diff --git a/tests/node/test_wakunode_peer_manager.nim b/tests/node/test_wakunode_peer_manager.nim index a43f76d97..0f85d61fe 100644 --- a/tests/node/test_wakunode_peer_manager.nim +++ b/tests/node/test_wakunode_peer_manager.nim @@ -63,9 +63,9 @@ suite "Peer Manager": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, listenIp, Port(3000)) + server = newTestWakuNode(serverKey, listenIp, Port(0)) serverPeerStore = server.peerManager.switch.peerStore - client = newTestWakuNode(clientKey, listenIp, Port(3001)) + client = newTestWakuNode(clientKey, listenIp, Port(0)) clientPeerStore = client.peerManager.switch.peerStore await allFutures(server.start(), client.start()) diff --git a/tests/node/test_wakunode_relay_rln.nim b/tests/node/test_wakunode_relay_rln.nim index c53b3d571..76074bb6d 100644 --- a/tests/node/test_wakunode_relay_rln.nim +++ b/tests/node/test_wakunode_relay_rln.nim @@ -17,9 +17,9 @@ import waku_core, waku_node, common/error_handling, - waku_rln_relay, - waku_rln_relay/rln, - waku_rln_relay/protocol_types, + rln, + rln/bindings, + rln/protocol_types, waku_keystore/keystore, ], ../waku_store/store_utils, @@ -85,15 +85,15 @@ proc getWakuRlnConfigOnChain*( ethClientAddress: ethClientAddress.get(EthClient), epochSizeSec: 1, onFatalErrorAction: fatalErrorHandler.get(fatalErrorVoidHandler), - # If these are used, initialisation fails with "failed to mount WakuRlnRelay: could not initialize the group manager: the commitment does not have a membership" - creds: some(RlnRelayCreds(path: keystorePath, password: password)), + # If these are used, initialisation fails with "failed to mount Rln: could not initialize the group manager: the commitment does not have a membership" + creds: some(RlnCreds(path: keystorePath, password: password)), ) proc setupRelayWithOnChainRln*( node: WakuNode, shards: seq[RelayShard], wakuRlnConfig: WakuRlnConfig ) {.async.} = await node.mountRelay(shards) - await node.mountRlnRelay(wakuRlnConfig) + await node.setRlnValidator(wakuRlnConfig) suite "Waku RlnRelay - End to End - Static": var @@ -116,8 +116,8 @@ suite "Waku RlnRelay - End to End - Static": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) await allFutures(server.start(), client.start()) @@ -128,32 +128,15 @@ suite "Waku RlnRelay - End to End - Static": await allFutures(client.stop(), server.stop()) suite "Mount": - asyncTest "Can't mount if relay is not mounted": - # Given Relay and RLN are not mounted - check: - server.wakuRelay == nil - server.wakuRlnRelay == nil - - # When RlnRelay is mounted - let catchRes = catch: - await server.setupStaticRln(1) - - # Then Relay and RLN are not mounted,and the process fails - check: - server.wakuRelay == nil - server.wakuRlnRelay == nil - catchRes.error()[].msg == - "WakuRelay protocol is not mounted, cannot mount WakuRlnRelay" - asyncTest "Pubsub topics subscribed before mounting RlnRelay are added to it": # Given the node enables Relay and Rln while subscribing to a pubsub topic await server.setupRelayWithStaticRln(1.uint, @[pubsubTopic]) await client.setupRelayWithStaticRln(2.uint, @[pubsubTopic]) check: server.wakuRelay != nil - server.wakuRlnRelay != nil + server.rln != nil client.wakuRelay != nil - client.wakuRlnRelay != nil + client.rln != nil # And the nodes are connected await client.connectToNodes(@[serverRemotePeerInfo]) @@ -215,7 +198,7 @@ suite "Waku RlnRelay - End to End - Static": asyncTest "rln-relay-max-message-limit testing": let nodekey = generateSecp256k1Key() - node = newTestWakuNode(nodekey, parseIpAddress("0.0.0.0"), Port(0)) + node = newTestWakuNode(nodekey) await node.mountRelay(@[DefaultRelayShard]) @@ -233,10 +216,10 @@ suite "Waku RlnRelay - End to End - Static": ) try: - await node.mountRlnRelay(wakuRlnConfig) + await node.setRlnValidator(wakuRlnConfig) except CatchableError as e: check e.msg == - "failed to mount WakuRlnRelay: rln-relay-user-message-limit can't exceed the MAX_MESSAGE_LIMIT in the rln contract" + "failed to mount Rln: rln-relay-user-message-limit can't exceed the MAX_MESSAGE_LIMIT in the rln contract" suite "Analysis of Bandwith Limitations": asyncTest "Valid Payload Sizes": @@ -278,30 +261,27 @@ suite "Waku RlnRelay - End to End - Static": WakuMessage(payload: @payload150kibPlus, contentTopic: contentTopic) message1b.proof = ( - await client.wakuRlnRelay.generateRLNProof( - message1b.toRLNSignal(), - epoch + float64(client.wakuRlnRelay.rlnEpochSizeSec * 0), + await client.rln.generateRLNProof( + message1b.toRLNSignal(), epoch + float64(client.rln.rlnEpochSizeSec * 0) ) ).valueOr: raiseAssert "generateRLNProof failed: " & error message1kib.proof = ( - await client.wakuRlnRelay.generateRLNProof( - message1kib.toRLNSignal(), - epoch + float64(client.wakuRlnRelay.rlnEpochSizeSec * 1), + await client.rln.generateRLNProof( + message1kib.toRLNSignal(), epoch + float64(client.rln.rlnEpochSizeSec * 1) ) ).valueOr: raiseAssert "generateRLNProof failed: " & error message150kib.proof = ( - await client.wakuRlnRelay.generateRLNProof( - message150kib.toRLNSignal(), - epoch + float64(client.wakuRlnRelay.rlnEpochSizeSec * 2), + await client.rln.generateRLNProof( + message150kib.toRLNSignal(), epoch + float64(client.rln.rlnEpochSizeSec * 2) ) ).valueOr: raiseAssert "generateRLNProof failed: " & error message151kibPlus.proof = ( - await client.wakuRlnRelay.generateRLNProof( + await client.rln.generateRLNProof( message151kibPlus.toRLNSignal(), - epoch + float64(client.wakuRlnRelay.rlnEpochSizeSec * 3), + epoch + float64(client.rln.rlnEpochSizeSec * 3), ) ).valueOr: raiseAssert "generateRLNProof failed: " & error @@ -367,9 +347,9 @@ suite "Waku RlnRelay - End to End - Static": WakuMessage(payload: @payload150kibPlus, contentTopic: contentTopic) message151kibPlus.proof = ( - await client.wakuRlnRelay.generateRLNProof( + await client.rln.generateRLNProof( message151kibPlus.toRLNSignal(), - epoch + float64(client.wakuRlnRelay.rlnEpochSizeSec * 3), + epoch + float64(client.rln.rlnEpochSizeSec * 3), ) ).valueOr: raiseAssert "generateRLNProof failed: " & error @@ -404,8 +384,8 @@ suite "Waku RlnRelay - End to End - OnChain": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) await allFutures(server.start(), client.start()) @@ -576,14 +556,14 @@ suite "Waku RlnRelay - End to End - OnChain": await client.setupRelayWithOnChainRln(@[pubsubTopic], wakuRlnConfig2) try: - (await server.wakuRlnRelay.groupManager.startGroupSync()).isOkOr: + (await server.rln.groupManager.startGroupSync()).isOkOr: raiseAssert $error - (await client.wakuRlnRelay.groupManager.startGroupSync()).isOkOr: + (await client.rln.groupManager.startGroupSync()).isOkOr: raiseAssert $error # Test Hack: Monkeypatch the idCredentials into the groupManager - server.wakuRlnRelay.groupManager.idCredentials = some(idCredential1) - client.wakuRlnRelay.groupManager.idCredentials = some(idCredential2) + server.rln.groupManager.idCredentials = some(idCredential1) + client.rln.groupManager.idCredentials = some(idCredential2) except Exception, CatchableError: assert false, "exception raised: " & getCurrentExceptionMsg() @@ -657,14 +637,14 @@ suite "Waku RlnRelay - End to End - OnChain": await client.setupRelayWithOnChainRln(@[pubsubTopic], wakuRlnConfig2) try: - (await server.wakuRlnRelay.groupManager.startGroupSync()).isOkOr: + (await server.rln.groupManager.startGroupSync()).isOkOr: raiseAssert $error - (await client.wakuRlnRelay.groupManager.startGroupSync()).isOkOr: + (await client.rln.groupManager.startGroupSync()).isOkOr: raiseAssert $error # Test Hack: Monkeypatch the idCredentials into the groupManager - echo server.wakuRlnRelay.groupManager.idCredentials - echo client.wakuRlnRelay.groupManager.idCredentials + echo server.rln.groupManager.idCredentials + echo client.rln.groupManager.idCredentials except Exception, CatchableError: assert false, "exception raised: " & getCurrentExceptionMsg() diff --git a/tests/node/test_wakunode_restart.nim b/tests/node/test_wakunode_restart.nim new file mode 100644 index 000000000..8d05ce131 --- /dev/null +++ b/tests/node/test_wakunode_restart.nim @@ -0,0 +1,40 @@ +{.used.} + +import std/options +import testutils/unittests, chronos, chronicles +import libp2p/switch + +import logos_delivery/waku/[waku_node, waku_core, node/peer_manager] +import ../testlib/[wakucore, wakunode, testasync] + +suite "WakuNode - restart (#3979)": + asyncTest "start -> stop -> start re-opens the listener promptly": + ## A restart must not block on the relay-reconnect backoff. + let + node1 = newTestWakuNode(generateSecp256k1Key()) + node2 = newTestWakuNode(generateSecp256k1Key()) + + (await node1.mountRelay()).isOkOr: + raiseAssert "mountRelay node1: " & error + (await node2.mountRelay()).isOkOr: + raiseAssert "mountRelay node2: " & error + + await allFutures(node1.start(), node2.start()) + + # node1 learns node2 as a relay peer, so a restart triggers reconnectRelayPeers. + await node1.connectToNodes(@[node2.peerInfo.toRemotePeerInfo()]) + + await node1.stop() + + # The restart must complete promptly and yield a usable, listening node. + let startFut = node1.start() + let restarted = await startFut.withTimeout(20.seconds) + if not restarted: + await startFut.cancelAndWait() + + check: + restarted + node1.started + node1.switch.peerInfo.listenAddrs.len > 0 + + await allFutures(node1.stop(), node2.stop()) diff --git a/tests/node/test_wakunode_store.nim b/tests/node/test_wakunode_store.nim index 3d62a17c7..b0dc3fc28 100644 --- a/tests/node/test_wakunode_store.nim +++ b/tests/node/test_wakunode_store.nim @@ -69,8 +69,8 @@ suite "Waku Store - End to End - Sorted Archive": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) archiveDriver = newArchiveDriverWithMessages(pubsubTopic, messages) let mountArchiveResult = server.mountArchive(archiveDriver) @@ -473,8 +473,7 @@ suite "Waku Store - End to End - Sorted Archive": pubsubTopic, archiveMessages.mapIt(it.message.get()) ) otherServerKey = generateSecp256k1Key() - otherServer = - newTestWakuNode(otherServerKey, parseIpAddress("0.0.0.0"), Port(0)) + otherServer = newTestWakuNode(otherServerKey) mountOtherArchiveResult = otherServer.mountArchive(otherArchiveDriverWithMessages) assert mountOtherArchiveResult.isOk() @@ -564,8 +563,8 @@ suite "Waku Store - End to End - Unsorted Archive": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) let unsortedArchiveDriverWithMessages = @@ -781,8 +780,8 @@ suite "Waku Store - End to End - Unsorted Archive without provided Timestamp": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) let unsortedArchiveDriverWithMessages = @@ -930,8 +929,8 @@ suite "Waku Store - End to End - Archive with Multiple Topics": serverKey = generateSecp256k1Key() clientKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) + client = newTestWakuNode(clientKey) let archiveDriver = newSqliteArchiveDriver().put(pubsubTopic, messages[0 ..< 6]).put( pubsubTopicB, messages[6 ..< 10] @@ -1179,8 +1178,7 @@ suite "Waku Store - End to End - Archive with Multiple Topics": # And a server node with the ephemeral archive let ephemeralServerKey = generateSecp256k1Key() - ephemeralServer = - newTestWakuNode(ephemeralServerKey, parseIpAddress("0.0.0.0"), Port(0)) + ephemeralServer = newTestWakuNode(ephemeralServerKey) mountEphemeralArchiveResult = ephemeralServer.mountArchive(ephemeralArchiveDriver) assert mountEphemeralArchiveResult.isOk() @@ -1219,8 +1217,7 @@ suite "Waku Store - End to End - Archive with Multiple Topics": # And a server node with the mixed archive let mixedServerKey = generateSecp256k1Key() - mixedServer = - newTestWakuNode(mixedServerKey, parseIpAddress("0.0.0.0"), Port(0)) + mixedServer = newTestWakuNode(mixedServerKey) mountMixedArchiveResult = mixedServer.mountArchive(mixedArchiveDriver) assert mountMixedArchiveResult.isOk() @@ -1246,8 +1243,7 @@ suite "Waku Store - End to End - Archive with Multiple Topics": # And a server node with the empty archive let emptyServerKey = generateSecp256k1Key() - emptyServer = - newTestWakuNode(emptyServerKey, parseIpAddress("0.0.0.0"), Port(0)) + emptyServer = newTestWakuNode(emptyServerKey) mountEmptyArchiveResult = emptyServer.mountArchive(emptyArchiveDriver) assert mountEmptyArchiveResult.isOk() @@ -1286,8 +1282,7 @@ suite "Waku Store - End to End - Archive with Multiple Topics": # And a server node with the voluminous archive let voluminousServerKey = generateSecp256k1Key() - voluminousServer = - newTestWakuNode(voluminousServerKey, parseIpAddress("0.0.0.0"), Port(0)) + voluminousServer = newTestWakuNode(voluminousServerKey) mountVoluminousArchiveResult = voluminousServer.mountArchive(voluminousArchiveDriverWithMessages) assert mountVoluminousArchiveResult.isOk() diff --git a/tests/persistency/test_lifecycle.nim b/tests/persistency/test_lifecycle.nim index 2626cb01b..3006672da 100644 --- a/tests/persistency/test_lifecycle.nim +++ b/tests/persistency/test_lifecycle.nim @@ -156,7 +156,7 @@ suite "Persistency lifecycle": let ev = PersistEvent( ops: @[TxOp(category: "msg", key: k, kind: txPut, payload: payloadBytes("hello"))] ) - await PersistEvent.emit(t.context, ev) + PersistEvent.emit(t.context, ev) let ckOk2 = await t.pollExists("msg", k) check ckOk2 @@ -178,7 +178,7 @@ suite "Persistency lifecycle": check a.context != b.context let k = key("shared", 1'i64) - await PersistEvent.emit( + PersistEvent.emit( a.context, PersistEvent( ops: @[ @@ -188,7 +188,7 @@ suite "Persistency lifecycle": ] ), ) - await PersistEvent.emit( + PersistEvent.emit( b.context, PersistEvent( ops: @[ @@ -255,7 +255,7 @@ suite "Persistency lifecycle": ops.add( TxOp(category: "msg", key: key("c", i), kind: txPut, payload: payloadBytes($i)) ) - await PersistEvent.emit(t.context, PersistEvent(ops: ops)) + PersistEvent.emit(t.context, PersistEvent(ops: ops)) # Wait for the last insert to land. let ckOk5 = await t.pollExists("msg", key("c", 5'i64)) check ckOk5 @@ -285,7 +285,7 @@ suite "Persistency lifecycle": let r1 = aw7.get() check r1.existed == false - await PersistEvent.emit( + PersistEvent.emit( t.context, PersistEvent( ops: @[TxOp(category: "msg", key: k, kind: txPut, payload: payloadBytes("v"))] diff --git a/tests/test_peer_manager.nim b/tests/test_peer_manager.nim index d4c2af5b5..b364fc8c3 100644 --- a/tests/test_peer_manager.nim +++ b/tests/test_peer_manager.nim @@ -2,11 +2,13 @@ import std/[sequtils, times, sugar, net, options], + results, testutils/unittests, chronos, json_rpc/rpcserver, json_rpc/rpcclient, eth/keys, + eth/p2p/discoveryv5/enr, eth/common/eth_types, libp2p/[builders, switch, multiaddress], libp2p/protobuf/minprotobuf, @@ -34,12 +36,17 @@ import ./testlib/wakucore, ./testlib/wakunode +proc syncToBoundPort(node: WakuNode, key: keys.PrivateKey) = + let addrs = node.switch.peerInfo.listenAddrs + node.switch.peerInfo.addrs = addrs + node.announcedAddresses = addrs + node.enr.update(key, tcpPort = Opt.some(node.boundTcpPort())).isOkOr: + raiseAssert "failed to sync node ENR to bound tcp port: " & $error + procSuite "Peer Manager": asyncTest "connectPeer() works": # Create 2 nodes - let nodes = toSeq(0 ..< 2).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 2).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) let connOk = @@ -55,9 +62,7 @@ procSuite "Peer Manager": Connectedness.Connected asyncTest "Peer manager tracks active store request state": - let nodes = toSeq(0 ..< 2).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 2).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) await allFutures(nodes.mapIt(it.mountRelay())) @@ -94,9 +99,7 @@ procSuite "Peer Manager": asyncTest "dialPeer() works": # Create 2 nodes - let nodes = toSeq(0 ..< 2).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 2).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) await allFutures(nodes.mapIt(it.mountRelay())) @@ -129,9 +132,7 @@ procSuite "Peer Manager": asyncTest "dialPeer() fails gracefully": # Create 2 nodes and start them - let nodes = toSeq(0 ..< 2).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 2).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) await allFutures(nodes.mapIt(it.mountRelay())) @@ -158,7 +159,7 @@ procSuite "Peer Manager": asyncTest "Adding, selecting and filtering peers work": let - node = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node = newTestWakuNode(generateSecp256k1Key()) # Create filter peer filterLoc = MultiAddress.init("/ip4/127.0.0.1/tcp/0").tryGet() @@ -190,9 +191,7 @@ procSuite "Peer Manager": asyncTest "Peer manager keeps track of connections": # Create 2 nodes - let nodes = toSeq(0 ..< 2).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 2).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) await allFutures(nodes.mapIt(it.mountRelay())) @@ -243,9 +242,7 @@ procSuite "Peer Manager": asyncTest "Peer manager updates failed peers correctly": # Create 2 nodes - let nodes = toSeq(0 ..< 2).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 2).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) await allFutures(nodes.mapIt(it.mountRelay())) @@ -311,13 +308,13 @@ procSuite "Peer Manager": node1 = newTestWakuNode( generateSecp256k1Key(), getPrimaryIPAddr(), - Port(44048), + Port(0), peerStorage = storage, quicEnabled = false, ) - node2 = newTestWakuNode( - generateSecp256k1Key(), getPrimaryIPAddr(), Port(34023), quicEnabled = false - ) + node2Key = generateSecp256k1Key() + node2 = + newTestWakuNode(node2Key, getPrimaryIPAddr(), Port(0), quicEnabled = false) node1.mountMetadata(0, @[0'u16]).expect("Mounted Waku Metadata") node2.mountMetadata(0, @[0'u16]).expect("Mounted Waku Metadata") @@ -330,6 +327,8 @@ procSuite "Peer Manager": (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" + node2.syncToBoundPort(keys.PrivateKey(node2Key.skkey)) + let peerInfo2 = node2.switch.peerInfo var remotePeerInfo2 = peerInfo2.toRemotePeerInfo() remotePeerInfo2.enr = some(node2.enr) @@ -354,7 +353,7 @@ procSuite "Peer Manager": let node3 = newTestWakuNode( generateSecp256k1Key(), parseIpAddress("127.0.0.1"), - Port(56037), + Port(0), peerStorage = storage, quicEnabled = false, ) @@ -392,13 +391,13 @@ procSuite "Peer Manager": node1 = newTestWakuNode( generateSecp256k1Key(), getPrimaryIPAddr(), - Port(44048), + Port(0), peerStorage = storage, quicEnabled = false, ) - node2 = newTestWakuNode( - generateSecp256k1Key(), getPrimaryIPAddr(), Port(34023), quicEnabled = false - ) + node2Key = generateSecp256k1Key() + node2 = + newTestWakuNode(node2Key, getPrimaryIPAddr(), Port(0), quicEnabled = false) node1.mountMetadata(0, @[0'u16]).expect("Mounted Waku Metadata") node2.mountMetadata(0, @[0'u16]).expect("Mounted Waku Metadata") @@ -411,6 +410,8 @@ procSuite "Peer Manager": (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" + node2.syncToBoundPort(keys.PrivateKey(node2Key.skkey)) + let peerInfo2 = node2.switch.peerInfo var remotePeerInfo2 = peerInfo2.toRemotePeerInfo() remotePeerInfo2.enr = some(node2.enr) @@ -435,7 +436,7 @@ procSuite "Peer Manager": let node3 = newTestWakuNode( generateSecp256k1Key(), parseIpAddress("127.0.0.1"), - Port(56037), + Port(0), peerStorage = storage, quicEnabled = false, ) @@ -525,14 +526,8 @@ procSuite "Peer Manager": let database = SqliteDatabase.new(":memory:")[] storage = WakuPeerStorage.new(database)[] - node1 = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - peerStorage = storage, - ) - node2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(generateSecp256k1Key(), peerStorage = storage) + node2 = newTestWakuNode(generateSecp256k1Key()) peerInfo2 = node2.switch.peerInfo betaCodec = "/vac/waku/relay/2.0.0-beta2" stableCodec = "/vac/waku/relay/2.0.0" @@ -559,9 +554,7 @@ procSuite "Peer Manager": node1.peerManager.switch.peerStore.connectedness(peerInfo2.peerId) == Connected # Simulate restart by initialising a new node using the same storage - let node3 = newTestWakuNode( - generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0), peerStorage = storage - ) + let node3 = newTestWakuNode(generateSecp256k1Key(), peerStorage = storage) (await node3.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -595,8 +588,6 @@ procSuite "Peer Manager": let nodes = toSeq(0 ..< 4).mapIt( newTestWakuNode( nodeKey = generateSecp256k1Key(), - bindIp = parseIpAddress("0.0.0.0"), - bindPort = Port(0), wakuFlags = some(CapabilitiesBitfield.init(@[Relay])), ) ) @@ -668,8 +659,6 @@ procSuite "Peer Manager": let nodes = toSeq(0 ..< 4).mapIt( newTestWakuNode( nodeKey = generateSecp256k1Key(), - bindIp = parseIpAddress("0.0.0.0"), - bindPort = Port(0), wakuFlags = some(CapabilitiesBitfield.init(@[Relay])), ) ) @@ -747,9 +736,7 @@ procSuite "Peer Manager": asyncTest "Peer store keeps track of incoming connections": # Create 4 nodes - let nodes = toSeq(0 ..< 4).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 4).mapIt(newTestWakuNode(generateSecp256k1Key())) # Start them await allFutures(nodes.mapIt(it.start())) @@ -842,7 +829,7 @@ procSuite "Peer Manager": let basePeerId = "16Uiu2HAm7QGEZKujdSbbo1aaQyfDPQ6Bw3ybQnj6fruH5Dxwd7D" let - node = newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node = newTestWakuNode(generateSecp256k1Key()) peers = toSeq(1 .. 4) .mapIt(parsePeerInfo("/ip4/0.0.0.0/tcp/30300/p2p/" & basePeerId & $it)) .filterIt(it.isOk()) @@ -880,9 +867,7 @@ procSuite "Peer Manager": asyncTest "connectedPeers() returns expected number of connections per protocol": # Create 4 nodes - let nodes = toSeq(0 ..< 4).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 4).mapIt(newTestWakuNode(generateSecp256k1Key())) # Start them with relay + filter await allFutures(nodes.mapIt(it.start())) @@ -935,9 +920,7 @@ procSuite "Peer Manager": asyncTest "getNumStreams() returns expected number of connections per protocol": # Create 2 nodes - let nodes = toSeq(0 ..< 2).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 2).mapIt(newTestWakuNode(generateSecp256k1Key())) # Start them with relay + filter await allFutures(nodes.mapIt(it.start())) @@ -1210,9 +1193,7 @@ procSuite "Peer Manager": asyncTest "colocationLimit is enforced by pruneConnsByIp()": # Create 5 nodes - let nodes = toSeq(0 ..< 5).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 5).mapIt(newTestWakuNode(generateSecp256k1Key())) # Start them with relay + filter await allFutures(nodes.mapIt(it.start())) @@ -1275,25 +1256,13 @@ procSuite "Peer Manager": # Create 3 nodes with different shards let nodes = @[ newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - subscribeShards = @[shardId0], + generateSecp256k1Key(), clusterId = clusterId, subscribeShards = @[shardId0] ), newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - subscribeShards = @[shardId1], + generateSecp256k1Key(), clusterId = clusterId, subscribeShards = @[shardId1] ), newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - subscribeShards = @[shardId0], + generateSecp256k1Key(), clusterId = clusterId, subscribeShards = @[shardId0] ), ] @@ -1399,18 +1368,12 @@ procSuite "Peer Manager": asyncTest "selectPeer() handles invalid pubsub topic gracefully": ## Given: A peer manager with valid peers - let node = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = 0, - subscribeShards = @[0'u16], - ) + let node = + newTestWakuNode(generateSecp256k1Key(), clusterId = 0, subscribeShards = @[0'u16]) await node.start() # Add a peer - let peer = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + let peer = newTestWakuNode(generateSecp256k1Key()) await peer.start() discard await peer.mountRelay() @@ -1443,22 +1406,14 @@ procSuite "Peer Manager": shardId1 = 1.uint16 let node = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - subscribeShards = @[shardId0], + generateSecp256k1Key(), clusterId = clusterId, subscribeShards = @[shardId0] ) await node.start() discard await node.mountRelay() # Create peer with ENR containing shard 0 let peer = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - subscribeShards = @[shardId0], + generateSecp256k1Key(), clusterId = clusterId, subscribeShards = @[shardId0] ) await peer.start() discard await peer.mountRelay() diff --git a/tests/test_waku.nim b/tests/test_waku.nim index ff009c3ba..7ddde8ad9 100644 --- a/tests/test_waku.nim +++ b/tests/test_waku.nim @@ -9,32 +9,30 @@ import tools/confutils/cli_args import logos_delivery/waku/factory/networks_config import logos_delivery/waku/factory/conf_builder/conf_builder -suite "Waku API - Create node": +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().toWakuNodeConf(Core).valueOr: + raiseAssert "toWakuNodeConf failed: " & error nodeConf.clusterId = some(3'u16) nodeConf.rest = false # This is the actual minimal config but as the node auto-start, it is not suitable for tests ## When - let node = (await createNode(nodeConf)).valueOr: - raiseAssert "createNode (minimal config) failed: " & error + let ld = (await LogosDelivery.new(nodeConf)).valueOr: + raiseAssert "LogosDelivery.new (minimal config) failed: " & error ## Then check: - not node.isNil() - node.conf.clusterId == 3 - node.conf.relay == true + not ld.isNil() + ld.waku.conf.clusterId == 3 + ld.waku.conf.relay == true asyncTest "Create node with full configuration": ## Given - var nodeConf = defaultWakuNodeConf().valueOr: - raiseAssert "defaultWakuNodeConf failed: " & error - nodeConf.mode = Core + var nodeConf = MessagingClientConf().toWakuNodeConf(Core).valueOr: + raiseAssert "toWakuNodeConf failed: " & error nodeConf.clusterId = some(99'u16) nodeConf.rest = false nodeConf.numShardsInNetwork = 16 @@ -47,26 +45,25 @@ suite "Waku API - Create node": ] ## When - let node = (await createNode(nodeConf)).valueOr: - raiseAssert "createNode (full config) failed: " & error + let ld = (await LogosDelivery.new(nodeConf)).valueOr: + raiseAssert "LogosDelivery.new (full config) failed: " & 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 + not ld.isNil() + ld.waku.conf.clusterId == 99 + ld.waku.conf.shardingConf.numShardsInCluster == 16 + ld.waku.conf.maxMessageSizeBytes == 1024'u64 * 1024'u64 + ld.waku.conf.staticNodes.len == 1 + ld.waku.conf.relay == true + ld.waku.conf.lightPush == true + ld.waku.conf.peerExchangeService == true + ld.waku.conf.rendezvous == true asyncTest "Create node with mixed entry nodes (enrtree, multiaddr)": ## Given - var nodeConf = defaultWakuNodeConf().valueOr: - raiseAssert "defaultWakuNodeConf failed: " & error - nodeConf.mode = Core + var nodeConf = MessagingClientConf().toWakuNodeConf(Core).valueOr: + raiseAssert "toWakuNodeConf failed: " & error nodeConf.clusterId = some(42'u16) nodeConf.rest = false nodeConf.entryNodes = @[ @@ -75,18 +72,18 @@ suite "Waku API - Create node": ] ## When - let node = (await createNode(nodeConf)).valueOr: - raiseAssert "createNode (mixed entry nodes) failed: " & error + let ld = (await LogosDelivery.new(nodeConf)).valueOr: + raiseAssert "LogosDelivery.new (mixed entry nodes) failed: " & error ## Then check: - not node.isNil() - node.conf.clusterId == 42 + not ld.isNil() + ld.waku.conf.clusterId == 42 # ENRTree should go to DNS discovery - node.conf.dnsDiscoveryConf.isSome() - node.conf.dnsDiscoveryConf.get().enrTreeUrl == + ld.waku.conf.dnsDiscoveryConf.isSome() + ld.waku.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] == + ld.waku.conf.staticNodes.len == 1 + ld.waku.conf.staticNodes[0] == "/ip4/127.0.0.1/tcp/60000/p2p/16Uuu2HBmAcHvhLqQKwSSbX6BG5JLWUDRcaLVrehUVqpw7fz1hbYc" diff --git a/tests/test_waku_dnsdisc.nim b/tests/test_waku_dnsdisc.nim index d0430adb0..13b5ef6c8 100644 --- a/tests/test_waku_dnsdisc.nim +++ b/tests/test_waku_dnsdisc.nim @@ -17,25 +17,22 @@ import logos_delivery/waku/discovery/waku_dnsdisc, ./testlib/common, ./testlib/wakucore, - ./testlib/wakunode + ./testlib/wakunode, + ./waku_enr/utils suite "Waku DNS Discovery": asyncTest "Waku DNS Discovery end-to-end": ## Tests integrated DNS discovery, from building ## the tree to connecting to discovered nodes - # Create nodes and ENR. These will be added to the discoverable list let - bindIp = parseIpAddress("0.0.0.0") + bindIp = parseIpAddress("127.0.0.1") nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, bindIp, Port(63500)) - enr1 = node1.enr + node1 = newTestWakuNode(nodeKey1, bindIp, Port(0)) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, bindIp, Port(63502)) - enr2 = node2.enr + node2 = newTestWakuNode(nodeKey2, bindIp, Port(0)) nodeKey3 = generateSecp256k1Key() - node3 = newTestWakuNode(nodeKey3, bindIp, Port(63503)) - enr3 = node3.enr + node3 = newTestWakuNode(nodeKey3, bindIp, Port(0)) (await node1.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -45,6 +42,11 @@ suite "Waku DNS Discovery": assert false, "Failed to mount relay" await allFutures([node1.start(), node2.start(), node3.start()]) + let + enr1 = newTestEnrRecord(nodeKey1, $bindIp, uint16(node1.boundTcpPort()), 0) + enr2 = newTestEnrRecord(nodeKey2, $bindIp, uint16(node2.boundTcpPort()), 0) + enr3 = newTestEnrRecord(nodeKey3, $bindIp, uint16(node3.boundTcpPort()), 0) + # Build and sign tree var tree = buildTree( 1, # Seq no @@ -76,7 +78,7 @@ suite "Waku DNS Discovery": let nodeKey4 = generateSecp256k1Key() - node4 = newTestWakuNode(nodeKey4, bindIp, Port(63504)) + node4 = newTestWakuNode(nodeKey4, bindIp, Port(0)) (await node4.mountRelay()).isOkOr: assert false, "Failed to mount relay" diff --git a/tests/test_waku_keepalive.nim b/tests/test_waku_keepalive.nim index d8412f2c5..f628de069 100644 --- a/tests/test_waku_keepalive.nim +++ b/tests/test_waku_keepalive.nim @@ -20,9 +20,9 @@ suite "Waku Keepalive": asyncTest "handle ping keepalives": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) var completionFut = newFuture[bool]() diff --git a/tests/test_waku_metadata.nim b/tests/test_waku_metadata.nim index 0252d3f77..1ae4ce7b7 100644 --- a/tests/test_waku_metadata.nim +++ b/tests/test_waku_metadata.nim @@ -29,18 +29,8 @@ procSuite "Waku Metadata Protocol": asyncTest "request() returns the supported metadata of the peer": let clusterId = 10.uint16 let - node1 = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - ) - node2 = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - ) + node1 = newTestWakuNode(generateSecp256k1Key(), clusterId = clusterId) + node2 = newTestWakuNode(generateSecp256k1Key(), clusterId = clusterId) # Mount metadata protocol on both nodes before starting discard node1.mountMetadata(clusterId, @[]) @@ -89,15 +79,9 @@ procSuite "Waku Metadata Protocol": configuredShards = @[uint16(0), uint16(1)] let node1 = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - subscribeShards = configuredShards, - ) - let node2 = newTestWakuNode( - generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0), clusterId = clusterId + generateSecp256k1Key(), clusterId = clusterId, subscribeShards = configuredShards ) + let node2 = newTestWakuNode(generateSecp256k1Key(), clusterId = clusterId) # Mount metadata with configured shards on node1 discard node1.mountMetadata(clusterId, configuredShards) diff --git a/tests/test_waku_rendezvous.nim b/tests/test_waku_rendezvous.nim index 0ebe648fa..4bdb90630 100644 --- a/tests/test_waku_rendezvous.nim +++ b/tests/test_waku_rendezvous.nim @@ -22,24 +22,9 @@ procSuite "Waku Rendezvous": asyncTest "Simple remote test": let clusterId = 10.uint16 - node1 = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - ) - node2 = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - ) - node3 = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - ) + node1 = newTestWakuNode(generateSecp256k1Key(), clusterId = clusterId) + node2 = newTestWakuNode(generateSecp256k1Key(), clusterId = clusterId) + node3 = newTestWakuNode(generateSecp256k1Key(), clusterId = clusterId) await allFutures( [ @@ -90,11 +75,7 @@ procSuite "Waku Rendezvous": configuredShards = @[RelayShard(clusterId: clusterId, shardId: 0)] let node = newTestWakuNode( - generateSecp256k1Key(), - parseIpAddress("0.0.0.0"), - Port(0), - clusterId = clusterId, - subscribeShards = @[0'u16], + generateSecp256k1Key(), clusterId = clusterId, subscribeShards = @[0'u16] ) ## When: Node mounts rendezvous with configured shards (before relay) @@ -122,9 +103,7 @@ procSuite "Waku Rendezvous": RelayShard(clusterId: clusterId, shardId: 1), ] - let lightClient = newTestWakuNode( - generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0), clusterId = clusterId - ) + let lightClient = newTestWakuNode(generateSecp256k1Key(), clusterId = clusterId) ## When: Node mounts rendezvous with configured shards (no relay mounted) await lightClient.mountRendezvous(clusterId, configuredShards) diff --git a/tests/test_wakunode.nim b/tests/test_wakunode.nim index 902ca7192..4279d9066 100644 --- a/tests/test_wakunode.nim +++ b/tests/test_wakunode.nim @@ -27,9 +27,9 @@ suite "WakuNode": asyncTest "Protocol matcher works as expected": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(61000)) + node1 = newTestWakuNode(nodeKey1) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(61002)) + node2 = newTestWakuNode(nodeKey2) shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() @@ -94,16 +94,9 @@ suite "WakuNode": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode( - nodeKey1, parseIpAddress("0.0.0.0"), Port(61020), nameResolver = resolver - ) + node1 = newTestWakuNode(nodeKey1, nameResolver = resolver) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(61022)) - - # Construct DNS multiaddr for node2 - let - node2PeerId = $(node2.switch.peerInfo.peerId) - node2Dns4Addr = "/dns4/localhost/tcp/61022/p2p/" & node2PeerId + node2 = newTestWakuNode(nodeKey2) (await node1.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -112,6 +105,12 @@ suite "WakuNode": await allFutures([node1.start(), node2.start()]) + # Construct DNS multiaddr for node2 + let + node2PeerId = $(node2.switch.peerInfo.peerId) + node2Dns4Addr = + "/dns4/localhost/tcp/" & $node2.boundTcpPort() & "/p2p/" & node2PeerId + await node1.connectToNodes(@[node2Dns4Addr]) check: @@ -171,8 +170,6 @@ suite "WakuNode": # gibberish discard newTestWakuNode( nodeKey1, - parseIpAddress("0.0.0.0"), - bindPort = Port(61004), wsBindPort = Port(8000), wssEnabled = true, secureKey = "../../waku/node/key_dummy.txt", @@ -221,7 +218,7 @@ suite "WakuNode": let nodeKey = generateSecp256k1Key() bindIp = parseIpAddress("0.0.0.0") - bindPort = Port(61006) + bindPort = Port(0) extIp = some(getPrimaryIPAddr()) extPort = some(Port(61008)) node = @@ -247,8 +244,7 @@ suite "WakuNode": test "toRemotePeerInfo sorts quic-v1 addresses first": let nodeKey = generateSecp256k1Key() - node = - newTestWakuNode(nodeKey, parseIpAddress("0.0.0.0"), Port(0), quicEnabled = true) + node = newTestWakuNode(nodeKey, quicEnabled = true) # peerinfo path let fromPeerInfo = node.switch.peerInfo.toRemotePeerInfo() @@ -266,13 +262,9 @@ suite "WakuNode": asyncTest "Dual-stack nodes connect over QUIC": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode( - nodeKey1, parseIpAddress("0.0.0.0"), Port(0), quicEnabled = true - ) + node1 = newTestWakuNode(nodeKey1, quicEnabled = true) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode( - nodeKey2, parseIpAddress("0.0.0.0"), Port(0), quicEnabled = true - ) + node2 = newTestWakuNode(nodeKey2, quicEnabled = true) await allFutures(node1.start(), node2.start()) @@ -374,16 +366,11 @@ suite "WakuNode": let # node with custom agent string nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode( - nodeKey1, - parseIpAddress("0.0.0.0"), - Port(61014), - agentString = some(expectedAgentString1), - ) + node1 = newTestWakuNode(nodeKey1, agentString = some(expectedAgentString1)) # node with default agent string from libp2p nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(61016)) + node2 = newTestWakuNode(nodeKey2) await node1.start() (await node1.mountRelay()).isOkOr: @@ -417,16 +404,11 @@ suite "WakuNode": let # node with custom multiaddress nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode( - nodeKey1, - parseIpAddress("0.0.0.0"), - Port(61018), - extMultiAddrs = @[expectedMultiaddress1], - ) + node1 = newTestWakuNode(nodeKey1, extMultiAddrs = @[expectedMultiaddress1]) # node with default multiaddress nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(61020)) + node2 = newTestWakuNode(nodeKey2) await node1.start() (await node1.mountRelay()).isOkOr: diff --git a/tests/testlib/wakunode.nim b/tests/testlib/wakunode.nim index 65b165a08..f269f6314 100644 --- a/tests/testlib/wakunode.nim +++ b/tests/testlib/wakunode.nim @@ -51,8 +51,8 @@ proc defaultTestWakuConf*(): WakuConf = proc newTestWakuNode*( nodeKey: crypto.PrivateKey, - bindIp: IpAddress, - bindPort: Port, + bindIp: IpAddress = parseIpAddress("0.0.0.0"), + bindPort: Port = Port(0), extIp = none(IpAddress), extPort = none(Port), extMultiAddrs = newSeq[MultiAddress](), @@ -170,3 +170,10 @@ proc newTestWakuNode*( ) return builder.build().get() + +proc boundTcpPort*(node: WakuNode): Port = + let ports = getPorts(node.switch.peerInfo.listenAddrs).valueOr: + raiseAssert "getPorts failed: " & error + if ports.tcpPort.isNone(): + raiseAssert "no tcp listen address in " & $node.switch.peerInfo.listenAddrs + ports.tcpPort.get() diff --git a/tests/waku_core/test_published_address.nim b/tests/waku_core/test_published_address.nim index 9d6201a77..5b65c7804 100644 --- a/tests/waku_core/test_published_address.nim +++ b/tests/waku_core/test_published_address.nim @@ -5,8 +5,7 @@ import ../testlib/wakucore, ../testlib/wakunode suite "Waku Core - Published Address": test "Test IP 0.0.0.0": - let node = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + let node = newTestWakuNode(generateSecp256k1Key()) check: ($node.announcedAddresses).contains("127.0.0.1") diff --git a/tests/waku_discv5/test_waku_discv5.nim b/tests/waku_discv5/test_waku_discv5.nim index b5d34486f..7f42e3b80 100644 --- a/tests/waku_discv5/test_waku_discv5.nim +++ b/tests/waku_discv5/test_waku_discv5.nim @@ -19,9 +19,10 @@ import waku_core/codecs, waku_enr, discovery/waku_discv5, + net/auto_port, waku_enr/capabilities, factory/conf_builder/conf_builder, - factory/waku, + waku, waku_node, node/peer_manager, ], @@ -179,51 +180,57 @@ suite "Waku Discovery v5": suite "findRandomPeers": proc buildNode( tcpPort: uint16, - udpPort: uint16, bindIp: string = "0.0.0.0", extIp: string = "127.0.0.1", indices: seq[uint64] = @[], recordFlags: Option[CapabilitiesBitfield] = none(CapabilitiesBitfield), bootstrapRecords: seq[waku_enr.Record] = @[], - ): (WakuDiscoveryV5, Record) {.raises: [ValueError, LPError].} = - let - privKey = generateSecp256k1Key() - record = newTestEnrRecord( - privKey = privKey, - extIp = extIp, - tcpPort = tcpPort, - udpPort = udpPort, - indices = indices, - flags = recordFlags, - ) - node = discv5_utils.newTestDiscv5( - privKey = privKey, - bindIp = bindIp, - tcpPort = tcpPort, - udpPort = udpPort, - record = record, - bootstrapRecords = bootstrapRecords, - ) + ): Future[Result[(WakuDiscoveryV5, waku_enr.Record), string]] {.async.} = + let privKey = generateSecp256k1Key() + proc attempt( + p: Port + ): Future[Result[(WakuDiscoveryV5, waku_enr.Record), string]] {. + async: (raises: []) + .} = + let (node, record) = + try: + let record = newTestEnrRecord( + privKey = privKey, + extIp = extIp, + tcpPort = tcpPort, + udpPort = uint16(p), + indices = indices, + flags = recordFlags, + ) + ( + discv5_utils.newTestDiscv5( + privKey = privKey, + bindIp = bindIp, + tcpPort = tcpPort, + udpPort = uint16(p), + record = record, + bootstrapRecords = bootstrapRecords, + ), + record, + ) + except CatchableError as e: + return err("could not build discv5 node: " & e.msg) + (await node.start()).isOkOr: + return err(error) + ok((node, record)) - (node, record) + return await tryWithAutoPort[(WakuDiscoveryV5, waku_enr.Record)](Port(0), attempt) asyncTest "find random peers without predicate": # Given 3 nodes - let - (node1, record1) = buildNode(tcpPort = 61500u16, udpPort = 9000u16) - (node2, record2) = buildNode(tcpPort = 61502u16, udpPort = 9002u16) - (node3, record3) = buildNode( - tcpPort = 61504u16, udpPort = 9004u16, bootstrapRecords = @[record1, record2] - ) - - let res1 = await node1.start() - assertResultOk res1 - - let res2 = await node2.start() - assertResultOk res2 - - let res3 = await node3.start() - assertResultOk res3 + let (node1, record1) = (await buildNode(tcpPort = 61500u16)).valueOr: + raiseAssert "node1: " & error + let (node2, record2) = (await buildNode(tcpPort = 61502u16)).valueOr: + raiseAssert "node2: " & error + let (node3, record3) = ( + await buildNode(tcpPort = 61504u16, bootstrapRecords = @[record1, record2]) + ).valueOr: + raiseAssert "node3: " & error await sleepAsync(FUTURE_TIMEOUT) @@ -254,45 +261,39 @@ suite "Waku Discovery v5": return capabilities.get().supportsCapability(Capabilities.Store) # Given 4 nodes - let - (node3, record3) = buildNode( + let (node3, record3) = ( + await buildNode( tcpPort = 61504u16, - udpPort = 9004u16, recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Filter)), ) - (node4, record4) = buildNode( + ).valueOr: + raiseAssert "node3: " & error + let (node4, record4) = ( + await buildNode( tcpPort = 61506u16, - udpPort = 9006u16, recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store)), ) - (node2, record2) = buildNode( + ).valueOr: + raiseAssert "node4: " & error + let (node2, record2) = ( + await buildNode( tcpPort = 61502u16, - udpPort = 9002u16, recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store)), bootstrapRecords = @[record3, record4], ) - (node1, record1) = buildNode( + ).valueOr: + raiseAssert "node2: " & error + let (node1, record1) = ( + await buildNode( tcpPort = 61500u16, - udpPort = 9000u16, recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay)), bootstrapRecords = @[record2], ) - - # Start nodes' discoveryV5 protocols - let res1 = await node1.start() - assertResultOk res1 - - let res2 = await node2.start() - assertResultOk res2 - - let res3 = await node3.start() - assertResultOk res3 - - let res4 = await node4.start() - assertResultOk res4 + ).valueOr: + raiseAssert "node1: " & error await sleepAsync(FUTURE_TIMEOUT) @@ -312,46 +313,40 @@ suite "Waku Discovery v5": ## Setup # Records - let - (node3, record3) = buildNode( + let (node3, record3) = ( + await buildNode( tcpPort = 61504u16, - udpPort = 9004u16, recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Filter)), ) - (node4, record4) = buildNode( + ).valueOr: + raiseAssert "node3: " & error + let (node4, record4) = ( + await buildNode( tcpPort = 61506u16, - udpPort = 9006u16, recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store)), ) - (node2, record2) = buildNode( + ).valueOr: + raiseAssert "node4: " & error + let (node2, record2) = ( + await buildNode( tcpPort = 61502u16, - udpPort = 9002u16, recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay, Capabilities.Store)), bootstrapRecords = @[record3, record4], ) - let (node1, record1) = buildNode( - tcpPort = 61500u16, - udpPort = 9000u16, - recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay)), - indices = @[0u64, 0u64, 1u64, 0u64, 0u64], - bootstrapRecords = @[record2], - ) - - # Start nodes' discoveryV5 protocols - let res1 = await node1.start() - assertResultOk res1 - - let res2 = await node2.start() - assertResultOk res2 - - let res3 = await node3.start() - assertResultOk res3 - - let res4 = await node4.start() - assertResultOk res4 + ).valueOr: + raiseAssert "node2: " & error + let (node1, record1) = ( + await buildNode( + tcpPort = 61500u16, + recordFlags = some(CapabilitiesBitfield.init(Capabilities.Relay)), + indices = @[0u64, 0u64, 1u64, 0u64, 0u64], + bootstrapRecords = @[record2], + ) + ).valueOr: + raiseAssert "node1: " & error ## leave some time for discv5 to act await sleepAsync(chronos.seconds(10)) @@ -425,7 +420,7 @@ suite "Waku Discovery v5": confBuilder.withNodeKey(libp2p_keys.PrivateKey.random(Secp256k1, myRng)[]) confBuilder.discv5Conf.withEnabled(true) - confBuilder.discv5Conf.withUdpPort(9000.Port) + confBuilder.discv5Conf.withUdpPort(Port(0)) let conf = confBuilder.build().valueOr: raiseAssert error @@ -437,8 +432,8 @@ suite "Waku Discovery v5": confBuilder.withNodeKey(crypto.PrivateKey.random(Secp256k1, myRng)[]) confBuilder.discv5Conf.withBootstrapNodes(@[waku0.node.enr.toURI()]) confBuilder.discv5Conf.withEnabled(true) - confBuilder.discv5Conf.withUdpPort(9001.Port) - confBuilder.withP2pTcpPort(60001.Port) + confBuilder.discv5Conf.withUdpPort(Port(0)) + confBuilder.withP2pTcpPort(Port(0)) let conf1 = confBuilder.build().valueOr: raiseAssert error @@ -452,8 +447,8 @@ suite "Waku Discovery v5": await waku1.node.mountRendezvous(conf.clusterId) confBuilder.discv5Conf.withBootstrapNodes(@[waku1.node.enr.toURI()]) - confBuilder.withP2pTcpPort(60003.Port) - confBuilder.discv5Conf.withUdpPort(9003.Port) + confBuilder.withP2pTcpPort(Port(0)) + confBuilder.discv5Conf.withUdpPort(Port(0)) confBuilder.withNodeKey(crypto.PrivateKey.random(Secp256k1, myRng)[]) let conf2 = confBuilder.build().valueOr: @@ -505,7 +500,6 @@ suite "Waku Discovery v5": waku.rng, waku.conf.nodeKey, waku.conf.endpointConf.p2pListenAddress, - waku.conf.portsShift, ).valueOr: raiseAssert "failed setup discv5 in test: " & $error @@ -537,7 +531,6 @@ suite "Waku Discovery v5": waku.rng, waku.conf.nodeKey, waku.conf.endpointConf.p2pListenAddress, - waku.conf.portsShift, ).valueOr: raiseAssert "failed setup discv5 in test: " & $error diff --git a/tests/waku_filter_v2/test_waku_client.nim b/tests/waku_filter_v2/test_waku_client.nim index c5bd3c558..8a802d911 100644 --- a/tests/waku_filter_v2/test_waku_client.nim +++ b/tests/waku_filter_v2/test_waku_client.nim @@ -2257,12 +2257,9 @@ suite "Waku Filter - End to End": contentTopic = DefaultContentTopic contentTopicSeq = @[contentTopic] - client = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(23450)) - server = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(23451)) - client2nd = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(23452)) + client = newTestWakuNode(generateSecp256k1Key()) + server = newTestWakuNode(generateSecp256k1Key()) + client2nd = newTestWakuNode(generateSecp256k1Key()) await allFutures(server.start(), client.start(), client2nd.start()) diff --git a/tests/waku_keystore/utils.nim b/tests/waku_keystore/utils.nim index 9d86df9bb..61fc524ce 100644 --- a/tests/waku_keystore/utils.nim +++ b/tests/waku_keystore/utils.nim @@ -3,9 +3,7 @@ import stint -import - logos_delivery/waku/ - [waku_keystore/protocol_types, waku_rln_relay, waku_rln_relay/protocol_types] +import logos_delivery/waku/[waku_keystore/protocol_types, rln, rln/protocol_types] func fromStrToBytesLe*(v: string): seq[byte] = try: diff --git a/tests/waku_peer_exchange/test_protocol.nim b/tests/waku_peer_exchange/test_protocol.nim index 93c522c06..d8083158b 100644 --- a/tests/waku_peer_exchange/test_protocol.nim +++ b/tests/waku_peer_exchange/test_protocol.nim @@ -40,76 +40,31 @@ suite "Waku Peer Exchange": lightpush = false, filter = false, store = false, relay = true ) bindIp = parseIpAddress("0.0.0.0") - extIp = parseIpAddress("127.0.0.1") nodeKey1 = generateSecp256k1Key() - nodeTcpPort1 = Port(64010) - nodeUdpPort1 = Port(9000) - node1 = newTestWakuNode( - nodeKey1, - bindIp, - nodeTcpPort1, - some(extIp), - wakuFlags = some(flags), - discv5UdpPort = some(nodeUdpPort1), - ) + node1 = newTestWakuNode(nodeKey1, bindIp, Port(0), wakuFlags = some(flags)) nodeKey2 = generateSecp256k1Key() - nodeTcpPort2 = Port(64012) - nodeUdpPort2 = Port(9002) - node2 = newTestWakuNode( - nodeKey2, - bindIp, - nodeTcpPort2, - some(extIp), - wakuFlags = some(flags), - discv5UdpPort = some(nodeUdpPort2), - ) + node2 = newTestWakuNode(nodeKey2, bindIp, Port(0), wakuFlags = some(flags)) nodeKey3 = generateSecp256k1Key() - nodeTcpPort3 = Port(64014) - nodeUdpPort3 = Port(9004) - node3 = newTestWakuNode( - nodeKey3, - bindIp, - nodeTcpPort3, - some(extIp), - wakuFlags = some(flags), - discv5UdpPort = some(nodeUdpPort3), - ) - - # discv5 - let conf1 = WakuDiscoveryV5Config( - discv5Config: none(DiscoveryConfig), - address: bindIp, - port: nodeUdpPort1, - privateKey: keys.PrivateKey(nodeKey1.skkey), - bootstrapRecords: @[], - autoupdateRecord: true, - ) - - let disc1 = WakuDiscoveryV5.new( - node1.rng, conf1, some(node1.enr), some(node1.peerManager) - ) - - let conf2 = WakuDiscoveryV5Config( - discv5Config: none(DiscoveryConfig), - address: bindIp, - port: nodeUdpPort2, - privateKey: keys.PrivateKey(nodeKey2.skkey), - bootstrapRecords: @[disc1.protocol.getRecord()], - autoupdateRecord: true, - ) - - let disc2 = WakuDiscoveryV5.new( - node2.rng, conf2, some(node2.enr), some(node2.peerManager) - ) + node3 = newTestWakuNode(nodeKey3, bindIp, Port(0), wakuFlags = some(flags)) await allFutures(node1.start(), node2.start(), node3.start()) - let resultDisc1StartRes = await disc1.start() - assert resultDisc1StartRes.isOk(), resultDisc1StartRes.error - let resultDisc2StartRes = await disc2.start() - assert resultDisc2StartRes.isOk(), resultDisc2StartRes.error + + let disc1 = ( + await startDiscv5WithAutoPort(node1, keys.PrivateKey(nodeKey1.skkey), bindIp) + ).valueOr: + raiseAssert "disc1: " & error + let disc2 = ( + await startDiscv5WithAutoPort( + node2, + keys.PrivateKey(nodeKey2.skkey), + bindIp, + @[disc1.protocol.getRecord()], + ) + ).valueOr: + raiseAssert "disc2: " & error ## When var attempts = 10 @@ -142,14 +97,10 @@ suite "Waku Peer Exchange": asyncTest "Request returns some discovered peers": let - node1 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node3 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node4 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(generateSecp256k1Key()) + node2 = newTestWakuNode(generateSecp256k1Key()) + node3 = newTestWakuNode(generateSecp256k1Key()) + node4 = newTestWakuNode(generateSecp256k1Key()) # Start and mount peer exchange await allFutures([node1.start(), node2.start(), node3.start(), node4.start()]) @@ -199,10 +150,8 @@ suite "Waku Peer Exchange": asyncTest "Request fails gracefully": let - node1 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(generateSecp256k1Key()) + node2 = newTestWakuNode(generateSecp256k1Key()) # Start and mount peer exchange await allFutures([node1.start(), node2.start()]) @@ -276,12 +225,9 @@ suite "Waku Peer Exchange": asyncTest "Request 0 peers, with 1 peer in PeerExchange": # Given two valid nodes with PeerExchange let - node1 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node3 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(generateSecp256k1Key()) + node2 = newTestWakuNode(generateSecp256k1Key()) + node3 = newTestWakuNode(generateSecp256k1Key()) # Start and mount peer exchange await allFutures([node1.start(), node2.start(), node3.start()]) @@ -308,10 +254,8 @@ suite "Waku Peer Exchange": asyncTest "Request with invalid peer info": # Given two valid nodes with PeerExchange let - node1 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(generateSecp256k1Key()) + node2 = newTestWakuNode(generateSecp256k1Key()) # Start and mount peer exchange await allFutures([node1.start(), node2.start()]) @@ -329,9 +273,7 @@ suite "Waku Peer Exchange": asyncTest "Connections are closed after response is sent": # Create 3 nodes - let nodes = toSeq(0 ..< 3).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 3).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) await allFutures(nodes.mapIt(it.mountPeerExchange())) @@ -356,12 +298,9 @@ suite "Waku Peer Exchange": suite "Protocol Handler": asyncTest "Works as expected": let - node1 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node3 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(generateSecp256k1Key()) + node2 = newTestWakuNode(generateSecp256k1Key()) + node3 = newTestWakuNode(generateSecp256k1Key()) # Start and mount peer exchange await allFutures([node1.start(), node2.start(), node3.start()]) @@ -398,12 +337,9 @@ suite "Waku Peer Exchange": asyncTest "RateLimit as expected": let - node1 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node2 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - node3 = - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(generateSecp256k1Key()) + node2 = newTestWakuNode(generateSecp256k1Key()) + node3 = newTestWakuNode(generateSecp256k1Key()) # Start and mount peer exchange await allFutures([node1.start(), node2.start(), node3.start()]) diff --git a/tests/waku_peer_exchange/utils.nim b/tests/waku_peer_exchange/utils.nim index c72405c51..f762541f8 100644 --- a/tests/waku_peer_exchange/utils.nim +++ b/tests/waku_peer_exchange/utils.nim @@ -1,7 +1,8 @@ {.used.} import - std/options, + std/[options, net], + results, testutils/unittests, chronos, libp2p/switch, @@ -14,6 +15,7 @@ import logos_delivery/waku/[ waku_node, discovery/waku_discv5, + net/auto_port, waku_peer_exchange, waku_peer_exchange/rpc, waku_peer_exchange/protocol, @@ -23,6 +25,33 @@ import ], ../testlib/[futures, wakucore, assertions] +proc startDiscv5WithAutoPort*( + node: WakuNode, + key: keys.PrivateKey, + bindIp: IpAddress, + bootstrapRecords: seq[enr.Record] = @[], +): Future[Result[WakuDiscoveryV5, string]] {.async.} = + proc attempt( + p: Port + ): Future[Result[WakuDiscoveryV5, string]] {.async: (raises: []).} = + var record = node.enr + record.update(key, udpPort = Opt.some(p)).isOkOr: + return err("could not set discv5 udp port in enr: " & $error) + let conf = WakuDiscoveryV5Config( + discv5Config: none(DiscoveryConfig), + address: bindIp, + port: p, + privateKey: key, + bootstrapRecords: bootstrapRecords, + autoupdateRecord: true, + ) + let wd = WakuDiscoveryV5.new(node.rng, conf, some(record), some(node.peerManager)) + (await wd.start()).isOkOr: + return err(error) + return ok(wd) + + return await tryWithAutoPort[WakuDiscoveryV5](Port(0), attempt) + proc dialForPeerExchange*( client: WakuNode, peerInfo: RemotePeerInfo, diff --git a/tests/waku_relay/test_wakunode_relay.nim b/tests/waku_relay/test_wakunode_relay.nim index 311454e22..6ec622f2a 100644 --- a/tests/waku_relay/test_wakunode_relay.nim +++ b/tests/waku_relay/test_wakunode_relay.nim @@ -24,7 +24,7 @@ suite "WakuNode - Relay": asyncTest "Relay protocol is started correctly": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) # Relay protocol starts if mounted after node start @@ -39,7 +39,7 @@ suite "WakuNode - Relay": let nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -59,11 +59,11 @@ suite "WakuNode - Relay": asyncTest "Messages are correctly relayed": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) nodeKey3 = generateSecp256k1Key() - node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) + node3 = newTestWakuNode(nodeKey3) shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() @@ -134,13 +134,13 @@ suite "WakuNode - Relay": let # publisher node nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) # Relay node nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) # Subscriber nodeKey3 = generateSecp256k1Key() - node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) + node3 = newTestWakuNode(nodeKey3) shard = DefaultRelayShard contentTopic1 = ContentTopic("/waku/2/default-content/proto") @@ -238,9 +238,7 @@ suite "WakuNode - Relay": # TODO: Add a function to validate the WakuMessage integrity xasyncTest "Stats of peer sending wrong WakuMessages are updated": # Create 2 nodes - let nodes = toSeq(0 .. 1).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 .. 1).mapIt(newTestWakuNode(generateSecp256k1Key())) # Start all the nodes and mount relay with await allFutures(nodes.mapIt(it.start())) @@ -278,21 +276,9 @@ suite "WakuNode - Relay": asyncTest "Messages are relayed between two websocket nodes": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode( - nodeKey1, - parseIpAddress("0.0.0.0"), - bindPort = Port(0), - wsBindPort = Port(0), - wsEnabled = true, - ) + node1 = newTestWakuNode(nodeKey1, wsBindPort = Port(0), wsEnabled = true) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode( - nodeKey2, - parseIpAddress("0.0.0.0"), - bindPort = Port(0), - wsBindPort = Port(0), - wsEnabled = true, - ) + node2 = newTestWakuNode(nodeKey2, wsBindPort = Port(0), wsEnabled = true) shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") payload = "hello world".toBytes() @@ -342,13 +328,7 @@ suite "WakuNode - Relay": asyncTest "Messages are relayed between nodes with multiple transports (TCP and Websockets)": let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode( - nodeKey1, - parseIpAddress("0.0.0.0"), - bindPort = Port(0), - wsBindPort = Port(0), - wsEnabled = true, - ) + node1 = newTestWakuNode(nodeKey1, wsBindPort = Port(0), wsEnabled = true) nodeKey2 = generateSecp256k1Key() node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), bindPort = Port(0)) shard = DefaultRelayShard @@ -406,12 +386,7 @@ suite "WakuNode - Relay": ) nodeKey2 = generateSecp256k1Key() node2 = newTestWakuNode( - nodeKey2, - parseIpAddress("0.0.0.0"), - bindPort = Port(0), - wsBindPort = Port(0), - wsEnabled = true, - quicEnabled = false, + nodeKey2, wsBindPort = Port(0), wsEnabled = true, quicEnabled = false ) shard = DefaultRelayShard contentTopic = ContentTopic("/waku/2/default-content/proto") @@ -469,8 +444,6 @@ suite "WakuNode - Relay": nodeKey1 = generateSecp256k1Key() node1 = newTestWakuNode( nodeKey1, - parseIpAddress("0.0.0.0"), - bindPort = Port(0), wsBindPort = Port(0), wssEnabled = true, secureKey = KEY_PATH, @@ -529,21 +502,13 @@ suite "WakuNode - Relay": nodeKey1 = generateSecp256k1Key() node1 = newTestWakuNode( nodeKey1, - parseIpAddress("0.0.0.0"), - bindPort = Port(0), wsBindPort = Port(0), wssEnabled = true, secureKey = KEY_PATH, secureCert = CERT_PATH, ) nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode( - nodeKey2, - parseIpAddress("0.0.0.0"), - bindPort = Port(0), - wsBindPort = Port(0), - wsEnabled = true, - ) + node2 = newTestWakuNode(nodeKey2, wsBindPort = Port(0), wsEnabled = true) let shard = DefaultRelayShard @@ -594,9 +559,7 @@ suite "WakuNode - Relay": asyncTest "Bad peers with low reputation are disconnected": # Create 5 nodes - let nodes = toSeq(0 ..< 5).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 5).mapIt(newTestWakuNode(generateSecp256k1Key())) await allFutures(nodes.mapIt(it.start())) await allFutures(nodes.mapIt(it.mountRelay())) @@ -657,7 +620,7 @@ suite "WakuNode - Relay": ## Setup let nodeKey = generateSecp256k1Key() - node = newTestWakuNode(nodeKey, parseIpAddress("0.0.0.0"), Port(0)) + node = newTestWakuNode(nodeKey) await node.start() (await node.mountRelay()).isOkOr: diff --git a/tests/waku_relay/utils.nim b/tests/waku_relay/utils.nim index 0391d47bc..663d06c18 100644 --- a/tests/waku_relay/utils.nim +++ b/tests/waku_relay/utils.nim @@ -13,9 +13,8 @@ import brokers/broker_context from std/times import epochTime import - logos_delivery/waku/[ - waku_relay, node/waku_node, node/peer_manager, waku_core, waku_node, waku_rln_relay - ], + logos_delivery/waku/ + [waku_relay, node/waku_node, node/peer_manager, waku_core, waku_node, rln], ../waku_store/store_utils, ../waku_archive/archive_utils, ../testlib/[wakucore, futures] @@ -36,11 +35,6 @@ proc newTestWakuRelay*(switch = newTestSwitch()): Future[WakuRelay] {.async.} = return proto -proc setupRln*(node: WakuNode, identifier: uint) {.async.} = - await node.mountRlnRelay( - WakuRlnConfig(dynamic: false, credIndex: some(identifier), epochSizeSec: 1) - ) - proc subscribeToContentTopicWithHandler*( node: WakuNode, contentTopic: string ): Future[bool] = @@ -78,7 +72,7 @@ proc sendRlnMessage*( ): Future[bool] {.async.} = var message = WakuMessage(payload: payload, contentTopic: contentTopic) message.proof = ( - await client.wakuRlnRelay.generateRLNProof(message.toRLNSignal(), epochTime()) + await client.rln.generateRLNProof(message.toRLNSignal(), epochTime()) ).valueOr: raiseAssert "generateRLNProof failed: " & error discard await client.publish(some(pubsubTopic), message) @@ -93,10 +87,10 @@ proc sendRlnMessageWithInvalidProof*( payload: seq[byte] = "Hello".toBytes(), ): Future[bool] {.async.} = let extraBytes: seq[byte] = @[byte(1), 2, 3] - let rateLimitProofRes = await client.wakuRlnRelay.groupManager.generateProof( + let rateLimitProofRes = await client.rln.groupManager.generateProof( concat(payload, extraBytes), # we add extra bytes to invalidate proof verification against original payload - client.wakuRlnRelay.getCurrentEpoch(), + client.rln.getCurrentEpoch(), messageId = MessageId(0), ) let diff --git a/tests/waku_rln_relay/rln/test_rln_interface.nim b/tests/waku_rln_relay/rln/test_rln_interface.nim index 5ace0c8bb..7d714c2dd 100644 --- a/tests/waku_rln_relay/rln/test_rln_interface.nim +++ b/tests/waku_rln_relay/rln/test_rln_interface.nim @@ -1,7 +1,7 @@ import testutils/unittests, results -import logos_delivery/waku/waku_rln_relay/rln/rln_interface -import logos_delivery/waku/waku_rln_relay/rln/wrappers +import logos_delivery/waku/rln/rln/rln_interface +import logos_delivery/waku/rln/rln/wrappers suite "Vec_uint8": suite "toVecUint8": diff --git a/tests/waku_rln_relay/rln/test_wrappers.nim b/tests/waku_rln_relay/rln/test_wrappers.nim index 8d969a31b..9b239dbf0 100644 --- a/tests/waku_rln_relay/rln/test_wrappers.nim +++ b/tests/waku_rln_relay/rln/test_wrappers.nim @@ -1,8 +1,8 @@ import testutils/unittests, results import - logos_delivery/waku/waku_rln_relay/rln, - logos_delivery/waku/waku_rln_relay/rln/wrappers, + logos_delivery/waku/rln/bindings, + logos_delivery/waku/rln/rln/wrappers, ./waku_rln_relay_utils suite "membershipKeyGen": diff --git a/tests/waku_rln_relay/rln/waku_rln_relay_utils.nim b/tests/waku_rln_relay/rln/waku_rln_relay_utils.nim index 81032eab9..d7577b88a 100644 --- a/tests/waku_rln_relay/rln/waku_rln_relay_utils.nim +++ b/tests/waku_rln_relay/rln/waku_rln_relay_utils.nim @@ -1,19 +1,20 @@ import std/options import std/tempfiles +import results import - logos_delivery/waku/waku_rln_relay, - logos_delivery/waku/waku_rln_relay/[ - group_manager, rln, conversion_utils, constants, protocol_types, protocol_metrics, - nonce_manager, + logos_delivery/waku/rln, + logos_delivery/waku/rln/[ + group_manager, bindings, conversion_utils, constants, protocol_types, + protocol_metrics, nonce_manager, ] -proc createRLNInstanceWrapper*(): RLNResult = +proc createRLNInstanceWrapper*(): Result[ptr RlnRaw, string] = return createRlnInstance() proc unsafeAppendRLNProof*( - rlnPeer: WakuRLNRelay, msg: var WakuMessage, epoch: Epoch, messageId: MessageId -): RlnRelayResult[void] = + rlnPeer: Rln, msg: var WakuMessage, epoch: Epoch, messageId: MessageId +): Result[void, string] = ## Test helper derived from the publish-path proof flow. ## - Skips nonce validation to intentionally allow generating "bad" message IDs for tests. ## - Forces a real-time on-chain Merkle root refresh via `updateRoots()` and fetches Merkle diff --git a/tests/waku_rln_relay/test_rln_contract_deployment.nim b/tests/waku_rln_relay/test_rln_contract_deployment.nim index f903a2382..e5600e829 100644 --- a/tests/waku_rln_relay/test_rln_contract_deployment.nim +++ b/tests/waku_rln_relay/test_rln_contract_deployment.nim @@ -5,11 +5,8 @@ import std/[options, os], results, testutils/unittests, chronos, web3 import - logos_delivery/waku/[ - waku_rln_relay, - waku_rln_relay/conversion_utils, - waku_rln_relay/group_manager/on_chain/group_manager, - ], + logos_delivery/waku/ + [rln, rln/conversion_utils, rln/group_manager/on_chain/group_manager], ./utils_onchain suite "Token and RLN Contract Deployment": diff --git a/tests/waku_rln_relay/test_rln_group_manager_onchain.nim b/tests/waku_rln_relay/test_rln_group_manager_onchain.nim index fa406eda3..491da4274 100644 --- a/tests/waku_rln_relay/test_rln_group_manager_onchain.nim +++ b/tests/waku_rln_relay/test_rln_group_manager_onchain.nim @@ -18,13 +18,13 @@ import import logos_delivery/waku/[ - waku_rln_relay, - waku_rln_relay/protocol_types, - waku_rln_relay/protocol_metrics, - waku_rln_relay/constants, - waku_rln_relay/rln, - waku_rln_relay/conversion_utils, - waku_rln_relay/group_manager/on_chain/group_manager, + rln, + rln/protocol_types, + rln/protocol_metrics, + rln/constants, + rln/bindings, + rln/conversion_utils, + rln/group_manager/on_chain/group_manager, ], ../testlib/wakucore, ./utils_onchain diff --git a/tests/waku_rln_relay/test_rln_nonce_manager.nim b/tests/waku_rln_relay/test_rln_nonce_manager.nim index fb24f0e29..f9e541e1a 100644 --- a/tests/waku_rln_relay/test_rln_nonce_manager.nim +++ b/tests/waku_rln_relay/test_rln_nonce_manager.nim @@ -1,7 +1,7 @@ {.used.} import testutils/unittests, chronos, os -import logos_delivery/waku/waku_rln_relay/nonce_manager +import logos_delivery/waku/rln/nonce_manager suite "Nonce manager": test "should initialize successfully": diff --git a/tests/waku_rln_relay/test_waku_rln_relay.nim b/tests/waku_rln_relay/test_waku_rln_relay.nim index 28557c1b7..10de8d4d6 100644 --- a/tests/waku_rln_relay/test_waku_rln_relay.nim +++ b/tests/waku_rln_relay/test_waku_rln_relay.nim @@ -12,13 +12,8 @@ import import brokers/broker_context import - logos_delivery/waku/[ - waku_core, - waku_rln_relay, - waku_rln_relay/rln, - waku_rln_relay/protocol_metrics, - waku_keystore, - ], + logos_delivery/waku/ + [waku_core, rln, rln/bindings, rln/protocol_metrics, waku_keystore], ./rln/waku_rln_relay_utils, ./utils_onchain, ../testlib/[wakucore, futures, wakunode, testutils] @@ -159,8 +154,8 @@ suite "Waku rln relay": test "updateLog and hasDuplicate tests": let - wakuRlnRelay = WakuRLNRelay() - epoch = wakuRlnRelay.getCurrentEpoch() + rln = Rln() + epoch = rln.getCurrentEpoch() # create some dummy nullifiers and secret shares var nullifier1: Nullifier @@ -205,25 +200,23 @@ suite "Waku rln relay": # check whether hasDuplicate correctly finds records with the same nullifiers but different secret shares # no duplicate for proof1 should be found, since the log is empty let proofMetadata1 = proof1.extractMetadata().tryGet() - let isDuplicate1 = wakuRlnRelay.hasDuplicate(epoch, proofMetadata1).valueOr: + let isDuplicate1 = rln.hasDuplicate(epoch, proofMetadata1).valueOr: raiseAssert $error assert isDuplicate1 == false, "no duplicate should be found" # add it to the log - discard wakuRlnRelay.updateLog(epoch, proofMetadata1) + discard rln.updateLog(epoch, proofMetadata1) # no duplicate for proof2 should be found, its nullifier differs from proof1 let proofMetadata2 = proof2.extractMetadata().tryGet() - let isDuplicate2 = wakuRlnRelay.hasDuplicate(epoch, proofMetadata2).valueOr: + let isDuplicate2 = rln.hasDuplicate(epoch, proofMetadata2).valueOr: raiseAssert $error # no duplicate is found assert isDuplicate2 == false, "no duplicate should be found" # add it to the log - discard wakuRlnRelay.updateLog(epoch, proofMetadata2) + discard rln.updateLog(epoch, proofMetadata2) # proof3 has the same nullifier as proof1 but different secret shares, it should be detected as duplicate - let isDuplicate3 = wakuRlnRelay.hasDuplicate( - epoch, proof3.extractMetadata().tryGet() - ).valueOr: + let isDuplicate3 = rln.hasDuplicate(epoch, proof3.extractMetadata().tryGet()).valueOr: raiseAssert $error # it is a duplicate assert isDuplicate3, "duplicate should be found" @@ -232,18 +225,18 @@ suite "Waku rln relay": let index = MembershipIndex(5) let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = index) - var wakuRlnRelay: WakuRlnRelay + var rln: Rln lockNewGlobalBrokerContext: - wakuRlnRelay = (await WakuRlnRelay.new(wakuRlnConfig)).valueOr: + rln = (await Rln.new(wakuRlnConfig)).valueOr: raiseAssert $error - let manager = cast[OnchainGroupManager](wakuRlnRelay.groupManager) + let manager = cast[OnchainGroupManager](rln.groupManager) let idCredentials = generateCredentials() (waitFor manager.register(idCredentials, UserMessageLimit(20))).isOkOr: assert false, "error returned when calling register: " & error - let epoch1 = wakuRlnRelay.getCurrentEpoch() + let epoch1 = rln.getCurrentEpoch() # Create messages from the same peer and append RLN proof to them (except wm4) var @@ -252,7 +245,7 @@ suite "Waku rln relay": wm2 = WakuMessage(payload: "Spam message".toBytes(), timestamp: now()) await sleepAsync(1.seconds) - let epoch2 = wakuRlnRelay.getCurrentEpoch() + let epoch2 = rln.getCurrentEpoch() var # wm3 points to the next epoch due to the sleep @@ -260,22 +253,22 @@ suite "Waku rln relay": wm4 = WakuMessage(payload: "Invalid message".toBytes(), timestamp: now()) # Append RLN proofs - wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch1, MessageId(1)).isOkOr: + rln.unsafeAppendRLNProof(wm1, epoch1, MessageId(1)).isOkOr: raiseAssert $error - wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch1, MessageId(1)).isOkOr: + rln.unsafeAppendRLNProof(wm2, epoch1, MessageId(1)).isOkOr: raiseAssert $error - wakuRlnRelay.unsafeAppendRLNProof(wm3, epoch2, MessageId(3)).isOkOr: + rln.unsafeAppendRLNProof(wm3, epoch2, MessageId(3)).isOkOr: raiseAssert $error # Validate messages let - msgValidate1 = await wakuRlnRelay.validateMessageAndUpdateLog(wm1) + msgValidate1 = await rln.validateMessageAndUpdateLog(wm1) # wm2 is within the same epoch as wm1 → should be spam - msgValidate2 = await wakuRlnRelay.validateMessageAndUpdateLog(wm2) + msgValidate2 = await rln.validateMessageAndUpdateLog(wm2) # wm3 is in the next epoch → should be valid - msgValidate3 = await wakuRlnRelay.validateMessageAndUpdateLog(wm3) + msgValidate3 = await rln.validateMessageAndUpdateLog(wm3) # wm4 has no RLN proof → should be invalid - msgValidate4 = await wakuRlnRelay.validateMessageAndUpdateLog(wm4) + msgValidate4 = await rln.validateMessageAndUpdateLog(wm4) check: msgValidate1 == MessageValidationResult.Valid @@ -288,21 +281,21 @@ suite "Waku rln relay": let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = index) - var wakuRlnRelay: WakuRlnRelay + var rln: Rln lockNewGlobalBrokerContext: - wakuRlnRelay = (await WakuRlnRelay.new(wakuRlnConfig)).valueOr: + rln = (await Rln.new(wakuRlnConfig)).valueOr: raiseAssert $error - let manager = cast[OnchainGroupManager](wakuRlnRelay.groupManager) + let manager = cast[OnchainGroupManager](rln.groupManager) let idCredentials = generateCredentials() (waitFor manager.register(idCredentials, UserMessageLimit(20))).isOkOr: assert false, "error returned when calling register: " & error # usually it's 20 seconds but we set it to 1 for testing purposes which make the test faster - wakuRlnRelay.rlnMaxTimestampGap = 1 + rln.rlnMaxTimestampGap = 1 - var epoch = wakuRlnRelay.getCurrentEpoch() + var epoch = rln.getCurrentEpoch() var wm1 = WakuMessage( @@ -316,19 +309,19 @@ suite "Waku rln relay": timestamp: now(), ) - wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch, MessageId(1)).isOkOr: + rln.unsafeAppendRLNProof(wm1, epoch, MessageId(1)).isOkOr: raiseAssert $error - wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch, MessageId(2)).isOkOr: + rln.unsafeAppendRLNProof(wm2, epoch, MessageId(2)).isOkOr: raiseAssert $error # validate the first message because it's timestamp is the same as the generated timestamp - let msgValidate1 = await wakuRlnRelay.validateMessageAndUpdateLog(wm1) + let msgValidate1 = await rln.validateMessageAndUpdateLog(wm1) # wait for 2 seconds to make the timestamp different from generated timestamp await sleepAsync(2.seconds) - let msgValidate2 = await wakuRlnRelay.validateMessageAndUpdateLog(wm2) + let msgValidate2 = await rln.validateMessageAndUpdateLog(wm2) check: msgValidate1 == MessageValidationResult.Valid @@ -337,9 +330,9 @@ suite "Waku rln relay": asyncTest "multiple senders with same external nullifier": let index1 = MembershipIndex(5) let rlnConf1 = getWakuRlnConfig(manager = manager, index = index1) - var wakuRlnRelay1: WakuRlnRelay + var wakuRlnRelay1: Rln lockNewGlobalBrokerContext: - wakuRlnRelay1 = (await WakuRlnRelay.new(rlnConf1)).valueOr: + wakuRlnRelay1 = (await Rln.new(rlnConf1)).valueOr: raiseAssert "failed to create waku rln relay: " & $error let manager1 = cast[OnchainGroupManager](wakuRlnRelay1.groupManager) @@ -350,9 +343,9 @@ suite "Waku rln relay": let index2 = MembershipIndex(6) let rlnConf2 = getWakuRlnConfig(manager = manager, index = index2) - var wakuRlnRelay2: WakuRlnRelay + var wakuRlnRelay2: Rln lockNewGlobalBrokerContext: - wakuRlnRelay2 = (await WakuRlnRelay.new(rlnConf2)).valueOr: + wakuRlnRelay2 = (await Rln.new(rlnConf2)).valueOr: raiseAssert "failed to create waku rln relay: " & $error let manager2 = cast[OnchainGroupManager](wakuRlnRelay2.groupManager) @@ -481,31 +474,31 @@ suite "Waku rln relay": let wakuRlnConfig = getWakuRlnConfig( manager = manager, index = index, epochSizeSec = rlnEpochSizeSec.uint64 ) - var wakuRlnRelay: WakuRlnRelay + var rln: Rln lockNewGlobalBrokerContext: - wakuRlnRelay = (await WakuRlnRelay.new(wakuRlnConfig)).valueOr: + rln = (await Rln.new(wakuRlnConfig)).valueOr: raiseAssert $error - let rlnMaxEpochGap = wakuRlnRelay.rlnMaxEpochGap + let rlnMaxEpochGap = rln.rlnMaxEpochGap let testProofMetadata = default(ProofMetadata) let testProofMetadataTable = {testProofMetadata.nullifier: testProofMetadata}.toTable() for i in 0 .. rlnMaxEpochGap: # we add epochs to the nullifierLog - let testEpoch = wakuRlnRelay.calcEpoch(epochTime() + float(rlnEpochSizeSec * i)) - wakuRlnRelay.nullifierLog[testEpoch] = testProofMetadataTable + let testEpoch = rln.calcEpoch(epochTime() + float(rlnEpochSizeSec * i)) + rln.nullifierLog[testEpoch] = testProofMetadataTable check: - wakuRlnRelay.nullifierLog.len().uint == i + 1 + rln.nullifierLog.len().uint == i + 1 check: - wakuRlnRelay.nullifierLog.len().uint == rlnMaxEpochGap + 1 + rln.nullifierLog.len().uint == rlnMaxEpochGap + 1 # clearing it now will remove 1 epoch - wakuRlnRelay.clearNullifierLog() + rln.clearNullifierLog() check: - wakuRlnRelay.nullifierLog.len().uint == rlnMaxEpochGap + rln.nullifierLog.len().uint == rlnMaxEpochGap var testEpochSizes: seq[uint] = @[1, 5, 10, 30, 60, 600] for i in testEpochSizes: diff --git a/tests/waku_rln_relay/test_wakunode_rln_relay.nim b/tests/waku_rln_relay/test_wakunode_rln_relay.nim index 0056e575e..7954317e7 100644 --- a/tests/waku_rln_relay/test_wakunode_rln_relay.nim +++ b/tests/waku_rln_relay/test_wakunode_rln_relay.nim @@ -11,7 +11,7 @@ import brokers/broker_context import - logos_delivery/waku/[waku_core, waku_node, waku_rln_relay], + logos_delivery/waku/[waku_core, waku_node, rln], ../testlib/[wakucore, futures, wakunode, testutils], ./utils_onchain, ./rln/waku_rln_relay_utils @@ -21,7 +21,7 @@ from std/times import epochTime proc waitForNullifierLog(node: WakuNode, expectedLen: int): Future[bool] {.async.} = ## Helper function for i in 0 .. 100: # Try for up to 50 seconds (100 * 500ms) - if node.wakuRlnRelay.nullifierLog.len() == expectedLen: + if node.rln.nullifierLog.len() == expectedLen: return true await sleepAsync(500.millis) return false @@ -44,7 +44,7 @@ procSuite "WakuNode - RLN relay": # set up three nodes lockNewGlobalBrokerContext: let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) (await node1.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -52,11 +52,11 @@ procSuite "WakuNode - RLN relay": let wakuRlnConfig1 = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node1.mountRlnRelay(wakuRlnConfig1) + await node1.setRlnValidator(wakuRlnConfig1) await node1.start() # Registration is mandatory before sending messages with rln-relay - let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager) + let manager1 = cast[OnchainGroupManager](node1.rln.groupManager) let idCredentials1 = generateCredentials() (waitFor manager1.register(idCredentials1, UserMessageLimit(20))).isOkOr: @@ -68,7 +68,7 @@ procSuite "WakuNode - RLN relay": lockNewGlobalBrokerContext: # Relay node let nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -76,17 +76,17 @@ procSuite "WakuNode - RLN relay": let wakuRlnConfig2 = getWakuRlnConfig(manager = manager, index = MembershipIndex(2)) - await node2.mountRlnRelay(wakuRlnConfig2) + await node2.setRlnValidator(wakuRlnConfig2) await node2.start() - let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager) + let manager2 = cast[OnchainGroupManager](node2.rln.groupManager) let rootUpdated2 = waitFor manager2.updateRoots() info "Updated root for node2", rootUpdated2 lockNewGlobalBrokerContext: # Subscriber let nodeKey3 = generateSecp256k1Key() - node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) + node3 = newTestWakuNode(nodeKey3) (await node3.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -94,10 +94,10 @@ procSuite "WakuNode - RLN relay": let wakuRlnConfig3 = getWakuRlnConfig(manager = manager, index = MembershipIndex(3)) - await node3.mountRlnRelay(wakuRlnConfig3) + await node3.setRlnValidator(wakuRlnConfig3) await node3.start() - let manager3 = cast[OnchainGroupManager](node3.wakuRlnRelay.groupManager) + let manager3 = cast[OnchainGroupManager](node3.rln.groupManager) let rootUpdated3 = waitFor manager3.updateRoots() info "Updated root for node3", rootUpdated3 @@ -135,10 +135,8 @@ procSuite "WakuNode - RLN relay": var message = WakuMessage(payload: @payload, contentTopic: contentTopic, timestamp: now()) doAssert( - node1.wakuRlnRelay - .unsafeAppendRLNProof( - message, node1.wakuRlnRelay.getCurrentEpoch(), MessageId(0) - ) + node1.rln + .unsafeAppendRLNProof(message, node1.rln.getCurrentEpoch(), MessageId(0)) .isOk() ) @@ -162,14 +160,14 @@ procSuite "WakuNode - RLN relay": var node1, node2, node3: WakuNode lockNewGlobalBrokerContext: let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) (await node1.mountRelay()).isOkOr: assert false, "Failed to mount relay" let wakuRlnConfig1 = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node1.mountRlnRelay(wakuRlnConfig1) + await node1.setRlnValidator(wakuRlnConfig1) await node1.start() - let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager) + let manager1 = cast[OnchainGroupManager](node1.rln.groupManager) let idCredentials1 = generateCredentials() (waitFor manager1.register(idCredentials1, UserMessageLimit(20))).isOkOr: @@ -179,14 +177,14 @@ procSuite "WakuNode - RLN relay": info "Updated root for node", node = 1, rootUpdated = rootUpdated1 lockNewGlobalBrokerContext: let nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" let wakuRlnConfig2 = getWakuRlnConfig(manager = manager, index = MembershipIndex(2)) - await node2.mountRlnRelay(wakuRlnConfig2) + await node2.setRlnValidator(wakuRlnConfig2) await node2.start() - let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager) + let manager2 = cast[OnchainGroupManager](node2.rln.groupManager) let idCredentials2 = generateCredentials() (waitFor manager2.register(idCredentials2, UserMessageLimit(20))).isOkOr: @@ -196,14 +194,14 @@ procSuite "WakuNode - RLN relay": info "Updated root for node", node = 2, rootUpdated = rootUpdated2 lockNewGlobalBrokerContext: let nodeKey3 = generateSecp256k1Key() - node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) + node3 = newTestWakuNode(nodeKey3) (await node3.mountRelay()).isOkOr: assert false, "Failed to mount relay" let wakuRlnConfig3 = getWakuRlnConfig(manager = manager, index = MembershipIndex(3)) - await node3.mountRlnRelay(wakuRlnConfig3) + await node3.setRlnValidator(wakuRlnConfig3) await node3.start() - let manager3 = cast[OnchainGroupManager](node3.wakuRlnRelay.groupManager) + let manager3 = cast[OnchainGroupManager](node3.rln.groupManager) let idCredentials3 = generateCredentials() (waitFor manager3.register(idCredentials3, UserMessageLimit(20))).isOkOr: @@ -264,8 +262,8 @@ procSuite "WakuNode - RLN relay": contentTopic: contentTopics[0], ) - node1.wakuRlnRelay.unsafeAppendRLNProof( - message, node1.wakuRlnRelay.getCurrentEpoch(), MessageId(i.uint8) + node1.rln.unsafeAppendRLNProof( + message, node1.rln.getCurrentEpoch(), MessageId(i.uint8) ).isOkOr: raiseAssert $error messages1.add(message) @@ -277,8 +275,8 @@ procSuite "WakuNode - RLN relay": contentTopic: contentTopics[1], ) - node2.wakuRlnRelay.unsafeAppendRLNProof( - message, node2.wakuRlnRelay.getCurrentEpoch(), MessageId(i.uint8) + node2.rln.unsafeAppendRLNProof( + message, node2.rln.getCurrentEpoch(), MessageId(i.uint8) ).isOkOr: raiseAssert $error messages2.add(message) @@ -309,7 +307,7 @@ procSuite "WakuNode - RLN relay": lockNewGlobalBrokerContext: # publisher node let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) (await node1.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -317,10 +315,10 @@ procSuite "WakuNode - RLN relay": let wakuRlnConfig1 = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node1.mountRlnRelay(wakuRlnConfig1) + await node1.setRlnValidator(wakuRlnConfig1) await node1.start() - let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager) + let manager1 = cast[OnchainGroupManager](node1.rln.groupManager) let idCredentials1 = generateCredentials() (waitFor manager1.register(idCredentials1, UserMessageLimit(20))).isOkOr: @@ -331,33 +329,33 @@ procSuite "WakuNode - RLN relay": lockNewGlobalBrokerContext: # Relay node let nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" # mount rlnrelay in off-chain mode let wakuRlnConfig2 = getWakuRlnConfig(manager = manager, index = MembershipIndex(2)) - await node2.mountRlnRelay(wakuRlnConfig2) + await node2.setRlnValidator(wakuRlnConfig2) await node2.start() - let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager) + let manager2 = cast[OnchainGroupManager](node2.rln.groupManager) let rootUpdated2 = waitFor manager2.updateRoots() info "Updated root for node2", rootUpdated2 lockNewGlobalBrokerContext: # Subscriber let nodeKey3 = generateSecp256k1Key() - node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) + node3 = newTestWakuNode(nodeKey3) (await node3.mountRelay()).isOkOr: assert false, "Failed to mount relay" let wakuRlnConfig3 = getWakuRlnConfig(manager = manager, index = MembershipIndex(3)) - await node3.mountRlnRelay(wakuRlnConfig3) + await node3.setRlnValidator(wakuRlnConfig3) await node3.start() - let manager3 = cast[OnchainGroupManager](node3.wakuRlnRelay.groupManager) + let manager3 = cast[OnchainGroupManager](node3.rln.groupManager) let rootUpdated3 = waitFor manager3.updateRoots() info "Updated root for node3", rootUpdated3 @@ -392,12 +390,12 @@ procSuite "WakuNode - RLN relay": # prepare the message payload let payload = "valid".toBytes() # prepare the epoch - let epoch = node1.wakuRlnRelay.getCurrentEpoch() + let epoch = node1.rln.getCurrentEpoch() var message = WakuMessage(payload: @payload, contentTopic: DefaultPubsubTopic, timestamp: now()) - node1.wakuRlnRelay.unsafeAppendRLNProof(message, epoch, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(message, epoch, MessageId(0)).isOkOr: assert false, "Failed to append rln proof: " & $error # message.payload = "Invalid".toBytes() @@ -420,7 +418,7 @@ procSuite "WakuNode - RLN relay": lockNewGlobalBrokerContext: # publisher node let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) (await node1.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -428,11 +426,11 @@ procSuite "WakuNode - RLN relay": let wakuRlnConfig1 = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node1.mountRlnRelay(wakuRlnConfig1) + await node1.setRlnValidator(wakuRlnConfig1) await node1.start() # Registration is mandatory before sending messages with rln-relay - let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager) + let manager1 = cast[OnchainGroupManager](node1.rln.groupManager) let idCredentials1 = generateCredentials() (waitFor manager1.register(idCredentials1, UserMessageLimit(20))).isOkOr: @@ -443,7 +441,7 @@ procSuite "WakuNode - RLN relay": lockNewGlobalBrokerContext: # Relay node let nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -451,17 +449,17 @@ procSuite "WakuNode - RLN relay": let wakuRlnConfig2 = getWakuRlnConfig(manager = manager, index = MembershipIndex(2)) - await node2.mountRlnRelay(wakuRlnConfig2) + await node2.setRlnValidator(wakuRlnConfig2) await node2.start() # Registration is mandatory before sending messages with rln-relay - let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager) + let manager2 = cast[OnchainGroupManager](node2.rln.groupManager) let rootUpdated2 = waitFor manager2.updateRoots() info "Updated root for node2", rootUpdated2 lockNewGlobalBrokerContext: # Subscriber let nodeKey3 = generateSecp256k1Key() - node3 = newTestWakuNode(nodeKey3, parseIpAddress("0.0.0.0"), Port(0)) + node3 = newTestWakuNode(nodeKey3) (await node3.mountRelay()).isOkOr: assert false, "Failed to mount relay" @@ -469,11 +467,11 @@ procSuite "WakuNode - RLN relay": let wakuRlnConfig3 = getWakuRlnConfig(manager = manager, index = MembershipIndex(3)) - await node3.mountRlnRelay(wakuRlnConfig3) + await node3.setRlnValidator(wakuRlnConfig3) await node3.start() # Registration is mandatory before sending messages with rln-relay - let manager3 = cast[OnchainGroupManager](node3.wakuRlnRelay.groupManager) + let manager3 = cast[OnchainGroupManager](node3.rln.groupManager) let rootUpdated3 = waitFor manager3.updateRoots() info "Updated root for node3", rootUpdated3 @@ -482,7 +480,7 @@ procSuite "WakuNode - RLN relay": await node3.connectToNodes(@[node2.switch.peerInfo.toRemotePeerInfo()]) # get the current epoch time - let epoch_1 = node1.wakuRlnRelay.getCurrentEpoch() + let epoch_1 = node1.rln.getCurrentEpoch() # create some messages with rate limit proofs var @@ -500,7 +498,7 @@ procSuite "WakuNode - RLN relay": # wm3 points to the next epoch await sleepAsync(1000.millis) - let epoch_2 = node1.wakuRlnRelay.getCurrentEpoch() + let epoch_2 = node1.rln.getCurrentEpoch() var wm3 = WakuMessage( @@ -514,12 +512,12 @@ procSuite "WakuNode - RLN relay": contentTopic: DefaultPubsubTopic, ) - node1.wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch_1, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm1, epoch_1, MessageId(0)).isOkOr: raiseAssert $error - node1.wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch_1, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm2, epoch_1, MessageId(0)).isOkOr: raiseAssert $error - node1.wakuRlnRelay.unsafeAppendRLNProof(wm3, epoch_2, MessageId(2)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm3, epoch_2, MessageId(2)).isOkOr: raiseAssert $error # relay handler for node3 @@ -592,16 +590,16 @@ procSuite "WakuNode - RLN relay": epochSizeSec: uint64 = 5 # This means rlnMaxEpochGap = 4 lockNewGlobalBrokerContext: let nodeKey1 = generateSecp256k1Key() - node1 = newTestWakuNode(nodeKey1, parseIpAddress("0.0.0.0"), Port(0)) + node1 = newTestWakuNode(nodeKey1) (await node1.mountRelay()).isOkOr: assert false, "Failed to mount relay" let wakuRlnConfig1 = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node1.mountRlnRelay(wakuRlnConfig1) + await node1.setRlnValidator(wakuRlnConfig1) await node1.start() # Registration is mandatory before sending messages with rln-relay - let manager1 = cast[OnchainGroupManager](node1.wakuRlnRelay.groupManager) + let manager1 = cast[OnchainGroupManager](node1.rln.groupManager) let idCredentials1 = generateCredentials() (waitFor manager1.register(idCredentials1, UserMessageLimit(20))).isOkOr: @@ -611,16 +609,16 @@ procSuite "WakuNode - RLN relay": info "Updated root for node1", rootUpdated1 lockNewGlobalBrokerContext: let nodeKey2 = generateSecp256k1Key() - node2 = newTestWakuNode(nodeKey2, parseIpAddress("0.0.0.0"), Port(0)) + node2 = newTestWakuNode(nodeKey2) (await node2.mountRelay()).isOkOr: assert false, "Failed to mount relay" let wakuRlnConfig2 = getWakuRlnConfig(manager = manager, index = MembershipIndex(2)) - await node2.mountRlnRelay(wakuRlnConfig2) + await node2.setRlnValidator(wakuRlnConfig2) await node2.start() # Registration is mandatory before sending messages with rln-relay - let manager2 = cast[OnchainGroupManager](node2.wakuRlnRelay.groupManager) + let manager2 = cast[OnchainGroupManager](node2.rln.groupManager) let rootUpdated2 = waitFor manager2.updateRoots() info "Updated root for node2", rootUpdated2 @@ -673,26 +671,22 @@ procSuite "WakuNode - RLN relay": # Given all messages have an rln proof and are published by the node 1 let publishSleepDuration: Duration = 5000.millis - let epoch_1 = node1.wakuRlnRelay.calcEpoch(epochTime().float64) - let epoch_2 = node1.wakuRlnRelay.calcEpoch( - epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 1 - ) - let epoch_3 = node1.wakuRlnRelay.calcEpoch( - epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 2 - ) - let epoch_4 = node1.wakuRlnRelay.calcEpoch( - epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 3 - ) - let epoch_5 = node1.wakuRlnRelay.calcEpoch( - epochTime().float64 + node1.wakuRlnRelay.rlnEpochSizeSec.float64 * 4 - ) + let epoch_1 = node1.rln.calcEpoch(epochTime().float64) + let epoch_2 = + node1.rln.calcEpoch(epochTime().float64 + node1.rln.rlnEpochSizeSec.float64 * 1) + let epoch_3 = + node1.rln.calcEpoch(epochTime().float64 + node1.rln.rlnEpochSizeSec.float64 * 2) + let epoch_4 = + node1.rln.calcEpoch(epochTime().float64 + node1.rln.rlnEpochSizeSec.float64 * 3) + let epoch_5 = + node1.rln.calcEpoch(epochTime().float64 + node1.rln.rlnEpochSizeSec.float64 * 4) # Epoch 1 - node1.wakuRlnRelay.unsafeAppendRLNProof(wm1, epoch_1, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm1, epoch_1, MessageId(0)).isOkOr: raiseAssert $error # Message wm2 is published in the same epoch as wm1, so it'll be considered spam - node1.wakuRlnRelay.unsafeAppendRLNProof(wm2, epoch_1, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm2, epoch_1, MessageId(0)).isOkOr: raiseAssert $error discard await node1.publish(some(DefaultPubsubTopic), wm1) @@ -704,7 +698,7 @@ procSuite "WakuNode - RLN relay": # Epoch 2 - node1.wakuRlnRelay.unsafeAppendRLNProof(wm3, epoch_2, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm3, epoch_2, MessageId(0)).isOkOr: raiseAssert $error discard await node1.publish(some(DefaultPubsubTopic), wm3) @@ -716,7 +710,7 @@ procSuite "WakuNode - RLN relay": await node2.waitForNullifierLog(2) # Epoch 3 - node1.wakuRlnRelay.unsafeAppendRLNProof(wm4, epoch_3, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm4, epoch_3, MessageId(0)).isOkOr: raiseAssert $error discard await node1.publish(some(DefaultPubsubTopic), wm4) @@ -726,7 +720,7 @@ procSuite "WakuNode - RLN relay": await node2.waitForNullifierLog(3) # Epoch 4 - node1.wakuRlnRelay.unsafeAppendRLNProof(wm5, epoch_4, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm5, epoch_4, MessageId(0)).isOkOr: raiseAssert $error discard await node1.publish(some(DefaultPubsubTopic), wm5) @@ -736,7 +730,7 @@ procSuite "WakuNode - RLN relay": await node2.waitForNullifierLog(4) # Epoch 5 - node1.wakuRlnRelay.unsafeAppendRLNProof(wm6, epoch_5, MessageId(0)).isOkOr: + node1.rln.unsafeAppendRLNProof(wm6, epoch_5, MessageId(0)).isOkOr: raiseAssert $error discard await node1.publish(some(DefaultPubsubTopic), wm6) diff --git a/tests/waku_rln_relay/utils_offchain.nim b/tests/waku_rln_relay/utils_offchain.nim index 593f31c3f..9976ec783 100644 --- a/tests/waku_rln_relay/utils_offchain.nim +++ b/tests/waku_rln_relay/utils_offchain.nim @@ -12,7 +12,7 @@ from std/times import epochTime import ../../../logos_delivery/waku/ - [node/waku_node, node/peer_manager, waku_core, waku_node, waku_rln_relay], + [node/waku_node, node/peer_manager, waku_core, waku_node, rln], ../waku_store/store_utils, ../waku_archive/archive_utils, ../testlib/[wakucore, futures, assertions] @@ -22,7 +22,7 @@ proc setupStaticRln*( identifier: uint, rlnRelayEthContractAddress: Option[string] = none(string), ) {.async.} = - await node.mountRlnRelay( + await node.setRlnValidator( WakuRlnConfig(dynamic: false, credIndex: some(identifier), epochSizeSec: 1) ) @@ -55,7 +55,7 @@ proc sendRlnMessage*( ): Future[bool] {.async.} = var message = WakuMessage(payload: payload, contentTopic: contentTopic) message.proof = ( - await client.wakuRlnRelay.generateRLNProof(message.toRLNSignal(), epochTime()) + await client.rln.generateRLNProof(message.toRLNSignal(), epochTime()) ).valueOr: raiseAssert "generateRLNProof failed: " & error discard await client.publish(some(pubsubTopic), message) @@ -70,10 +70,10 @@ proc sendRlnMessageWithInvalidProof*( payload: seq[byte] = "Hello".toBytes(), ): Future[bool] {.async.} = let extraBytes: seq[byte] = @[byte(1), 2, 3] - let rateLimitProofRes = await client.wakuRlnRelay.groupManager.generateProof( + let rateLimitProofRes = await client.rln.groupManager.generateProof( concat(payload, extraBytes), # we add extra bytes to invalidate proof verification against original payload - client.wakuRlnRelay.getCurrentEpoch(), + client.rln.getCurrentEpoch(), ) let rateLimitProof = rateLimitProofRes.get().encode().buffer diff --git a/tests/waku_rln_relay/utils_onchain.nim b/tests/waku_rln_relay/utils_onchain.nim index 828b273a2..4955d8d64 100644 --- a/tests/waku_rln_relay/utils_onchain.nim +++ b/tests/waku_rln_relay/utils_onchain.nim @@ -20,12 +20,7 @@ import results import - logos_delivery/waku/[ - waku_rln_relay, - waku_rln_relay/protocol_types, - waku_rln_relay/constants, - waku_rln_relay/rln, - ], + logos_delivery/waku/[rln, rln/protocol_types, rln/constants, rln/bindings], ../testlib/common const CHAIN_ID* = 1234'u256 @@ -42,7 +37,7 @@ proc generateCredentials*(): IdentityCredential = proc getRateCommitment*( idCredential: IdentityCredential, userMessageLimit: UserMessageLimit -): RlnRelayResult[RawRateCommitment] = +): Result[RawRateCommitment, string] = return RateCommitment( idCommitment: idCredential.idCommitment, userMessageLimit: userMessageLimit ).toLeaf() diff --git a/tests/waku_store/test_wakunode_store.nim b/tests/waku_store/test_wakunode_store.nim index 0447bcdfe..9c067488a 100644 --- a/tests/waku_store/test_wakunode_store.nim +++ b/tests/waku_store/test_wakunode_store.nim @@ -66,9 +66,9 @@ procSuite "WakuNode - Store": ## Setup let serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) clientKey = generateSecp256k1Key() - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + client = newTestWakuNode(clientKey) waitFor allFutures(client.start(), server.start()) @@ -101,9 +101,9 @@ procSuite "WakuNode - Store": ## Setup let serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) clientKey = generateSecp256k1Key() - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + client = newTestWakuNode(clientKey) waitFor allFutures(client.start(), server.start()) @@ -157,9 +157,9 @@ procSuite "WakuNode - Store": ## Setup let serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) clientKey = generateSecp256k1Key() - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + client = newTestWakuNode(clientKey) waitFor allFutures(client.start(), server.start()) @@ -214,12 +214,11 @@ procSuite "WakuNode - Store": ## Setup let filterSourceKey = generateSecp256k1Key() - filterSource = - newTestWakuNode(filterSourceKey, parseIpAddress("0.0.0.0"), Port(0)) + filterSource = newTestWakuNode(filterSourceKey) serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) clientKey = generateSecp256k1Key() - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + client = newTestWakuNode(clientKey) waitFor allFutures(client.start(), server.start(), filterSource.start()) @@ -289,9 +288,9 @@ procSuite "WakuNode - Store": ## Setup let serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) clientKey = generateSecp256k1Key() - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + client = newTestWakuNode(clientKey) waitFor allFutures(client.start(), server.start()) @@ -332,9 +331,9 @@ procSuite "WakuNode - Store": ## Setup let serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) clientKey = generateSecp256k1Key() - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + client = newTestWakuNode(clientKey) waitFor allFutures(client.start(), server.start()) @@ -382,9 +381,9 @@ procSuite "WakuNode - Store": ## Setup let serverKey = generateSecp256k1Key() - server = newTestWakuNode(serverKey, parseIpAddress("0.0.0.0"), Port(0)) + server = newTestWakuNode(serverKey) clientKey = generateSecp256k1Key() - client = newTestWakuNode(clientKey, parseIpAddress("0.0.0.0"), Port(0)) + client = newTestWakuNode(clientKey) waitFor allFutures(client.start(), server.start()) diff --git a/tests/wakunode2/test_app.nim b/tests/wakunode2/test_app.nim index eefa866db..879dcd7b5 100644 --- a/tests/wakunode2/test_app.nim +++ b/tests/wakunode2/test_app.nim @@ -10,7 +10,7 @@ import tests/testlib/[wakucore, wakunode], logos_delivery/waku/factory/conf_builder/conf_builder -include logos_delivery/waku/factory/waku, logos_delivery/waku/common/enr/typed_record +include logos_delivery/waku/waku, logos_delivery/waku/common/enr/typed_record suite "Wakunode2 - Waku": test "compilation version should be reported": @@ -170,3 +170,42 @@ suite "Wakunode2 - Waku initialization": check: quicAddrs.len >= 1 quicAddrs.allIt("/udp/0/quic-v1" notin $it) + + test "portsShift is applied exactly once": + # The announced port must equal the bound port, not bound + portsShift. + const shift = 5'u16 + + # Reserve a free port, then set base = port - shift so base + shift binds onto it. + let boundTarget = block: + let sock = newSocket() + defer: + sock.close() + sock.bindAddr(Port(0), "127.0.0.1") + sock.getLocalAddr()[1] + doAssert boundTarget.uint16 > shift, "ephemeral port unexpectedly low" + + var builder = defaultTestWakuConfBuilder() + builder.withP2pListenAddress(parseIpAddress("127.0.0.1")) + builder.withP2pTcpPort(Port(boundTarget.uint16 - shift)) + builder.withPortsShift(shift) + + let conf = builder.build().valueOr: + raiseAssert error + + var waku = (waitFor Waku.new(conf)).valueOr: + raiseAssert error + defer: + (waitFor waku.stop()).isOkOr: + raiseAssert error + + (waitFor waku.start()).isOkOr: + raiseAssert error + + let typedEnr = waku.node.enr.toTyped().valueOr: + raiseAssert $error + let announcedTcp = typedEnr.tcp() + + check: + announcedTcp.isSome() + waku.node.ports.tcp == boundTarget.uint16 + announcedTcp.get() == waku.node.ports.tcp diff --git a/tests/wakunode2/test_validators.nim b/tests/wakunode2/test_validators.nim index 7a84c365a..fc4c2cf57 100644 --- a/tests/wakunode2/test_validators.nim +++ b/tests/wakunode2/test_validators.nim @@ -21,9 +21,7 @@ import suite "WakuNode2 - Validators": asyncTest "Spam protected topic accepts signed messages": # Create 5 nodes - let nodes = toSeq(0 ..< 5).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 5).mapIt(newTestWakuNode(generateSecp256k1Key())) # Protected shard and key to sign let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7) @@ -108,9 +106,7 @@ suite "WakuNode2 - Validators": asyncTest "Spam protected topic rejects non-signed/wrongly-signed/no-timestamp messages": # Create 5 nodes - let nodes = toSeq(0 ..< 5).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 5).mapIt(newTestWakuNode(generateSecp256k1Key())) # Protected shard and key to sign let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7) @@ -260,9 +256,7 @@ suite "WakuNode2 - Validators": asyncTest "Spam protected topic rejects a spammer node": # Create 5 nodes - let nodes = toSeq(0 ..< 5).mapIt( - newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0)) - ) + let nodes = toSeq(0 ..< 5).mapIt(newTestWakuNode(generateSecp256k1Key())) # Protected shard and key to sign let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7) diff --git a/tests/wakunode_rest/test_rest_health.nim b/tests/wakunode_rest/test_rest_health.nim index f2d94f6e5..4a2fa0250 100644 --- a/tests/wakunode_rest/test_rest_health.nim +++ b/tests/wakunode_rest/test_rest_health.nim @@ -19,7 +19,7 @@ import rest_api/endpoint/responses, rest_api/endpoint/health/handlers as health_rest_interface, rest_api/endpoint/health/client as health_rest_client, - waku_rln_relay, + rln, node/health_monitor, ], ../testlib/common, @@ -67,7 +67,7 @@ suite "Waku v2 REST API - health": let client = newRestHttpClient(initTAddress(restAddress, restPort)) # kick in rln (currently the only check for health) - await node.mountRlnRelay( + await node.setRlnValidator( getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) ) diff --git a/tests/wakunode_rest/test_rest_relay.nim b/tests/wakunode_rest/test_rest_relay.nim index e90ccb31d..ccf59b4fd 100644 --- a/tests/wakunode_rest/test_rest_relay.nim +++ b/tests/wakunode_rest/test_rest_relay.nim @@ -21,7 +21,7 @@ import rest_api/endpoint/relay/handlers as relay_rest_interface, rest_api/endpoint/relay/client as relay_rest_client, waku_relay, - waku_rln_relay, + rln, ], ../testlib/wakucore, ../testlib/wakunode, @@ -261,10 +261,10 @@ suite "Waku v2 Rest API - Relay": assert false, "Failed to mount relay" let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node.mountRlnRelay(wakuRlnConfig) + await node.setRlnValidator(wakuRlnConfig) await node.start() # Registration is mandatory before sending messages with rln-relay - let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager) + let manager = cast[OnchainGroupManager](node.rln.groupManager) let idCredentials = generateCredentials() (waitFor manager.register(idCredentials, UserMessageLimit(20))).isOkOr: @@ -509,7 +509,7 @@ suite "Waku v2 Rest API - Relay": let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await meshNode.mountRlnRelay(wakuRlnConfig) + await meshNode.setRlnValidator(wakuRlnConfig) await meshNode.start() const testPubsubTopic = PubsubTopic("/waku/2/rs/1/0") proc dummyHandler( @@ -530,12 +530,12 @@ suite "Waku v2 Rest API - Relay": let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node.mountRlnRelay(wakuRlnConfig) + await node.setRlnValidator(wakuRlnConfig) await node.start() await node.connectToNodes(@[meshNode.peerInfo.toRemotePeerInfo()]) # Registration is mandatory before sending messages with rln-relay - let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager) + let manager = cast[OnchainGroupManager](node.rln.groupManager) let idCredentials = generateCredentials() (waitFor manager.register(idCredentials, UserMessageLimit(20))).isOkOr: @@ -600,11 +600,11 @@ suite "Waku v2 Rest API - Relay": require node.mountAutoSharding(1, 8).isOk let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node.mountRlnRelay(wakuRlnConfig) + await node.setRlnValidator(wakuRlnConfig) await node.start() # Registration is mandatory before sending messages with rln-relay - let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager) + let manager = cast[OnchainGroupManager](node.rln.groupManager) let idCredentials = generateCredentials() (waitFor manager.register(idCredentials, UserMessageLimit(20))).isOkOr: @@ -659,11 +659,11 @@ suite "Waku v2 Rest API - Relay": (await node.mountRelay()).isOkOr: assert false, "Failed to mount relay" let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node.mountRlnRelay(wakuRlnConfig) + await node.setRlnValidator(wakuRlnConfig) await node.start() # Registration is mandatory before sending messages with rln-relay - let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager) + let manager = cast[OnchainGroupManager](node.rln.groupManager) let idCredentials = generateCredentials() (waitFor manager.register(idCredentials, UserMessageLimit(20))).isOkOr: @@ -731,11 +731,11 @@ suite "Waku v2 Rest API - Relay": require node.mountAutoSharding(1, 8).isOk let wakuRlnConfig = getWakuRlnConfig(manager = manager, index = MembershipIndex(1)) - await node.mountRlnRelay(wakuRlnConfig) + await node.setRlnValidator(wakuRlnConfig) await node.start() # Registration is mandatory before sending messages with rln-relay - let manager = cast[OnchainGroupManager](node.wakuRlnRelay.groupManager) + let manager = cast[OnchainGroupManager](node.rln.groupManager) let idCredentials = generateCredentials() (waitFor manager.register(idCredentials, UserMessageLimit(20))).isOkOr: diff --git a/tools/confutils/cli_args.nim b/tools/confutils/cli_args.nim index 5228dfff7..aa7b6cb1b 100644 --- a/tools/confutils/cli_args.nim +++ b/tools/confutils/cli_args.nim @@ -60,11 +60,6 @@ type StartUpCommand* = enum noCommand # default, runs waku generateRlnKeystore # generates a new RLN keystore -type WakuMode* {.pure.} = enum - noMode # default - use explicit CLI flags as-is - Core # full service node - Edge # client-only node - type WakuNodeConf* = object configFile* {. desc: "Loads configuration from a TOML file (cmd-line parameters take precedence)", @@ -166,16 +161,9 @@ 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). Overrides other values.", + "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.", defaultValue: "", name: "preset" .}: string @@ -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) @@ -1001,6 +976,8 @@ proc toNetworkPresetConf( ok(some(NetworkPresetConf.LogosDevConf())) of "logos.test", "logostest": ok(some(NetworkPresetConf.LogosTestConf())) + of "status.prod", "statusprod": + ok(some(NetworkPresetConf.StatusProdConf())) else: err("Invalid --preset value passed: " & lcPreset) @@ -1144,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) @@ -1188,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) @@ -1204,25 +1185,4 @@ proc toWakuConf*(n: WakuNodeConf): ConfResult[WakuConf] = chronos.seconds(n.kadServiceLookupIntervalSec.int64) ) - # Mode-driven configuration overrides - case n.mode - of WakuMode.Core: - b.withRelay(true) - b.filterServiceConf.withEnabled(true) - b.withLightPush(true) - b.discv5Conf.withEnabled(true) - b.withPeerExchange(true) - b.withRendezvous(true) - b.rateLimitConf.withRateLimitsIfNotAssigned( - @["filter:100/1s", "lightpush:5/1s", "px:5/1s"] - ) - of WakuMode.Edge: - b.withPeerExchange(true) - b.withRelay(false) - b.filterServiceConf.withEnabled(false) - b.withLightPush(false) - b.storeServiceConf.withEnabled(false) - of WakuMode.noMode: - discard # use explicit CLI flags as-is - return b.build() diff --git a/tools/confutils/conf_from_json.nim b/tools/confutils/conf_from_json.nim index 69cca0ce7..7e78fd300 100644 --- a/tools/confutils/conf_from_json.nim +++ b/tools/confutils/conf_from_json.nim @@ -1,6 +1,9 @@ -import std/[json, macros, strutils, tables] +import std/[json, macros, options, strutils, tables] import confutils, confutils/defs, confutils/std/net, results -import ./cli_args + +# The shared JSON walker is `raises: []` so the messaging FFI parser (also +# `raises: []`) can build on it. +{.push raises: [].} proc collectJsonFields*( jsonNode: JsonNode @@ -12,7 +15,7 @@ proc collectJsonFields*( for key, value in jsonNode: let lowerKey = key.toLowerAscii() if jsonFields.hasKey(lowerKey): - let firstKey = jsonFields[lowerKey][0] + let firstKey = jsonFields.getOrDefault(lowerKey)[0] return err( "Duplicate configuration option (case-insensitive): '" & firstKey & "' and '" & key & "'" @@ -20,14 +23,14 @@ proc collectJsonFields*( jsonFields[lowerKey] = (key, value) return ok(jsonFields) -proc unknownKeysError( +proc unknownKeysError*( jsonFields: Table[string, (string, JsonNode)], prefix: string ): string = ## Format leftover JSON keys as an error message. 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,62 @@ 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) + when U is Port: + # `Port(0)` means "auto-allocate an ephemeral port" and is a valid value (see + # PR #3828). confutils' `parseCmdArg(Port)` enforces the CLI range 1-65535 and + # rejects "0", but the JSON config API must accept it, so handle 0 explicitly and + # delegate every other value to confutils. + if s == "0": + return ok(Port(0)) + 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 +111,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.} diff --git a/tools/rln_keystore_generator/rln_keystore_generator.nim b/tools/rln_keystore_generator/rln_keystore_generator.nim index 16508178b..9643f039f 100644 --- a/tools/rln_keystore_generator/rln_keystore_generator.nim +++ b/tools/rln_keystore_generator/rln_keystore_generator.nim @@ -6,12 +6,8 @@ else: import chronicles, results, std/[tempfiles, sequtils] import - logos_delivery/waku/[ - waku_keystore, - waku_rln_relay/rln, - waku_rln_relay/conversion_utils, - waku_rln_relay/group_manager/on_chain, - ] + logos_delivery/waku/ + [waku_keystore, rln/bindings, rln/conversion_utils, rln/group_manager/on_chain] logScope: topics = "rln_keystore_generator"